summaryrefslogtreecommitdiff
path: root/usr.bin
diff options
context:
space:
mode:
authorKiyoshi Aman <kiyoshi.aman+adelie@gmail.com>2019-02-01 22:55:37 +0000
committerKiyoshi Aman <kiyoshi.aman+adelie@gmail.com>2019-02-03 18:22:05 -0600
commit5b57d28ffb6e1ef86b50f7d05d977826eae89bfe (patch)
tree154a22fe556b49e6927197336f8bf91b12eacd5e /usr.bin
downloaduserland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.gz
userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.bz2
userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.xz
userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.zip
initial population
Diffstat (limited to 'usr.bin')
-rw-r--r--usr.bin/asa/asa.185
-rw-r--r--usr.bin/asa/asa.c116
-rw-r--r--usr.bin/at/at.1339
-rw-r--r--usr.bin/at/at.c758
-rw-r--r--usr.bin/at/at.h43
-rw-r--r--usr.bin/at/panic.c110
-rw-r--r--usr.bin/at/panic.h37
-rw-r--r--usr.bin/at/parsetime.c652
-rw-r--r--usr.bin/at/parsetime.h35
-rw-r--r--usr.bin/at/pathnames.h51
-rw-r--r--usr.bin/at/perm.c118
-rw-r--r--usr.bin/at/perm.h33
-rw-r--r--usr.bin/at/privs.c104
-rw-r--r--usr.bin/at/privs.h76
-rw-r--r--usr.bin/at/stime.c116
-rw-r--r--usr.bin/at/stime.h53
-rw-r--r--usr.bin/basename/basename.192
-rw-r--r--usr.bin/basename/basename.c103
-rw-r--r--usr.bin/c99/c99.181
-rw-r--r--usr.bin/c99/c99.sh2
-rw-r--r--usr.bin/cal/README42
-rw-r--r--usr.bin/cal/cal.1164
-rw-r--r--usr.bin/cal/cal.c924
-rw-r--r--usr.bin/cksum/cksum.1341
-rw-r--r--usr.bin/cksum/cksum.c551
-rw-r--r--usr.bin/cksum/crc.c162
-rw-r--r--usr.bin/cksum/crc_extern.h38
-rw-r--r--usr.bin/cksum/extern.h88
-rw-r--r--usr.bin/cksum/md2.c21
-rw-r--r--usr.bin/cksum/md4.c21
-rw-r--r--usr.bin/cksum/md5.c153
-rw-r--r--usr.bin/cksum/print.c76
-rw-r--r--usr.bin/cksum/rmd160.c25
-rw-r--r--usr.bin/cksum/sha1.c21
-rw-r--r--usr.bin/cksum/sha256.c25
-rw-r--r--usr.bin/cksum/sha384.c25
-rw-r--r--usr.bin/cksum/sha512.c25
-rw-r--r--usr.bin/cksum/sum1.c76
-rw-r--r--usr.bin/cksum/sum2.c80
-rw-r--r--usr.bin/cmp/cmp.1122
-rw-r--r--usr.bin/cmp/cmp.c168
-rw-r--r--usr.bin/cmp/extern.h44
-rw-r--r--usr.bin/cmp/misc.c84
-rw-r--r--usr.bin/cmp/regular.c118
-rw-r--r--usr.bin/cmp/special.c111
-rw-r--r--usr.bin/comm/comm.1102
-rw-r--r--usr.bin/comm/comm.c213
-rw-r--r--usr.bin/compress/compress.1171
-rw-r--r--usr.bin/compress/compress.c459
-rw-r--r--usr.bin/compress/doc/NOTES139
-rw-r--r--usr.bin/compress/doc/README283
-rw-r--r--usr.bin/compress/doc/revision.log116
-rw-r--r--usr.bin/compress/zopen.3138
-rw-r--r--usr.bin/compress/zopen.c699
-rw-r--r--usr.bin/csplit/csplit.1163
-rw-r--r--usr.bin/csplit/csplit.c479
-rw-r--r--usr.bin/ctags/C.c573
-rw-r--r--usr.bin/ctags/ctags.1225
-rw-r--r--usr.bin/ctags/ctags.c275
-rw-r--r--usr.bin/ctags/ctags.h92
-rw-r--r--usr.bin/ctags/fortran.c174
-rw-r--r--usr.bin/ctags/lisp.c112
-rw-r--r--usr.bin/ctags/print.c121
-rw-r--r--usr.bin/ctags/test/ctags.test69
-rw-r--r--usr.bin/ctags/tree.c145
-rw-r--r--usr.bin/ctags/yacc.c160
-rw-r--r--usr.bin/cut/cut.1126
-rw-r--r--usr.bin/cut/cut.c306
-rw-r--r--usr.bin/cut/x_cut.c95
-rw-r--r--usr.bin/dirname/dirname.c86
-rw-r--r--usr.bin/du/du.1168
-rw-r--r--usr.bin/du/du.c362
-rw-r--r--usr.bin/env/env.1122
-rw-r--r--usr.bin/env/env.c99
-rw-r--r--usr.bin/expand/expand.187
-rw-r--r--usr.bin/expand/expand.c182
-rw-r--r--usr.bin/false/false.156
-rw-r--r--usr.bin/false/false.sh2
-rw-r--r--usr.bin/find/extern.h102
-rw-r--r--usr.bin/find/find.1974
-rw-r--r--usr.bin/find/find.c306
-rw-r--r--usr.bin/find/find.h138
-rw-r--r--usr.bin/find/function.c2025
-rw-r--r--usr.bin/find/ls.c124
-rw-r--r--usr.bin/find/main.c164
-rw-r--r--usr.bin/find/misc.c151
-rw-r--r--usr.bin/find/operator.c272
-rw-r--r--usr.bin/find/option.c194
-rw-r--r--usr.bin/fold/fold.193
-rw-r--r--usr.bin/fold/fold.c248
-rw-r--r--usr.bin/gencat/gencat.1233
-rw-r--r--usr.bin/gencat/gencat.c879
-rw-r--r--usr.bin/getconf/getconf.194
-rw-r--r--usr.bin/getconf/getconf.c342
-rw-r--r--usr.bin/grep/fastgrep.c336
-rw-r--r--usr.bin/grep/file.c276
-rw-r--r--usr.bin/grep/grep.1486
-rw-r--r--usr.bin/grep/grep.c722
-rw-r--r--usr.bin/grep/grep.h162
-rw-r--r--usr.bin/grep/nls/C.msg13
-rw-r--r--usr.bin/grep/nls/es_ES.ISO8859-1.msg14
-rw-r--r--usr.bin/grep/nls/gl_ES.ISO8859-1.msg14
-rw-r--r--usr.bin/grep/nls/hu_HU.ISO8859-2.msg14
-rw-r--r--usr.bin/grep/nls/ja_JP.SJIS.msg14
-rw-r--r--usr.bin/grep/nls/ja_JP.UTF-8.msg14
-rw-r--r--usr.bin/grep/nls/ja_JP.eucJP.msg14
-rw-r--r--usr.bin/grep/nls/pt_BR.ISO8859-1.msg14
-rw-r--r--usr.bin/grep/nls/ru_RU.KOI8-R.msg14
-rw-r--r--usr.bin/grep/nls/uk_UA.UTF-8.msg13
-rw-r--r--usr.bin/grep/nls/zh_CN.UTF-8.msg14
-rw-r--r--usr.bin/grep/queue.c116
-rw-r--r--usr.bin/grep/util.c500
-rw-r--r--usr.bin/head/head.195
-rw-r--r--usr.bin/head/head.c204
-rw-r--r--usr.bin/iconv/iconv.1122
-rw-r--r--usr.bin/iconv/iconv.c236
-rw-r--r--usr.bin/id/groups.159
-rw-r--r--usr.bin/id/id.1136
-rw-r--r--usr.bin/id/id.c370
-rw-r--r--usr.bin/id/whoami.158
-rw-r--r--usr.bin/ipcrm/ipcrm.197
-rw-r--r--usr.bin/ipcrm/ipcrm.c313
-rw-r--r--usr.bin/ipcs/ipcs.1141
-rw-r--r--usr.bin/ipcs/ipcs.c678
-rw-r--r--usr.bin/join/join.1206
-rw-r--r--usr.bin/join/join.c637
-rw-r--r--usr.bin/ldd/Makefile.common16
-rw-r--r--usr.bin/ldd/Makefile.elf10
-rw-r--r--usr.bin/ldd/build/Makefile42
-rw-r--r--usr.bin/ldd/dummy.c3
-rw-r--r--usr.bin/ldd/elf32/Makefile33
-rw-r--r--usr.bin/ldd/elf32_compat/Makefile26
-rw-r--r--usr.bin/ldd/elf64/Makefile51
-rw-r--r--usr.bin/ldd/ldd.1126
-rw-r--r--usr.bin/ldd/ldd.c237
-rw-r--r--usr.bin/ldd/ldd.h47
-rw-r--r--usr.bin/ldd/ldd_elfxx.c269
-rw-r--r--usr.bin/locale/locale.1113
-rw-r--r--usr.bin/locale/locale.c707
-rw-r--r--usr.bin/logger/logger.1117
-rw-r--r--usr.bin/logger/logger.c203
-rw-r--r--usr.bin/logname/logname.172
-rw-r--r--usr.bin/logname/logname.c85
-rw-r--r--usr.bin/m4/NOTES64
-rw-r--r--usr.bin/m4/PSD.doc/Makefile10
-rw-r--r--usr.bin/m4/TEST/ack.m441
-rw-r--r--usr.bin/m4/TEST/hanoi.m446
-rw-r--r--usr.bin/m4/TEST/hash.m456
-rw-r--r--usr.bin/m4/TEST/math.m4182
-rw-r--r--usr.bin/m4/TEST/sqroot.m446
-rw-r--r--usr.bin/m4/TEST/string.m446
-rw-r--r--usr.bin/m4/TEST/test.m4244
-rw-r--r--usr.bin/m4/eval.c1053
-rw-r--r--usr.bin/m4/expr.c50
-rw-r--r--usr.bin/m4/extern.h190
-rw-r--r--usr.bin/m4/gnum4.c817
-rw-r--r--usr.bin/m4/lib/ohash.h73
-rw-r--r--usr.bin/m4/lib/ohash_create_entry.c38
-rw-r--r--usr.bin/m4/lib/ohash_delete.c31
-rw-r--r--usr.bin/m4/lib/ohash_do.c111
-rw-r--r--usr.bin/m4/lib/ohash_entries.c26
-rw-r--r--usr.bin/m4/lib/ohash_enum.c36
-rw-r--r--usr.bin/m4/lib/ohash_init.3229
-rw-r--r--usr.bin/m4/lib/ohash_init.c41
-rw-r--r--usr.bin/m4/lib/ohash_int.h23
-rw-r--r--usr.bin/m4/lib/ohash_interval.390
-rw-r--r--usr.bin/m4/lib/ohash_interval.c36
-rw-r--r--usr.bin/m4/lib/ohash_lookup_interval.c68
-rw-r--r--usr.bin/m4/lib/ohash_lookup_memory.c64
-rw-r--r--usr.bin/m4/lib/ohash_qlookup.c27
-rw-r--r--usr.bin/m4/lib/ohash_qlookupi.c29
-rw-r--r--usr.bin/m4/lib/strtonum.c73
-rw-r--r--usr.bin/m4/look.c315
-rw-r--r--usr.bin/m4/m4.1522
-rw-r--r--usr.bin/m4/main.c811
-rw-r--r--usr.bin/m4/mdef.h237
-rw-r--r--usr.bin/m4/misc.c414
-rw-r--r--usr.bin/m4/parser.y86
-rw-r--r--usr.bin/m4/pathnames.h40
-rw-r--r--usr.bin/m4/stdd.h54
-rw-r--r--usr.bin/m4/tokenizer.l108
-rw-r--r--usr.bin/m4/trace.c203
-rw-r--r--usr.bin/make/Makefile.boot45
-rw-r--r--usr.bin/make/PSD.doc/Makefile10
-rw-r--r--usr.bin/make/PSD.doc/tutorial.ms3794
-rw-r--r--usr.bin/make/arch.c1369
-rw-r--r--usr.bin/make/buf.c291
-rw-r--r--usr.bin/make/buf.h119
-rw-r--r--usr.bin/make/compat.c778
-rw-r--r--usr.bin/make/cond.c1436
-rw-r--r--usr.bin/make/config.h160
-rw-r--r--usr.bin/make/dir.c1849
-rw-r--r--usr.bin/make/dir.h108
-rw-r--r--usr.bin/make/for.c496
-rw-r--r--usr.bin/make/hash.c466
-rw-r--r--usr.bin/make/hash.h149
-rw-r--r--usr.bin/make/job.c3070
-rw-r--r--usr.bin/make/job.h274
-rw-r--r--usr.bin/make/lst.h189
-rw-r--r--usr.bin/make/lst.lib/Makefile10
-rw-r--r--usr.bin/make/lst.lib/lstAppend.c122
-rw-r--r--usr.bin/make/lst.lib/lstAtEnd.c79
-rw-r--r--usr.bin/make/lst.lib/lstAtFront.c76
-rw-r--r--usr.bin/make/lst.lib/lstClose.c86
-rw-r--r--usr.bin/make/lst.lib/lstConcat.c185
-rw-r--r--usr.bin/make/lst.lib/lstDatum.c77
-rw-r--r--usr.bin/make/lst.lib/lstDeQueue.c87
-rw-r--r--usr.bin/make/lst.lib/lstDestroy.c101
-rw-r--r--usr.bin/make/lst.lib/lstDupl.c107
-rw-r--r--usr.bin/make/lst.lib/lstEnQueue.c78
-rw-r--r--usr.bin/make/lst.lib/lstFind.c74
-rw-r--r--usr.bin/make/lst.lib/lstFindFrom.c90
-rw-r--r--usr.bin/make/lst.lib/lstFirst.c77
-rw-r--r--usr.bin/make/lst.lib/lstForEach.c76
-rw-r--r--usr.bin/make/lst.lib/lstForEachFrom.c125
-rw-r--r--usr.bin/make/lst.lib/lstInit.c85
-rw-r--r--usr.bin/make/lst.lib/lstInsert.c122
-rw-r--r--usr.bin/make/lst.lib/lstInt.h105
-rw-r--r--usr.bin/make/lst.lib/lstIsAtEnd.c87
-rw-r--r--usr.bin/make/lst.lib/lstIsEmpty.c75
-rw-r--r--usr.bin/make/lst.lib/lstLast.c77
-rw-r--r--usr.bin/make/lst.lib/lstMember.c77
-rw-r--r--usr.bin/make/lst.lib/lstNext.c120
-rw-r--r--usr.bin/make/lst.lib/lstOpen.c87
-rw-r--r--usr.bin/make/lst.lib/lstPrev.c79
-rw-r--r--usr.bin/make/lst.lib/lstRemove.c136
-rw-r--r--usr.bin/make/lst.lib/lstReplace.c78
-rw-r--r--usr.bin/make/lst.lib/lstSucc.c79
-rw-r--r--usr.bin/make/main.c2189
-rw-r--r--usr.bin/make/make.12413
-rw-r--r--usr.bin/make/make.c1555
-rw-r--r--usr.bin/make/make.h535
-rw-r--r--usr.bin/make/make_malloc.c119
-rw-r--r--usr.bin/make/make_malloc.h41
-rw-r--r--usr.bin/make/meta.c1641
-rw-r--r--usr.bin/make/meta.h56
-rw-r--r--usr.bin/make/metachar.c88
-rw-r--r--usr.bin/make/metachar.h61
-rw-r--r--usr.bin/make/nonints.h198
-rw-r--r--usr.bin/make/parse.c3357
-rw-r--r--usr.bin/make/pathnames.h53
-rw-r--r--usr.bin/make/sprite.h116
-rw-r--r--usr.bin/make/str.c526
-rw-r--r--usr.bin/make/strlist.c93
-rw-r--r--usr.bin/make/strlist.h62
-rw-r--r--usr.bin/make/suff.c2676
-rw-r--r--usr.bin/make/targ.c846
-rw-r--r--usr.bin/make/trace.c116
-rw-r--r--usr.bin/make/trace.h49
-rw-r--r--usr.bin/make/unit-tests/Makefile139
-rw-r--r--usr.bin/make/unit-tests/comment.exp5
-rw-r--r--usr.bin/make/unit-tests/comment.mk31
-rw-r--r--usr.bin/make/unit-tests/cond1.exp23
-rw-r--r--usr.bin/make/unit-tests/cond1.mk109
-rw-r--r--usr.bin/make/unit-tests/cond2.exp7
-rw-r--r--usr.bin/make/unit-tests/cond2.mk29
-rw-r--r--usr.bin/make/unit-tests/doterror.exp9
-rw-r--r--usr.bin/make/unit-tests/doterror.mk20
-rw-r--r--usr.bin/make/unit-tests/dotwait.exp30
-rw-r--r--usr.bin/make/unit-tests/dotwait.mk61
-rw-r--r--usr.bin/make/unit-tests/error.exp4
-rw-r--r--usr.bin/make/unit-tests/error.mk10
-rw-r--r--usr.bin/make/unit-tests/escape.exp104
-rw-r--r--usr.bin/make/unit-tests/escape.mk246
-rw-r--r--usr.bin/make/unit-tests/export-all.exp12
-rw-r--r--usr.bin/make/unit-tests/export-all.mk23
-rw-r--r--usr.bin/make/unit-tests/export-env.exp11
-rw-r--r--usr.bin/make/unit-tests/export-env.mk27
-rw-r--r--usr.bin/make/unit-tests/export.exp6
-rw-r--r--usr.bin/make/unit-tests/export.mk22
-rw-r--r--usr.bin/make/unit-tests/forloop.exp19
-rw-r--r--usr.bin/make/unit-tests/forloop.mk45
-rw-r--r--usr.bin/make/unit-tests/forsubst.exp2
-rw-r--r--usr.bin/make/unit-tests/forsubst.mk10
-rw-r--r--usr.bin/make/unit-tests/hash.exp9
-rw-r--r--usr.bin/make/unit-tests/hash.mk18
-rw-r--r--usr.bin/make/unit-tests/impsrc.exp13
-rw-r--r--usr.bin/make/unit-tests/impsrc.mk43
-rw-r--r--usr.bin/make/unit-tests/misc.exp1
-rw-r--r--usr.bin/make/unit-tests/misc.mk16
-rw-r--r--usr.bin/make/unit-tests/moderrs.exp16
-rw-r--r--usr.bin/make/unit-tests/moderrs.mk31
-rw-r--r--usr.bin/make/unit-tests/modmatch.exp20
-rw-r--r--usr.bin/make/unit-tests/modmatch.mk34
-rw-r--r--usr.bin/make/unit-tests/modmisc.exp10
-rw-r--r--usr.bin/make/unit-tests/modmisc.mk38
-rw-r--r--usr.bin/make/unit-tests/modorder.exp11
-rw-r--r--usr.bin/make/unit-tests/modorder.mk22
-rw-r--r--usr.bin/make/unit-tests/modts.exp39
-rw-r--r--usr.bin/make/unit-tests/modts.mk44
-rw-r--r--usr.bin/make/unit-tests/modword.exp122
-rw-r--r--usr.bin/make/unit-tests/modword.mk151
-rw-r--r--usr.bin/make/unit-tests/order.exp4
-rw-r--r--usr.bin/make/unit-tests/order.mk20
-rw-r--r--usr.bin/make/unit-tests/phony-end.exp6
-rw-r--r--usr.bin/make/unit-tests/phony-end.mk9
-rw-r--r--usr.bin/make/unit-tests/posix.exp23
-rw-r--r--usr.bin/make/unit-tests/posix.mk24
-rw-r--r--usr.bin/make/unit-tests/posix1.exp186
-rw-r--r--usr.bin/make/unit-tests/posix1.mk184
-rw-r--r--usr.bin/make/unit-tests/qequals.exp2
-rw-r--r--usr.bin/make/unit-tests/qequals.mk8
-rw-r--r--usr.bin/make/unit-tests/suffixes.exp35
-rw-r--r--usr.bin/make/unit-tests/suffixes.mk89
-rw-r--r--usr.bin/make/unit-tests/sunshcmd.exp4
-rw-r--r--usr.bin/make/unit-tests/sunshcmd.mk10
-rw-r--r--usr.bin/make/unit-tests/sysv.exp7
-rw-r--r--usr.bin/make/unit-tests/sysv.mk26
-rw-r--r--usr.bin/make/unit-tests/ternary.exp10
-rw-r--r--usr.bin/make/unit-tests/ternary.mk8
-rw-r--r--usr.bin/make/unit-tests/unexport-env.exp2
-rw-r--r--usr.bin/make/unit-tests/unexport-env.mk14
-rw-r--r--usr.bin/make/unit-tests/unexport.exp4
-rw-r--r--usr.bin/make/unit-tests/unexport.mk8
-rw-r--r--usr.bin/make/unit-tests/varcmd.exp11
-rw-r--r--usr.bin/make/unit-tests/varcmd.mk60
-rw-r--r--usr.bin/make/unit-tests/varmisc.exp25
-rw-r--r--usr.bin/make/unit-tests/varmisc.mk62
-rw-r--r--usr.bin/make/unit-tests/varquote.exp3
-rw-r--r--usr.bin/make/unit-tests/varquote.mk14
-rw-r--r--usr.bin/make/unit-tests/varshell.exp12
-rw-r--r--usr.bin/make/unit-tests/varshell.mk18
-rw-r--r--usr.bin/make/util.c494
-rw-r--r--usr.bin/make/var.c4355
-rw-r--r--usr.bin/man/man.1269
-rw-r--r--usr.bin/man/man.c1077
-rw-r--r--usr.bin/man/man.conf.5286
-rw-r--r--usr.bin/man/manconf.c272
-rw-r--r--usr.bin/man/manconf.h60
-rw-r--r--usr.bin/man/pathnames.h39
-rw-r--r--usr.bin/mesg/mesg.189
-rw-r--r--usr.bin/mesg/mesg.c108
-rw-r--r--usr.bin/mkfifo/mkfifo.186
-rw-r--r--usr.bin/mkfifo/mkfifo.c107
-rw-r--r--usr.bin/mkstr/mkstr.1131
-rw-r--r--usr.bin/mkstr/mkstr.c320
-rw-r--r--usr.bin/newgrp/grutil.c338
-rw-r--r--usr.bin/newgrp/grutil.h40
-rw-r--r--usr.bin/newgrp/newgrp.1121
-rw-r--r--usr.bin/newgrp/newgrp.c195
-rw-r--r--usr.bin/nice/nice.1120
-rw-r--r--usr.bin/nice/nice.c123
-rw-r--r--usr.bin/nl/nl.1214
-rw-r--r--usr.bin/nl/nl.c406
-rw-r--r--usr.bin/nohup/nohup.1115
-rw-r--r--usr.bin/nohup/nohup.c142
-rw-r--r--usr.bin/paste/paste.1116
-rw-r--r--usr.bin/paste/paste.c233
-rw-r--r--usr.bin/patch/backupfile.c254
-rw-r--r--usr.bin/patch/backupfile.h42
-rw-r--r--usr.bin/patch/common.h123
-rw-r--r--usr.bin/patch/inp.c516
-rw-r--r--usr.bin/patch/inp.h35
-rw-r--r--usr.bin/patch/mkpath.c84
-rw-r--r--usr.bin/patch/patch.1663
-rw-r--r--usr.bin/patch/patch.c1064
-rw-r--r--usr.bin/patch/pathnames.h14
-rw-r--r--usr.bin/patch/pch.c1600
-rw-r--r--usr.bin/patch/pch.h59
-rw-r--r--usr.bin/patch/util.c447
-rw-r--r--usr.bin/patch/util.h51
-rw-r--r--usr.bin/pathchk/pathchk.1123
-rw-r--r--usr.bin/pathchk/pathchk.c191
-rw-r--r--usr.bin/pr/egetopt.c215
-rw-r--r--usr.bin/pr/extern.h42
-rw-r--r--usr.bin/pr/pr.1361
-rw-r--r--usr.bin/pr/pr.c1901
-rw-r--r--usr.bin/pr/pr.h79
-rw-r--r--usr.bin/printf/printf.1438
-rw-r--r--usr.bin/printf/printf.c709
-rw-r--r--usr.bin/renice/renice.8151
-rw-r--r--usr.bin/renice/renice.c181
-rw-r--r--usr.bin/sed/POSIX205
-rw-r--r--usr.bin/sed/TEST/hanoi.sed103
-rw-r--r--usr.bin/sed/TEST/math.sed164
-rw-r--r--usr.bin/sed/TEST/sed.test554
-rw-r--r--usr.bin/sed/compile.c946
-rw-r--r--usr.bin/sed/defs.h150
-rw-r--r--usr.bin/sed/extern.h61
-rw-r--r--usr.bin/sed/main.c517
-rw-r--r--usr.bin/sed/misc.c119
-rw-r--r--usr.bin/sed/process.c792
-rw-r--r--usr.bin/sed/sed.1640
-rw-r--r--usr.bin/sort/append.c94
-rw-r--r--usr.bin/sort/fields.c380
-rw-r--r--usr.bin/sort/files.c279
-rw-r--r--usr.bin/sort/fsort.c202
-rw-r--r--usr.bin/sort/fsort.h78
-rw-r--r--usr.bin/sort/init.c447
-rw-r--r--usr.bin/sort/msort.c431
-rw-r--r--usr.bin/sort/pathnames.h66
-rw-r--r--usr.bin/sort/radix_sort.c217
-rw-r--r--usr.bin/sort/sort.1525
-rw-r--r--usr.bin/sort/sort.c419
-rw-r--r--usr.bin/sort/sort.h201
-rw-r--r--usr.bin/sort/tmp.c106
-rw-r--r--usr.bin/split/split.1132
-rw-r--r--usr.bin/split/split.c362
-rw-r--r--usr.bin/tabs/tabs.1166
-rw-r--r--usr.bin/tabs/tabs.c232
-rw-r--r--usr.bin/tail/extern.h52
-rw-r--r--usr.bin/tail/forward.c355
-rw-r--r--usr.bin/tail/misc.c89
-rw-r--r--usr.bin/tail/read.c210
-rw-r--r--usr.bin/tail/reverse.c269
-rw-r--r--usr.bin/tail/tac.156
-rw-r--r--usr.bin/tail/tail.1202
-rw-r--r--usr.bin/tail/tail.c323
-rw-r--r--usr.bin/talk/ctl.c125
-rw-r--r--usr.bin/talk/ctl_transact.c108
-rw-r--r--usr.bin/talk/display.c195
-rw-r--r--usr.bin/talk/get_addrs.c81
-rw-r--r--usr.bin/talk/get_names.c124
-rw-r--r--usr.bin/talk/init_disp.c153
-rw-r--r--usr.bin/talk/invite.c190
-rw-r--r--usr.bin/talk/io.c139
-rw-r--r--usr.bin/talk/look_up.c119
-rw-r--r--usr.bin/talk/msgs.c82
-rw-r--r--usr.bin/talk/talk.1143
-rw-r--r--usr.bin/talk/talk.c79
-rw-r--r--usr.bin/talk/talk.h89
-rw-r--r--usr.bin/talk/talk_ctl.h41
-rw-r--r--usr.bin/tee/tee.184
-rw-r--r--usr.bin/tee/tee.c149
-rw-r--r--usr.bin/time/ext.h5
-rw-r--r--usr.bin/time/time.1220
-rw-r--r--usr.bin/time/time.c215
-rw-r--r--usr.bin/time/xtime.c9
-rw-r--r--usr.bin/touch/touch.1215
-rw-r--r--usr.bin/touch/touch.c337
-rw-r--r--usr.bin/tput/clear.sh38
-rw-r--r--usr.bin/tput/tput.1139
-rw-r--r--usr.bin/tput/tput.c195
-rw-r--r--usr.bin/tr/extern.h43
-rw-r--r--usr.bin/tr/str.c453
-rw-r--r--usr.bin/tr/tr.1352
-rw-r--r--usr.bin/tr/tr.c283
-rw-r--r--usr.bin/true/true.156
-rw-r--r--usr.bin/true/true.sh2
-rw-r--r--usr.bin/tsort/tsort.187
-rw-r--r--usr.bin/tsort/tsort.c433
-rw-r--r--usr.bin/tty/tty.175
-rw-r--r--usr.bin/tty/tty.c81
-rw-r--r--usr.bin/uname/uname.186
-rw-r--r--usr.bin/uname/uname.c159
-rw-r--r--usr.bin/unexpand/unexpand.c218
-rw-r--r--usr.bin/unifdef/unifdef.1347
-rw-r--r--usr.bin/unifdef/unifdef.c1037
-rw-r--r--usr.bin/unifdef/unifdefall.sh29
-rw-r--r--usr.bin/uniq/uniq.1130
-rw-r--r--usr.bin/uniq/uniq.c266
-rw-r--r--usr.bin/uudecode/uudecode.c323
-rw-r--r--usr.bin/uuencode/uuencode.1175
-rw-r--r--usr.bin/uuencode/uuencode.5145
-rw-r--r--usr.bin/uuencode/uuencode.c202
-rw-r--r--usr.bin/wc/wc.1146
-rw-r--r--usr.bin/wc/wc.c354
-rw-r--r--usr.bin/what/what.182
-rw-r--r--usr.bin/what/what.c127
-rw-r--r--usr.bin/who/utmpentry.c329
-rw-r--r--usr.bin/who/utmpentry.h76
-rw-r--r--usr.bin/who/who.1197
-rw-r--r--usr.bin/who/who.c391
-rw-r--r--usr.bin/write/term_chk.c137
-rw-r--r--usr.bin/write/term_chk.h36
-rw-r--r--usr.bin/write/write.1106
-rw-r--r--usr.bin/write/write.c292
-rw-r--r--usr.bin/xargs/pathnames.h36
-rw-r--r--usr.bin/xargs/strnsubst.c115
-rw-r--r--usr.bin/xargs/xargs.1387
-rw-r--r--usr.bin/xargs/xargs.c652
471 files changed, 111978 insertions, 0 deletions
diff --git a/usr.bin/asa/asa.1 b/usr.bin/asa/asa.1
new file mode 100644
index 0000000..ab76d28
--- /dev/null
+++ b/usr.bin/asa/asa.1
@@ -0,0 +1,85 @@
+.\" $NetBSD: asa.1,v 1.14 2017/07/03 21:34:18 wiz Exp $
+.\"
+.\" Copyright (c) 1993 Winning Strategies, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by Winning Strategies, Inc.
+.\" 4. The name of the author may not be used to endorse or promote products
+.\" derived from this software without specific prior written permission
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd June 2, 2016
+.Dt ASA 1
+.Os
+.Sh NAME
+.Nm asa
+.Nd interpret carriage-control characters
+.Sh SYNOPSIS
+.Nm
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility reads files sequentially, mapping
+.Tn FORTRAN
+carriage-control characters to line-printer control sequences,
+and writes them to the standard output.
+.Pp
+The first character of each line is interpreted as a carriage-control
+character. The following characters are interpreted as follows:
+.Bl -tag -width "<space>"
+.It <space>
+Output the rest of the line without change.
+.It 0
+Output a <newline> character before printing the rest of the line.
+.It 1
+Output a <formfeed> character before printing the rest of the line.
+.It +
+The trailing <newline> of the previous line is replaced by a <carriage-return>
+before printing the rest of the line.
+.El
+.Pp
+Lines beginning with characters other than the above are treated as if they
+begin with <space>.
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+To view a file containing the output of a
+.Tn FORTRAN
+program:
+.Dl asa file
+.Pp
+To format the output of a
+.Tn FORTRAN
+program and redirect it to a line-printer:
+.Dl a.out | asa | lpr
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92
+and to
+.St -xcu5 .
+.Sh AUTHORS
+.An J.T. Conklin ,
+Winning Strategies, Inc.
diff --git a/usr.bin/asa/asa.c b/usr.bin/asa/asa.c
new file mode 100644
index 0000000..6c1efd6
--- /dev/null
+++ b/usr.bin/asa/asa.c
@@ -0,0 +1,116 @@
+/* $NetBSD: asa.c,v 1.17 2016/09/05 00:40:28 sevan Exp $ */
+
+/*
+ * Copyright (c) 1993,94 Winning Strategies, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Winning Strategies, Inc.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: asa.c,v 1.17 2016/09/05 00:40:28 sevan Exp $");
+#endif
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static void asa(FILE *);
+
+int
+/*ARGSUSED*/
+main (int argc, char *argv[])
+{
+ FILE *fp;
+
+ /* skip progname */
+ argv++;
+
+ if (*argv == NULL)
+ asa(stdin);
+ else
+ do {
+ if ((fp = fopen(*argv, "r")) == NULL) {
+ warn("%s", *argv);
+ continue;
+ }
+ asa(fp);
+ (void)fclose(fp);
+ } while (*++argv != NULL);
+
+ return 0;
+}
+
+static void
+asa(FILE *f)
+{
+ char *buf;
+ size_t len;
+
+ if ((buf = fgetln(f, &len)) != NULL) {
+ if (len > 0 && buf[len - 1] == '\n')
+ buf[--len] = '\0';
+ /* special case the first line */
+ switch (buf[0]) {
+ case '0':
+ (void)putchar('\n');
+ break;
+ case '1':
+ (void)putchar('\f');
+ break;
+ }
+
+ if (len > 1 && buf[0] && buf[1])
+ (void)fwrite(buf + 1, 1, len - 1, stdout);
+
+ while ((buf = fgetln(f, &len)) != NULL) {
+ if (len > 0 && buf[len - 1] == '\n')
+ buf[--len] = '\0';
+ switch (buf[0]) {
+ default:
+ case ' ':
+ (void)putchar('\n');
+ break;
+ case '0':
+ (void)putchar('\n');
+ (void)putchar('\n');
+ break;
+ case '1':
+ (void)putchar('\f');
+ break;
+ case '+':
+ (void)putchar('\r');
+ break;
+ }
+
+ if (len > 1 && buf[0] && buf[1])
+ (void)fwrite(buf + 1, 1, len - 1, stdout);
+ }
+
+ (void)putchar('\n');
+ }
+}
diff --git a/usr.bin/at/at.1 b/usr.bin/at/at.1
new file mode 100644
index 0000000..cdd97e1
--- /dev/null
+++ b/usr.bin/at/at.1
@@ -0,0 +1,339 @@
+.\" $NetBSD: at.1,v 1.29 2016/11/18 12:16:48 abhinav Exp $
+.\" $OpenBSD: at.1,v 1.6 1998/06/05 00:47:46 deraadt Exp $
+.\" $FreeBSD: at.man,v 1.6 1997/02/22 19:54:05 peter Exp $
+.Dd March 10, 2008
+.Dt AT 1
+.Os
+.Sh NAME
+.Nm at ,
+.Nm batch ,
+.Nm atq ,
+.Nm atrm
+.Nd queue, examine or delete jobs for later execution
+.Sh SYNOPSIS
+.Nm at
+.Op Fl bdlmrVv
+.Op Fl f Ar file
+.Op Fl q Ar queue
+.Fl t Ar [[CC]YY]MMDDhhmm[.SS]
+.Nm
+.Op Fl bdlmrVv
+.Op Fl f Ar file
+.Op Fl q Ar queue
+.Ar time
+.Nm
+.Op Fl V
+.Fl c Ar job Op Ar job ...
+.Nm atq
+.Op Fl Vv
+.Op Fl q Ar queue
+.Nm atrm
+.Op Fl V
+.Ar job
+.Op Ar job ...
+.Nm batch
+.Op Fl mVv
+.Op Fl f Ar file
+.Op Fl q Ar queue
+.Op Fl t Ar [[CC]YY]MMDDhhmm[.SS]
+.Nm batch
+.Op Fl mVv
+.Op Fl f Ar file
+.Op Fl q Ar queue
+.Op Ar time
+.Sh DESCRIPTION
+.Nm
+and
+.Nm batch
+read commands from standard input or a specified file which
+are to be executed at a later time, using
+.Xr sh 1 .
+.Bl -tag -width indent
+.It Nm at
+Executes commands at a specified time.
+.It Nm atq
+Lists the user's pending jobs, unless the user is the superuser.
+In that case, everybody's jobs are listed.
+.It Nm atrm
+Deletes jobs.
+.It Nm batch
+Executes commands when system load levels permit.
+In other words, when
+the load average drops below 1.5, or the value specified in the invocation of
+.Xr atrun 8 .
+.El
+.Pp
+.Nm
+allows some moderately complex
+.Ar time
+specifications.
+It accepts times of the form
+.Ar HHMM
+or
+.Ar HH:MM
+to run a job at a specific time of day.
+(If that time is already past, the next day is assumed.)
+You may also specify
+.Sq midnight ,
+.Sq noon ,
+or
+.Sq teatime
+(4pm)
+and you can have a time-of-day suffixed with
+.Sq AM
+or
+.Sq PM
+for running in the morning or the evening.
+You can also say what day the job will be run,
+by giving a date in the form
+.Ar %month-name day
+with an optional
+.Ar year ,
+or giving a date of the form
+.Ar MMDDYY
+or
+.Ar MM/DD/YY
+or
+.Ar DD.MM.YY .
+The specification of a date must follow the specification of
+the time of day.
+You can also give times like
+.Op Nm now
+or
+.Op Nm now
+.Sq + Ar count %time-units ,
+where the time-units can be
+.Sq minutes ,
+.Sq hours ,
+.Sq days ,
+.Sq weeks ,
+.Sq months ,
+or
+.Sq years
+and you can tell
+.Nm
+to run the job today by suffixing the time with
+.Sq today
+and to run the job tomorrow by suffixing the time with
+.Sq tomorrow .
+.Pp
+For example, to run a job at 4pm three days from now, you would do
+.Dl at 4pm + 3 days ,
+to run a job at 10:00am on July 31, you would do
+.Dl at 10am Jul 31
+and to run a job at 1am tomorrow, you would do
+.Dl at 1am tomorrow .
+.Pp
+Alternatively the time may be specified in a language-neutral fashion
+by using the
+.Fl t
+options.
+.Pp
+For both
+.Nm
+and
+.Nm batch ,
+commands are read from standard input or the file specified
+with the
+.Fl f
+option and executed.
+The working directory, the environment (except for the variables
+.Ev TERM ,
+.Ev TERMCAP ,
+.Ev DISPLAY
+and
+.Ev _ )
+and the
+.Ar umask
+are retained from the time of invocation.
+An
+.Nm
+or
+.Nm batch
+command invoked from a
+.Xr su 1
+shell will retain the current userid.
+The user will be mailed standard error and standard output from his
+commands, if any.
+Mail will be sent using the command
+.Xr sendmail 1 .
+If
+.Nm
+is executed from a
+.Xr su 1
+shell, the owner of the login shell will receive the mail.
+.Pp
+The superuser may use these commands in any case.
+For other users, permission to use
+.Nm at
+is determined by the files
+.Pa /var/at/at.allow
+and
+.Pa /var/at/at.deny .
+.Pp
+If the file
+.Pa /var/at/at.allow
+exists, only usernames mentioned in it are allowed to use
+.Nm .
+.Pp
+If
+.Pa /var/at/at.allow
+does not exist,
+.Pa /var/at/at.deny
+is checked; every username not mentioned in it is then allowed
+to use
+.Nm .
+.Pp
+If neither exists, only the superuser is allowed use of
+.Nm .
+.Pp
+An empty
+.Pa /var/at/at.deny
+means that every user is allowed use these commands.
+This is the default configuration.
+.Sh OPTIONS
+.Bl -tag -offset indent -width XqXqueueXX
+.It Fl b
+Is an alias for
+.Nm batch .
+.It Fl c
+Cats the jobs listed on the command line to standard output.
+.It Fl d
+Is an alias for
+.Nm atrm .
+.It Fl f Ar file
+Reads the job from
+.Ar file
+rather than standard input.
+.It Fl l
+Is an alias for
+.Nm atq .
+.It Fl m
+Send mail to the user when the job has completed even if there was no
+output.
+.It Fl q Ar queue
+Uses the specified queue.
+A queue designation consists of a single letter.
+Valid queue designations
+range from
+.Sq a
+to
+.Sq z
+and
+.Sq A
+to
+.Sq Z .
+The
+.Sq c
+queue is the default for
+.Nm
+and the
+.Sq E
+queue for
+.Nm batch .
+Queues with higher letters run with increased niceness.
+If a job is submitted to a queue designated with an uppercase letter, it
+is treated as if it had been submitted to batch at that time.
+If
+.Nm atq
+is given a specific queue, it will only show jobs pending in that queue.
+.It Fl r
+Is an alias for
+.Nm atrm .
+.It Fl t
+For both
+.Nm
+and
+.Nm batch ,
+the time may be specified in a language-neutral format consisting of:
+.Bl -tag -width Ds -compact -offset indent
+.It Ar CC
+The first two digits of the year (the century).
+.It Ar YY
+The second two digits of the year.
+If
+.Ar YY
+is specified, but
+.Ar CC
+is not, a value for
+.Ar YY
+between 69 and 99 results in a
+.Ar CC
+value of 19.
+Otherwise, a
+.Ar CC
+value of 20 is used.
+.It Ar MM
+The month of the year, from 01 to 12.
+.It Ar \&DD
+The day of the month, from 01 to 31.
+.It Ar hh
+The hour of the day, from 00 to 23.
+.It Ar mm
+The minute of the hour, from 00 to 59.
+.It Ar \&SS
+The second of the minute, from 00 to 61.
+.El
+.It Fl V
+Prints the version number to standard error.
+.It Fl v
+For
+.Nm atq ,
+shows completed but not yet deleted jobs in the queue.
+Otherwise shows the time the job will be executed.
+.El
+.Sh FILES
+.Bl -tag -width /var/at/.lockfile -compact
+.It Pa /var/at/jobs
+Directory containing job files
+.It Pa /var/at/spool
+Directory containing output spool files
+.It Pa /var/run/utmp
+Login records
+.It Pa /var/at/at.allow
+Allow permission control
+.It Pa /var/at/at.deny
+Deny permission control
+.It Pa /var/at/.lockfile
+Job-creation lock file.
+.El
+.Sh SEE ALSO
+.Xr nice 1 ,
+.Xr sendmail 1 ,
+.Xr sh 1 ,
+.Xr umask 2 ,
+.Xr atrun 8 ,
+.Xr cron 8
+.Sh STANDARDS
+The
+.Nm
+and
+.Nm batch
+utilities conform to
+.St -p1003.2-92 .
+.Sh AUTHORS
+.An -nosplit
+.Nm
+was mostly written by
+.An Thomas Koenig Aq Mt ig25@rz.uni-karlsruhe.de .
+The time parsing routines are implemented by
+.An David Parsons Aq Mt orc@pell.chi.il.us .
+.Sh BUGS
+If the file
+.Pa /var/run/utmp
+is not available or corrupted, or if the user is not logged on at the
+time
+.Nm
+is invoked, the mail is sent to the userid found
+in the environment variable
+.Ev LOGNAME .
+If that is undefined or empty, the current userid is assumed.
+.Pp
+.Nm
+and
+.Nm batch
+as presently implemented are not suitable when users are competing for
+resources.
+If this is the case for your site, you might want to consider another
+batch system, such as
+.Ic nqs .
diff --git a/usr.bin/at/at.c b/usr.bin/at/at.c
new file mode 100644
index 0000000..621b95d
--- /dev/null
+++ b/usr.bin/at/at.c
@@ -0,0 +1,758 @@
+/* $NetBSD: at.c,v 1.31 2016/03/13 00:32:09 dholland Exp $ */
+
+/*
+ * at.c : Put file into atrun queue
+ * Copyright (C) 1993, 1994 Thomas Koenig
+ *
+ * Atrun & Atq modifications
+ * Copyright (C) 1993 David Parsons
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* System Headers */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <util.h>
+
+/* Local headers */
+#include "at.h"
+#include "panic.h"
+#include "parsetime.h"
+#include "perm.h"
+#include "pathnames.h"
+#include "stime.h"
+#include "privs.h"
+
+/* Macros */
+#define ALARMC 10 /* Number of seconds to wait for timeout */
+
+#define TIMESIZE 50
+
+enum { ATQ, ATRM, AT, BATCH, CAT }; /* what program we want to run */
+
+/* File scope variables */
+#ifndef lint
+#if 0
+static char rcsid[] = "$OpenBSD: at.c,v 1.15 1998/06/03 16:20:26 deraadt Exp $";
+#else
+__RCSID("$NetBSD: at.c,v 1.31 2016/03/13 00:32:09 dholland Exp $");
+#endif
+#endif
+
+const char *no_export[] = {"TERM", "TERMCAP", "DISPLAY", "_"};
+static int send_mail = 0;
+
+/* External variables */
+
+extern char **environ;
+bool fcreated = false;
+char atfile[FILENAME_MAX];
+
+char *atinput = NULL; /* where to get input from */
+unsigned char atqueue = 0; /* which queue to examine for jobs (atq) */
+char atverify = 0; /* verify time instead of queuing job */
+
+/* Function declarations */
+
+__dead static void sigc (int);
+__dead static void alarmc (int);
+static char *cwdname (void);
+static int nextjob (void);
+static void writefile (time_t, unsigned char);
+static void list_jobs (void);
+static void process_jobs (int, char **, int);
+
+/* Signal catching functions */
+
+/*ARGSUSED*/
+static void
+sigc(int signo)
+{
+
+ /* If a signal interrupts us, remove the spool file and exit. */
+ if (fcreated) {
+ privs_enter();
+ (void)unlink(atfile);
+ privs_exit();
+ }
+ (void)raise_default_signal(signo);
+ exit(EXIT_FAILURE);
+}
+
+/*ARGSUSED*/
+static void
+alarmc(int signo)
+{
+
+ /* Time out after some seconds. */
+ warnx("File locking timed out");
+ sigc(signo);
+}
+
+/* Local functions */
+
+static char *
+cwdname(void)
+{
+
+ /*
+ * Read in the current directory; the name will be overwritten on
+ * subsequent calls.
+ */
+ static char path[MAXPATHLEN];
+
+ return getcwd(path, sizeof(path));
+}
+
+static int
+nextjob(void)
+{
+ int jobno;
+ FILE *fid;
+
+ if ((fid = fopen(_PATH_SEQFILE, "r+")) != NULL) {
+ if (fscanf(fid, "%5x", &jobno) == 1) {
+ (void)rewind(fid);
+ jobno = (1+jobno) % 0xfffff; /* 2^20 jobs enough? */
+ (void)fprintf(fid, "%05x\n", jobno);
+ } else
+ jobno = EOF;
+ (void)fclose(fid);
+ return jobno;
+ } else if ((fid = fopen(_PATH_SEQFILE, "w")) != NULL) {
+ (void)fprintf(fid, "%05x\n", jobno = 1);
+ (void)fclose(fid);
+ return 1;
+ }
+ return EOF;
+}
+
+static void
+writefile(time_t runtimer, unsigned char queue)
+{
+ /*
+ * This does most of the work if at or batch are invoked for
+ * writing a job.
+ */
+ int jobno;
+ char *ap, *ppos;
+ const char *mailname;
+ struct passwd *pass_entry;
+ struct stat statbuf;
+ int fdes, lockdes, fd2;
+ FILE *fp, *fpin;
+ struct sigaction act;
+ char **atenv;
+ int ch;
+ mode_t cmask;
+ struct flock lock;
+
+ (void)setlocale(LC_TIME, "");
+
+ /*
+ * Install the signal handler for SIGINT; terminate after removing the
+ * spool file if necessary
+ */
+ (void)memset(&act, 0, sizeof(act));
+ act.sa_handler = sigc;
+ (void)sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+
+ (void)sigaction(SIGINT, &act, NULL);
+
+ (void)strlcpy(atfile, _PATH_ATJOBS, sizeof(atfile));
+ ppos = atfile + strlen(atfile);
+
+ /*
+ * Loop over all possible file names for running something at this
+ * particular time, see if a file is there; the first empty slot at
+ * any particular time is used. Lock the file _PATH_LOCKFILE first
+ * to make sure we're alone when doing this.
+ */
+
+ privs_enter();
+
+ if ((lockdes = open(_PATH_LOCKFILE, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR)) < 0)
+ perr("Cannot open lockfile " _PATH_LOCKFILE);
+
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 0;
+
+ act.sa_handler = alarmc;
+ (void)sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+
+ /*
+ * Set an alarm so a timeout occurs after ALARMC seconds, in case
+ * something is seriously broken.
+ */
+ (void)sigaction(SIGALRM, &act, NULL);
+ (void)alarm(ALARMC);
+ (void)fcntl(lockdes, F_SETLKW, &lock);
+ (void)alarm(0);
+
+ if ((jobno = nextjob()) == EOF)
+ perr("Cannot generate job number");
+
+ (void)snprintf(ppos, sizeof(atfile) - (ppos - atfile),
+ "%c%5x%8lx", queue, jobno, (unsigned long) (runtimer/60));
+
+ for (ap = ppos; *ap != '\0'; ap++)
+ if (*ap == ' ')
+ *ap = '0';
+
+ if (stat(atfile, &statbuf) == -1)
+ if (errno != ENOENT)
+ perr("Cannot access " _PATH_ATJOBS);
+
+ /*
+ * Create the file. The x bit is only going to be set after it has
+ * been completely written out, to make sure it is not executed in
+ * the meantime. To make sure they do not get deleted, turn off
+ * their r bit. Yes, this is a kluge.
+ */
+ cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR);
+ if ((fdes = open(atfile, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR)) == -1)
+ perr("Cannot create atjob file");
+
+ if ((fd2 = dup(fdes)) == -1)
+ perr("Error in dup() of job file");
+
+ if (fchown(fd2, real_uid, real_gid) == -1)
+ perr("Cannot give away file");
+
+ privs_exit();
+
+ /*
+ * We've successfully created the file; let's set the flag so it
+ * gets removed in case of an interrupt or error.
+ */
+ fcreated = true;
+
+ /* Now we can release the lock, so other people can access it */
+ lock.l_type = F_UNLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 0;
+ (void)fcntl(lockdes, F_SETLKW, &lock);
+ (void)close(lockdes);
+
+ if ((fp = fdopen(fdes, "w")) == NULL)
+ panic("Cannot reopen atjob file");
+
+ /*
+ * Get the userid to mail to, first by trying getlogin(), which reads
+ * /etc/utmp, then from $LOGNAME or $USER, finally from getpwuid().
+ */
+ mailname = getlogin();
+ if (mailname == NULL && (mailname = getenv("LOGNAME")) == NULL)
+ mailname = getenv("USER");
+
+ if (mailname == NULL || mailname[0] == '\0' ||
+ strlen(mailname) > LOGIN_NAME_MAX || getpwnam(mailname) == NULL) {
+ pass_entry = getpwuid(real_uid);
+ if (pass_entry != NULL)
+ mailname = pass_entry->pw_name;
+ }
+
+ if (atinput != NULL) {
+ fpin = freopen(atinput, "r", stdin);
+ if (fpin == NULL)
+ perr("Cannot open input file");
+ }
+ (void)fprintf(fp,
+ "#!/bin/sh\n"
+ "# atrun uid=%u gid=%u\n"
+ "# mail %s %d\n",
+ real_uid, real_gid, mailname, send_mail);
+
+ /* Write out the umask at the time of invocation */
+ (void)fprintf(fp, "umask %o\n", cmask);
+
+ /*
+ * Write out the environment. Anything that may look like a special
+ * character to the shell is quoted, except for \n, which is done
+ * with a pair of "'s. Dont't export the no_export list (such as
+ * TERM or DISPLAY) because we don't want these.
+ */
+ for (atenv = environ; *atenv != NULL; atenv++) {
+ int export = 1;
+ char *eqp;
+
+ eqp = strchr(*atenv, '=');
+ if (eqp == NULL)
+ eqp = *atenv;
+ else {
+ size_t i;
+
+ for (i = 0; i < __arraycount(no_export); i++) {
+ export = export &&
+ strncmp(*atenv, no_export[i],
+ (size_t)(eqp - *atenv)) != 0;
+ }
+ eqp++;
+ }
+
+ if (export) {
+ (void)fwrite(*atenv, sizeof(char),
+ (size_t)(eqp - *atenv), fp);
+ for (ap = eqp; *ap != '\0'; ap++) {
+ if (*ap == '\n')
+ (void)fprintf(fp, "\"\n\"");
+ else {
+ if (!isalnum((unsigned char)*ap)) {
+ switch (*ap) {
+ case '%': case '/': case '{':
+ case '[': case ']': case '=':
+ case '}': case '@': case '+':
+ case '#': case ',': case '.':
+ case ':': case '-': case '_':
+ break;
+ default:
+ (void)fputc('\\', fp);
+ break;
+ }
+ }
+ (void)fputc(*ap, fp);
+ }
+ }
+ (void)fputs("; export ", fp);
+ (void)fwrite(*atenv, sizeof(char),
+ (size_t)(eqp - *atenv - 1), fp);
+ (void)fputc('\n', fp);
+ }
+ }
+ /*
+ * Cd to the directory at the time and write out all the
+ * commands the user supplies from stdin.
+ */
+ (void)fputs("cd ", fp);
+ for (ap = cwdname(); *ap != '\0'; ap++) {
+ if (*ap == '\n')
+ (void)fprintf(fp, "\"\n\"");
+ else {
+ if (*ap != '/' && !isalnum((unsigned char)*ap))
+ (void)fputc('\\', fp);
+
+ (void)fputc(*ap, fp);
+ }
+ }
+ /*
+ * Test cd's exit status: die if the original directory has been
+ * removed, become unreadable or whatever.
+ */
+ (void)fprintf(fp,
+ " || {\n"
+ "\t echo 'Execution directory inaccessible' >&2\n"
+ "\t exit 1\n"
+ "}\n");
+
+ if ((ch = getchar()) == EOF)
+ panic("Input error");
+
+ do {
+ (void)fputc(ch, fp);
+ } while ((ch = getchar()) != EOF);
+
+ (void)fprintf(fp, "\n");
+ if (ferror(fp))
+ panic("Output error");
+
+ if (ferror(stdin))
+ panic("Input error");
+
+ (void)fclose(fp);
+
+ privs_enter();
+
+ /*
+ * Set the x bit so that we're ready to start executing
+ */
+ if (fchmod(fd2, S_IRUSR | S_IWUSR | S_IXUSR) == -1)
+ perr("Cannot give away file");
+
+ privs_exit();
+
+ (void)close(fd2);
+ (void)fprintf(stderr,
+ "Job %d will be executed using /bin/sh\n", jobno);
+}
+
+static void
+list_jobs(void)
+{
+ /*
+ * List all a user's jobs in the queue, by looping through
+ * _PATH_ATJOBS, or everybody's if we are root
+ */
+ struct passwd *pw;
+ DIR *spool;
+ struct dirent *dirent;
+ struct stat buf;
+ struct tm runtime;
+ unsigned long ctm;
+ unsigned char queue;
+ int jobno;
+ time_t runtimer;
+ char timestr[TIMESIZE];
+ int first = 1;
+
+ privs_enter();
+
+ if (chdir(_PATH_ATJOBS) == -1)
+ perr("Cannot change to " _PATH_ATJOBS);
+
+ if ((spool = opendir(".")) == NULL)
+ perr("Cannot open " _PATH_ATJOBS);
+
+ /* Loop over every file in the directory */
+ while ((dirent = readdir(spool)) != NULL) {
+ if (stat(dirent->d_name, &buf) == -1)
+ perr("Cannot stat in " _PATH_ATJOBS);
+
+ /*
+ * See it's a regular file and has its x bit turned on and
+ * is the user's
+ */
+ if (!S_ISREG(buf.st_mode)
+ || (buf.st_uid != real_uid && real_uid != 0)
+ || !(S_IXUSR & buf.st_mode || atverify))
+ continue;
+
+ if (sscanf(dirent->d_name, "%c%5x%8lx", &queue, &jobno, &ctm) != 3)
+ continue;
+
+ if (atqueue && queue != atqueue)
+ continue;
+
+ runtimer = 60 * (time_t)ctm;
+ runtime = *localtime(&runtimer);
+#if 1
+ /*
+ * Provide a consistent date/time format instead of a
+ * locale-specific one that might have 2 digit years
+ */
+ (void)strftime(timestr, TIMESIZE, "%T %F", &runtime);
+#else
+ (void)strftime(timestr, TIMESIZE, "%X %x", &runtime);
+#endif
+ if (first) {
+ (void)printf("%-*s %-*s %-*s %s\n",
+ (int)strlen(timestr), "Date",
+ LOGIN_NAME_MAX, "Owner",
+ 7, "Queue",
+ "Job");
+ first = 0;
+ }
+ pw = getpwuid(buf.st_uid);
+
+ (void)printf("%s %-*s %c%-*s %d\n",
+ timestr,
+ LOGIN_NAME_MAX, pw ? pw->pw_name : "???",
+ queue,
+ 6, (S_IXUSR & buf.st_mode) ? "" : "(done)",
+ jobno);
+ }
+ (void)closedir(spool);
+ privs_exit();
+}
+
+static void
+process_jobs(int argc, char **argv, int what)
+{
+ /* Delete every argument (job - ID) given */
+ int i;
+ struct stat buf;
+ DIR *spool;
+ struct dirent *dirent;
+ unsigned long ctm;
+ unsigned char queue;
+ int jobno;
+
+ privs_enter();
+
+ if (chdir(_PATH_ATJOBS) == -1)
+ perr("Cannot change to " _PATH_ATJOBS);
+
+ if ((spool = opendir(".")) == NULL)
+ perr("Cannot open " _PATH_ATJOBS);
+
+ privs_exit();
+
+ /* Loop over every file in the directory */
+ while((dirent = readdir(spool)) != NULL) {
+
+ privs_enter();
+ if (stat(dirent->d_name, &buf) == -1)
+ perr("Cannot stat in " _PATH_ATJOBS);
+ privs_exit();
+
+ if (sscanf(dirent->d_name, "%c%5x%8lx", &queue, &jobno, &ctm) !=3)
+ continue;
+
+ for (i = optind; i < argc; i++) {
+ if (atoi(argv[i]) == jobno) {
+ if (buf.st_uid != real_uid && real_uid != 0)
+ errx(EXIT_FAILURE,
+ "%s: Not owner", argv[i]);
+
+ switch (what) {
+ case ATRM:
+ privs_enter();
+
+ if (unlink(dirent->d_name) == -1)
+ perr(dirent->d_name);
+
+ privs_exit();
+ break;
+
+ case CAT: {
+ FILE *fp;
+ int ch;
+
+ privs_enter();
+
+ fp = fopen(dirent->d_name, "r");
+
+ privs_exit();
+
+ if (!fp)
+ perr("Cannot open file");
+ else {
+ while((ch = getc(fp)) != EOF)
+ (void)putchar(ch);
+ (void)fclose(fp);
+ }
+ }
+ break;
+
+ default:
+ errx(EXIT_FAILURE,
+ "Internal error, process_jobs = %d",
+ what);
+ break;
+ }
+ }
+ }
+ }
+ (void)closedir(spool);
+}
+
+/* Global functions */
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ unsigned char queue = DEFAULT_AT_QUEUE;
+ char queue_set = 0;
+ char time_set = 0;
+ char *pgm;
+
+ int program = AT; /* our default program */
+ const char *options = "q:f:t:mvldbrVc"; /* default options for at */
+ int disp_version = 0;
+ time_t timer;
+
+ privs_relinquish();
+
+ /* Eat any leading paths */
+ if ((pgm = strrchr(argv[0], '/')) == NULL)
+ pgm = argv[0];
+ else
+ pgm++;
+
+ /* find out what this program is supposed to do */
+ if (strcmp(pgm, "atq") == 0) {
+ program = ATQ;
+ options = "q:vV";
+ } else if (strcmp(pgm, "atrm") == 0) {
+ program = ATRM;
+ options = "V";
+ } else if (strcmp(pgm, "batch") == 0) {
+ program = BATCH;
+ options = "f:q:t:mvV";
+ }
+
+ /* process whatever options we can process */
+ opterr = 1;
+ while ((c = getopt(argc, argv, options)) != -1) {
+ switch (c) {
+ case 'v': /* verify time settings */
+ atverify = 1;
+ break;
+
+ case 'm': /* send mail when job is complete */
+ send_mail = 1;
+ break;
+
+ case 'f':
+ atinput = optarg;
+ break;
+
+ case 'q': /* specify queue */
+ if (strlen(optarg) > 1)
+ usage();
+
+ atqueue = queue = *optarg;
+ if (!(islower(queue) || isupper(queue)))
+ usage();
+
+ queue_set = 1;
+ break;
+ case 't': /* touch(1) date format */
+ timer = stime(optarg);
+ time_set = 1;
+ break;
+
+ case 'd':
+ case 'r':
+ if (program != AT)
+ usage();
+
+ program = ATRM;
+ options = "V";
+ break;
+
+ case 'l':
+ if (program != AT)
+ usage();
+
+ program = ATQ;
+ options = "q:vV";
+ break;
+
+ case 'b':
+ if (program != AT)
+ usage();
+
+ program = BATCH;
+ options = "f:q:mvV";
+ break;
+
+ case 'V':
+ disp_version = 1;
+ break;
+
+ case 'c':
+ program = CAT;
+ options = "";
+ break;
+
+ default:
+ usage();
+ break;
+ }
+ } /* end of options eating */
+
+ if (disp_version)
+ (void)fprintf(stderr, "%s version %.1f\n", pgm, AT_VERSION);
+
+ if (!check_permission())
+ errx(EXIT_FAILURE,
+ "You do not have permission to use %s.", pgm);
+
+ /* select our program */
+ switch (program) {
+ case ATQ:
+ if (optind != argc)
+ usage();
+ list_jobs();
+ break;
+
+ case ATRM:
+ case CAT:
+ if (optind == argc)
+ usage();
+ process_jobs(argc, argv, program);
+ break;
+
+ case AT:
+ if (argc > optind) {
+ /* -t and timespec argument are mutually exclusive */
+ if (time_set) {
+ usage();
+ exit(EXIT_FAILURE);
+ } else {
+ timer = parsetime(argc, argv);
+ time_set = 1;
+ }
+ }
+
+ if (atverify) {
+ struct tm *tm = localtime(&timer);
+ (void)fprintf(stderr, "%s\n", asctime(tm));
+ }
+ writefile(timer, queue);
+ break;
+
+ case BATCH:
+ if (queue_set)
+ queue = toupper(queue);
+ else
+ queue = DEFAULT_BATCH_QUEUE;
+
+ if (argc > optind) {
+ /* -t and timespec argument are mutually exclusive */
+ if (time_set) {
+ usage();
+ exit(EXIT_FAILURE);
+ } else {
+ timer = parsetime(argc, argv);
+ time_set = 1;
+ }
+ } else if (!time_set)
+ timer = time(NULL);
+
+ if (atverify) {
+ struct tm *tm = localtime(&timer);
+ (void)fprintf(stderr, "%s\n", asctime(tm));
+ }
+
+ writefile(timer, queue);
+ break;
+
+ default:
+ panic("Internal error");
+ break;
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/usr.bin/at/at.h b/usr.bin/at/at.h
new file mode 100644
index 0000000..64a4a53
--- /dev/null
+++ b/usr.bin/at/at.h
@@ -0,0 +1,43 @@
+/* $NetBSD: at.h,v 1.5 2008/04/05 16:26:57 christos Exp $ */
+
+/*
+ * at.h - header for at(1)
+ * Copyright (C) 1993 Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * From: $OpenBSD: at.h,v 1.3 1997/03/01 23:40:09 millert Exp $
+ */
+
+#ifndef _AT_H_
+#define _AT_H_
+
+extern bool fcreated;
+extern char atfile[];
+extern char atverify;
+
+#define AT_MAXJOBS 255 /* max jobs outstanding per user */
+#define AT_VERSION 2.9 /* our version number */
+
+#define DEFAULT_BATCH_QUEUE 'E'
+#define DEFAULT_AT_QUEUE 'c'
+
+#endif /* _AT_H_ */
diff --git a/usr.bin/at/panic.c b/usr.bin/at/panic.c
new file mode 100644
index 0000000..b0b09ca
--- /dev/null
+++ b/usr.bin/at/panic.c
@@ -0,0 +1,110 @@
+/* $NetBSD: panic.c,v 1.14 2016/03/13 00:32:09 dholland Exp $ */
+
+/*
+ * panic.c - terminate fast in case of error
+ * Copyright (c) 1993 by Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* System Headers */
+
+#include <err.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/* Local headers */
+
+#include "panic.h"
+#include "at.h"
+#include "privs.h"
+
+/* File scope variables */
+
+#ifndef lint
+#if 0
+static char rcsid[] = "$OpenBSD: panic.c,v 1.4 1997/03/01 23:40:09 millert Exp $";
+#else
+__RCSID("$NetBSD: panic.c,v 1.14 2016/03/13 00:32:09 dholland Exp $");
+#endif
+#endif
+
+/* Global functions */
+
+__dead
+void
+panic(const char *a)
+{
+
+ /*
+ * Something fatal has happened, print error message and exit.
+ */
+ if (fcreated) {
+ privs_enter();
+ (void)unlink(atfile);
+ privs_exit();
+ }
+ errx(EXIT_FAILURE, "%s", a);
+}
+
+__dead
+void
+perr(const char *a)
+{
+
+ /*
+ * Some operating system error; print error message and exit.
+ */
+ perror(a);
+ if (fcreated) {
+ privs_enter();
+ (void)unlink(atfile);
+ privs_exit();
+ }
+ exit(EXIT_FAILURE);
+}
+
+__dead
+void
+privs_fail(const char *msg)
+{
+ perr(msg);
+}
+
+__dead
+void
+usage(void)
+{
+
+ /* Print usage and exit. */
+ (void)fprintf(stderr,
+ "usage: at [-bdlmrVv] [-f file] [-q queue] -t [[CC]YY]MMDDhhmm[.SS]\n"
+ " at [-bdlmrVv] [-f file] [-q queue] time\n"
+ " at [-V] -c job [job ...]\n"
+ " atq [-Vv] [-q queue]\n"
+ " atrm [-V] job [job ...]\n"
+ " batch [-mVv] [-f file] [-q queue] [-t [[CC]YY]MMDDhhmm[.SS]]\n"
+ " batch [-mVv] [-f file] [-q queue] [time]\n");
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/at/panic.h b/usr.bin/at/panic.h
new file mode 100644
index 0000000..3f470fe
--- /dev/null
+++ b/usr.bin/at/panic.h
@@ -0,0 +1,37 @@
+/* $NetBSD: panic.h,v 1.5 2008/04/05 16:26:57 christos Exp $ */
+
+/*
+ * panic.h - header for at(1)
+ * Copyright (c) 1993 Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * From: $OpenBSD: panic.h,v 1.3 1997/03/01 23:40:10 millert Exp $
+ */
+
+#ifndef _PANIC_H_
+#define _PANIC_H_
+
+void panic(const char *) __dead;
+void perr(const char *) __dead;
+void usage(void) __dead;
+
+#endif /* _PANIC_H_ */
diff --git a/usr.bin/at/parsetime.c b/usr.bin/at/parsetime.c
new file mode 100644
index 0000000..5b1cc15
--- /dev/null
+++ b/usr.bin/at/parsetime.c
@@ -0,0 +1,652 @@
+/* $NetBSD: parsetime.c,v 1.19 2009/01/18 01:02:31 lukem Exp $ */
+
+/*
+ * parsetime.c - parse time for at(1)
+ * Copyright (C) 1993, 1994 Thomas Koenig
+ *
+ * modifications for english-language times
+ * Copyright (C) 1993 David Parsons
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS
+ * /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]] \
+ * |NOON | |[TOMORROW] |
+ * |MIDNIGHT | |[DAY OF WEEK] |
+ * \TEATIME / |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
+ * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
+ */
+
+/* System Headers */
+
+#include <sys/types.h>
+#include <err.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <tzfile.h>
+#include <unistd.h>
+
+/* Local headers */
+
+#include "at.h"
+#include "panic.h"
+#include "parsetime.h"
+#include "stime.h"
+
+/* Structures and unions */
+
+typedef enum { /* symbols */
+ MIDNIGHT, NOON, TEATIME,
+ PM, AM, TOMORROW, TODAY, NOW,
+ MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS,
+ NUMBER, PLUS, DOT, SLASH, ID, JUNK,
+ JAN, FEB, MAR, APR, MAY, JUN,
+ JUL, AUG, SEP, OCT, NOV, DEC,
+ SUN, MON, TUE, WED, THU, FRI, SAT,
+ TOKEOF /* EOF marker */
+} tokid_t;
+
+/*
+ * parse translation table - table driven parsers can be your FRIEND!
+ */
+static const struct {
+ const char *name; /* token name */
+ tokid_t value; /* token id */
+ bool plural; /* is this plural? */
+} Specials[] = {
+ {"midnight", MIDNIGHT, false}, /* 00:00:00 of today or tomorrow */
+ {"noon", NOON, false}, /* 12:00:00 of today or tomorrow */
+ {"teatime", TEATIME, false}, /* 16:00:00 of today or tomorrow */
+ {"am", AM, false}, /* morning times for 0-12 clock */
+ {"pm", PM, false}, /* evening times for 0-12 clock */
+ {"tomorrow", TOMORROW, false}, /* execute 24 hours from time */
+ {"today", TODAY, false}, /* execute today - don't advance time */
+ {"now", NOW, false}, /* opt prefix for PLUS */
+
+ {"minute", MINUTES, false}, /* minutes multiplier */
+ {"min", MINUTES, false},
+ {"m", MINUTES, false},
+ {"minutes", MINUTES, true}, /* (pluralized) */
+ {"hour", HOURS, false}, /* hours ... */
+ {"hr", HOURS, false}, /* abbreviated */
+ {"h", HOURS, false},
+ {"hours", HOURS, true}, /* (pluralized) */
+ {"day", DAYS, false}, /* days ... */
+ {"d", DAYS, false},
+ {"days", DAYS, true}, /* (pluralized) */
+ {"week", WEEKS, false}, /* week ... */
+ {"w", WEEKS, false},
+ {"weeks", WEEKS, true}, /* (pluralized) */
+ { "month", MONTHS, 0 }, /* month ... */
+ { "months", MONTHS, 1 }, /* (pluralized) */
+ { "year", YEARS, 0 }, /* year ... */
+ { "years", YEARS, 1 }, /* (pluralized) */
+ {"jan", JAN, false},
+ {"feb", FEB, false},
+ {"mar", MAR, false},
+ {"apr", APR, false},
+ {"may", MAY, false},
+ {"jun", JUN, false},
+ {"jul", JUL, false},
+ {"aug", AUG, false},
+ {"sep", SEP, false},
+ {"oct", OCT, false},
+ {"nov", NOV, false},
+ {"dec", DEC, false},
+ {"january", JAN, false},
+ {"february", FEB, false},
+ {"march", MAR, false},
+ {"april", APR, false},
+ {"may", MAY, false},
+ {"june", JUN, false},
+ {"july", JUL, false},
+ {"august", AUG, false},
+ {"september", SEP, false},
+ {"october", OCT, false},
+ {"november", NOV, false},
+ {"december", DEC, false},
+ {"sunday", SUN, false},
+ {"sun", SUN, false},
+ {"monday", MON, false},
+ {"mon", MON, false},
+ {"tuesday", TUE, false},
+ {"tue", TUE, false},
+ {"wednesday", WED, false},
+ {"wed", WED, false},
+ {"thursday", THU, false},
+ {"thu", THU, false},
+ {"friday", FRI, false},
+ {"fri", FRI, false},
+ {"saturday", SAT, false},
+ {"sat", SAT, false}
+};
+
+/* File scope variables */
+
+static char **scp; /* scanner - pointer at arglist */
+static char scc; /* scanner - count of remaining arguments */
+static char *sct; /* scanner - next char pointer in current argument */
+static bool need; /* scanner - need to advance to next argument */
+
+static char *sc_token; /* scanner - token buffer */
+static size_t sc_len; /* scanner - length of token buffer */
+static tokid_t sc_tokid;/* scanner - token id */
+static bool sc_tokplur; /* scanner - is token plural? */
+
+#ifndef lint
+#if 0
+static char rcsid[] = "$OpenBSD: parsetime.c,v 1.4 1997/03/01 23:40:10 millert Exp $";
+#else
+__RCSID("$NetBSD: parsetime.c,v 1.19 2009/01/18 01:02:31 lukem Exp $");
+#endif
+#endif
+
+/* Local functions */
+static void assign_date(struct tm *, int, int, int);
+static void expect(tokid_t);
+static void init_scanner(int, char **);
+static void month(struct tm *);
+static tokid_t parse_token(char *);
+static void plonk(tokid_t) __dead;
+static void plus(struct tm *);
+static void tod(struct tm *);
+static tokid_t token(void);
+
+/*
+ * parse a token, checking if it's something special to us
+ */
+static tokid_t
+parse_token(char *arg)
+{
+ size_t i;
+
+ for (i=0; i < __arraycount(Specials); i++) {
+ if (strcasecmp(Specials[i].name, arg) == 0) {
+ sc_tokplur = Specials[i].plural;
+ return sc_tokid = Specials[i].value;
+ }
+ }
+
+ /* not special - must be some random id */
+ return ID;
+}
+
+/*
+ * init_scanner() sets up the scanner to eat arguments
+ */
+static void
+init_scanner(int argc, char **argv)
+{
+
+ scp = argv;
+ scc = argc;
+ need = true;
+ sc_len = 1;
+ while (argc-- > 0)
+ sc_len += strlen(*argv++);
+
+ if ((sc_token = malloc(sc_len)) == NULL)
+ panic("Insufficient virtual memory");
+}
+
+/*
+ * token() fetches a token from the input stream
+ */
+static tokid_t
+token(void)
+{
+ int idx;
+
+ for(;;) {
+ (void)memset(sc_token, 0, sc_len);
+ sc_tokid = TOKEOF;
+ sc_tokplur = false;
+ idx = 0;
+
+ /*
+ * if we need to read another argument, walk along the
+ * argument list; when we fall off the arglist, we'll
+ * just return TOKEOF forever
+ */
+ if (need) {
+ if (scc < 1)
+ return sc_tokid;
+ sct = *scp;
+ scp++;
+ scc--;
+ need = false;
+ }
+ /*
+ * eat whitespace now - if we walk off the end of the argument,
+ * we'll continue, which puts us up at the top of the while loop
+ * to fetch the next argument in
+ */
+ while (isspace((unsigned char)*sct))
+ ++sct;
+ if (!*sct) {
+ need = true;
+ continue;
+ }
+
+ /*
+ * preserve the first character of the new token
+ */
+ sc_token[0] = *sct++;
+
+ /*
+ * then see what it is
+ */
+ if (isdigit((unsigned char)sc_token[0])) {
+ while (isdigit((unsigned char)*sct))
+ sc_token[++idx] = *sct++;
+ sc_token[++idx] = 0;
+ return sc_tokid = NUMBER;
+ } else if (isalpha((unsigned char)sc_token[0])) {
+ while (isalpha((unsigned char)*sct))
+ sc_token[++idx] = *sct++;
+ sc_token[++idx] = 0;
+ return parse_token(sc_token);
+ }
+ else if (sc_token[0] == ':' || sc_token[0] == '.')
+ return sc_tokid = DOT;
+ else if (sc_token[0] == '+')
+ return sc_tokid = PLUS;
+ else if (sc_token[0] == '/')
+ return sc_tokid = SLASH;
+ else
+ return sc_tokid = JUNK;
+ }
+}
+
+/*
+ * plonk() gives an appropriate error message if a token is incorrect
+ */
+__dead
+static void
+plonk(tokid_t tok)
+{
+
+ panic(tok == TOKEOF ? "incomplete time" : "garbled time");
+}
+
+/*
+ * expect() gets a token and dies most horribly if it's not the token we want
+ */
+static void
+expect(tokid_t desired)
+{
+
+ if (token() != desired)
+ plonk(sc_tokid); /* and we die here... */
+}
+
+/*
+ * plus() parses a now + time
+ *
+ * at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS|MONTHS|YEARS]
+ *
+ */
+static void
+plus(struct tm *tm)
+{
+ int delay;
+ int expectplur;
+
+ expect(NUMBER);
+
+ delay = atoi(sc_token);
+ expectplur = delay != 1;
+
+ switch (token()) {
+ case YEARS:
+ tm->tm_year += delay;
+ break;
+ case MONTHS:
+ tm->tm_mon += delay;
+ break;
+ case WEEKS:
+ delay *= 7;
+ /*FALLTHROUGH*/
+ case DAYS:
+ tm->tm_mday += delay;
+ break;
+ case HOURS:
+ tm->tm_hour += delay;
+ break;
+ case MINUTES:
+ tm->tm_min += delay;
+ break;
+ default:
+ plonk(sc_tokid);
+ break;
+ }
+
+ if (expectplur != sc_tokplur)
+ warnx("pluralization is wrong");
+
+ tm->tm_isdst = -1;
+ if (mktime(tm) == -1)
+ plonk(sc_tokid);
+}
+
+/*
+ * tod() computes the time of day
+ * [NUMBER [DOT NUMBER] [AM|PM]]
+ */
+static void
+tod(struct tm *tm)
+{
+ int hour, minute;
+ size_t tlen;
+
+ minute = 0;
+ hour = atoi(sc_token);
+ tlen = strlen(sc_token);
+
+ /*
+ * first pick out the time of day - if it's 4 digits, we assume
+ * a HHMM time, otherwise it's HH DOT MM time
+ */
+ if (token() == DOT) {
+ expect(NUMBER);
+ minute = atoi(sc_token);
+ (void)token();
+ } else if (tlen == 4) {
+ minute = hour % 100;
+ hour = hour / 100;
+ }
+
+ if (minute > 59)
+ panic("garbled time");
+
+ /*
+ * check if an AM or PM specifier was given
+ */
+ if (sc_tokid == AM || sc_tokid == PM) {
+ if (hour > 12)
+ panic("garbled time");
+
+ if (sc_tokid == PM) {
+ if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */
+ hour += 12;
+ } else {
+ if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */
+ hour = 0;
+ }
+ (void)token();
+ } else if (hour > 23)
+ panic("garbled time");
+
+ /*
+ * if we specify an absolute time, we don't want to bump the day even
+ * if we've gone past that time - but if we're specifying a time plus
+ * a relative offset, it's okay to bump things
+ */
+ if ((sc_tokid == TOKEOF || sc_tokid == PLUS) && tm->tm_hour > hour) {
+ tm->tm_mday++;
+ tm->tm_wday++;
+ }
+
+ tm->tm_hour = hour;
+ tm->tm_min = minute;
+}
+
+/*
+ * assign_date() assigns a date, wrapping to next year if needed.
+ * Accept years in 4-digit, 2-digit, or current year (-1).
+ */
+static void
+assign_date(struct tm *tm, int mday, int mon, int year)
+{
+
+ if (year > 99) { /* four digit year */
+ if (year >= TM_YEAR_BASE)
+ tm->tm_year = year - TM_YEAR_BASE;
+ else
+ panic("garbled time");
+ }
+ else if (year >= 0) { /* two digit year */
+ tm->tm_year = conv_2dig_year(year) - TM_YEAR_BASE;
+ }
+ else if (year == -1) { /* year not given (use default in tm) */
+ /* allow for 1 year range from current date */
+ if (tm->tm_mon > mon ||
+ (tm->tm_mon == mon && tm->tm_mday > mday))
+ tm->tm_year++;
+ }
+ else
+ panic("invalid year");
+
+ tm->tm_mday = mday;
+ tm->tm_mon = mon;
+}
+
+/*
+ * month() picks apart a month specification
+ *
+ * /[<month> NUMBER [NUMBER]] \
+ * |[TOMORROW] |
+ * |[DAY OF WEEK] |
+ * |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
+ * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
+ */
+static void
+month(struct tm *tm)
+{
+ int year;
+ int mday, wday, mon;
+ size_t tlen;
+
+ year = -1;
+ mday = 0;
+ switch (sc_tokid) {
+ case PLUS:
+ plus(tm);
+ break;
+
+ case TOMORROW:
+ /* do something tomorrow */
+ tm->tm_mday++;
+ tm->tm_wday++;
+ /*FALLTHROUGH*/
+ case TODAY:
+ /* force ourselves to stay in today - no further processing */
+ (void)token();
+ break;
+
+ case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
+ case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
+ /*
+ * do month mday [year]
+ */
+ mon = sc_tokid - JAN;
+ expect(NUMBER);
+ mday = atoi(sc_token);
+ if (token() == NUMBER) {
+ year = atoi(sc_token);
+ (void)token();
+ }
+ assign_date(tm, mday, mon, year);
+ break;
+
+ case SUN: case MON: case TUE:
+ case WED: case THU: case FRI:
+ case SAT:
+ /* do a particular day of the week */
+ wday = sc_tokid - SUN;
+
+ mday = tm->tm_mday;
+
+ /* if this day is < today, then roll to next week */
+ if (wday < tm->tm_wday)
+ mday += 7 - (tm->tm_wday - wday);
+ else
+ mday += (wday - tm->tm_wday);
+
+ tm->tm_wday = wday;
+
+ assign_date(tm, mday, tm->tm_mon, tm->tm_year + TM_YEAR_BASE);
+ break;
+
+ case NUMBER:
+ /*
+ * get numeric MMDDYY, mm/dd/yy, or dd.mm.yy
+ */
+ tlen = strlen(sc_token);
+ mon = atoi(sc_token);
+ (void)token();
+
+ if (sc_tokid == SLASH || sc_tokid == DOT) {
+ tokid_t sep;
+
+ sep = sc_tokid;
+ expect(NUMBER);
+ mday = atoi(sc_token);
+ if (token() == sep) {
+ expect(NUMBER);
+ year = atoi(sc_token);
+ (void)token();
+ }
+
+ /*
+ * flip months and days for european timing
+ */
+ if (sep == DOT) {
+ int x = mday;
+ mday = mon;
+ mon = x;
+ }
+ } else if (tlen == 6 || tlen == 8) {
+ if (tlen == 8) {
+ year = (mon % 10000) - 1900;
+ mon /= 10000;
+ } else {
+ year = mon % 100;
+ mon /= 100;
+ }
+ mday = mon % 100;
+ mon /= 100;
+ } else
+ panic("garbled time");
+
+ mon--;
+ if (mon < 0 || mon > 11 || mday < 1 || mday > 31)
+ panic("garbled time");
+
+ assign_date(tm, mday, mon, year);
+ break;
+ default:
+ /* plonk(sc_tokid); */ /* XXX - die here? */
+ break;
+ }
+}
+
+
+/* Global functions */
+
+time_t
+parsetime(int argc, char **argv)
+{
+ /*
+ * Do the argument parsing, die if necessary, and return the
+ * time the job should be run.
+ */
+ time_t nowtimer, runtimer;
+ struct tm nowtime, runtime;
+ int hr = 0; /* this MUST be initialized to zero for
+ midnight/noon/teatime */
+
+ nowtimer = time(NULL);
+ nowtime = *localtime(&nowtimer);
+
+ runtime = nowtime;
+ runtime.tm_sec = 0;
+
+ if (argc <= optind)
+ usage();
+
+ init_scanner(argc - optind, argv + optind);
+
+ switch (token()) {
+ case NOW:
+ if (scc < 1)
+ return nowtimer;
+
+ /* now is optional prefix for PLUS tree */
+ expect(PLUS);
+ /*FALLTHROUGH*/
+ case PLUS:
+ plus(&runtime);
+ break;
+
+ case NUMBER:
+ tod(&runtime);
+ month(&runtime);
+ break;
+
+ /*
+ * evil coding for TEATIME|NOON|MIDNIGHT - we've initialised
+ * hr to zero up above, then fall into this case in such a
+ * way so we add +12 +4 hours to it for teatime, +12 hours
+ * to it for noon, and nothing at all for midnight, then
+ * set our runtime to that hour before leaping into the
+ * month scanner
+ */
+ case TEATIME:
+ hr += 4;
+ /*FALLTHROUGH*/
+ case NOON:
+ hr += 12;
+ /*FALLTHROUGH*/
+ case MIDNIGHT:
+ if (runtime.tm_hour >= hr) {
+ runtime.tm_mday++;
+ runtime.tm_wday++;
+ }
+ runtime.tm_hour = hr;
+ runtime.tm_min = 0;
+ (void)token();
+ /*FALLTHROUGH*/ /* fall through to month setting */
+ default:
+ month(&runtime);
+ break;
+ }
+ expect(TOKEOF);
+
+ /*
+ * adjust for daylight savings time
+ */
+ runtime.tm_isdst = -1;
+ runtimer = mktime(&runtime);
+
+ if (runtimer == (time_t)-1)
+ panic("Invalid time");
+
+ if (nowtimer > runtimer)
+ panic("Trying to travel back in time");
+
+ return runtimer;
+}
diff --git a/usr.bin/at/parsetime.h b/usr.bin/at/parsetime.h
new file mode 100644
index 0000000..a2c9a69
--- /dev/null
+++ b/usr.bin/at/parsetime.h
@@ -0,0 +1,35 @@
+/* $NetBSD: parsetime.h,v 1.5 2008/04/05 16:26:57 christos Exp $ */
+
+/*
+ * parsetime.h - header for at(1)
+ * Copyright (c) 1993 Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * From: $OpenBSD: parsetime.h,v 1.3 1997/03/01 23:40:11 millert Exp $
+ */
+
+#ifndef _PARSETIME_H_
+#define _PARSETIME_H_
+
+time_t parsetime(int, char **);
+
+#endif /* _PARSETIME_H_ */
diff --git a/usr.bin/at/pathnames.h b/usr.bin/at/pathnames.h
new file mode 100644
index 0000000..88e6a53
--- /dev/null
+++ b/usr.bin/at/pathnames.h
@@ -0,0 +1,51 @@
+/* $NetBSD: pathnames.h,v 1.8 2008/04/05 16:26:57 christos Exp $ */
+
+/*
+ * Copyright (c) 1993 Christopher G. Demetriou
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed for the
+ * NetBSD Project. See http://www.NetBSD.org/ for
+ * information about NetBSD.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * <<Id: LICENSE,v 1.2 2000/06/14 15:57:33 cgd Exp>>
+ *
+ * From: $OpenBSD: pathnames.h,v 1.3 1997/03/01 23:40:11 millert Exp $
+ */
+
+#ifndef _PATHNAMES_H_
+#define _PATHNAMES_H_
+
+#include <paths.h>
+
+#define _PATH_ATJOBS "/var/at/jobs/"
+#define _PATH_ATSPOOL "/var/at/spool/"
+#define _PATH_LOCKFILE "/var/at/.lockfile"
+#define _PATH_SEQFILE "/var/at/.SEQ"
+#define _PATH_AT_ALLOW "/var/at/at.allow"
+#define _PATH_AT_DENY "/var/at/at.deny"
+
+#endif /* _PATHNAMES_H_ */
diff --git a/usr.bin/at/perm.c b/usr.bin/at/perm.c
new file mode 100644
index 0000000..64e4fbb
--- /dev/null
+++ b/usr.bin/at/perm.c
@@ -0,0 +1,118 @@
+/* $NetBSD: perm.c,v 1.4 2016/03/13 00:32:09 dholland Exp $ */
+
+/*
+ * perm.c - check user permission for at(1)
+ * Copyright (C) 1994 Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* System Headers */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Local headers */
+
+#include "at.h"
+#include "panic.h"
+#include "pathnames.h"
+#include "privs.h"
+#include "perm.h"
+
+/* File scope variables */
+
+#ifndef lint
+#if 0
+static char rcsid[] = "$OpenBSD: perm.c,v 1.1 1997/03/01 23:40:12 millert Exp $";
+#else
+__RCSID("$NetBSD: perm.c,v 1.4 2016/03/13 00:32:09 dholland Exp $");
+#endif
+#endif
+
+/* Local functions */
+
+static bool
+check_for_user(FILE *fp, const char *name)
+{
+ char *buffer;
+ size_t len;
+ bool found = false;
+
+ len = strlen(name);
+ if ((buffer = malloc(len + 2)) == NULL)
+ panic("Insufficient virtual memory");
+
+ while (fgets(buffer, (int)len + 2, fp) != NULL) {
+ if (strncmp(name, buffer, len) == 0 && buffer[len] == '\n') {
+ found = true;
+ break;
+ }
+ }
+ (void)fclose(fp);
+ free(buffer);
+ return found;
+}
+
+/* Global functions */
+
+bool
+check_permission(void)
+{
+ FILE *fp;
+ uid_t uid = geteuid();
+ struct passwd *pentry;
+
+ if (uid == 0)
+ return true;
+
+ if ((pentry = getpwuid(uid)) == NULL) {
+ perror("Cannot access user database");
+ exit(EXIT_FAILURE);
+ }
+
+ privs_enter();
+
+ fp = fopen(_PATH_AT_ALLOW, "r");
+
+ privs_exit();
+
+ if (fp != NULL) {
+ return check_for_user(fp, pentry->pw_name);
+ } else {
+ privs_enter();
+
+ fp = fopen(_PATH_AT_DENY, "r");
+
+ privs_exit();
+
+ if (fp != NULL)
+ return !check_for_user(fp, pentry->pw_name);
+ }
+ return false;
+}
diff --git a/usr.bin/at/perm.h b/usr.bin/at/perm.h
new file mode 100644
index 0000000..89d0178
--- /dev/null
+++ b/usr.bin/at/perm.h
@@ -0,0 +1,33 @@
+/* $NetBSD: perm.h,v 1.3 2008/04/05 16:26:57 christos Exp $ */
+
+/*
+ * perm.h - header for at(1)
+ * Copyright (C) 1994 Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PERM_H_
+#define _PERM_H_
+
+bool check_permission(void);
+
+#endif /* _PERM_H_ */
diff --git a/usr.bin/at/privs.c b/usr.bin/at/privs.c
new file mode 100644
index 0000000..1b7e12d
--- /dev/null
+++ b/usr.bin/at/privs.c
@@ -0,0 +1,104 @@
+/* $NetBSD: privs.c,v 1.3 2018/02/25 23:48:16 htodd Exp $ */
+
+/*
+ * privs.c - privileged operations
+ * Copyright (C) 1993 Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * From: OpenBSD: privs.h,v 1.4 1997/03/01 23:40:12 millert Exp
+ */
+
+#include <unistd.h>
+
+#include "privs.h"
+
+/*
+ * Used by: usr.bin/at
+ * Used by: libexec/atrun
+ */
+
+/*
+ * Relinquish privileges temporarily for a setuid or setgid program
+ * with the option of getting them back later. This is done by
+ * using POSIX saved user and groups ids. Call RELINQUISH_PRIVS once
+ * at the beginning of the main program. This will cause all operations
+ * to be executed with the real userid. When you need the privileges
+ * of the setuid/setgid invocation, call PRIV_START; when you no longer
+ * need it, call PRIV_END. Note that it is an error to call PRIV_START
+ * and not PRIV_END within the same function.
+ *
+ * Use RELINQUISH_PRIVS_ROOT(a,b) if your program started out running
+ * as root, and you want to drop back the effective userid to a
+ * and the effective group id to b, with the option to get them back
+ * later.
+ *
+ * Problems: Do not use return between PRIV_START and PRIV_END; this
+ * will cause the program to continue running in an unprivileged
+ * state.
+ *
+ * It is NOT safe to call exec(), system() or popen() with a user-
+ * supplied program (i.e. without carefully checking PATH and any
+ * library load paths) with relinquished privileges; the called program
+ * can acquire them just as easily. Set both effective and real userid
+ * to the real userid before calling any of them.
+ */
+
+uid_t real_uid, effective_uid;
+gid_t real_gid, effective_gid;
+
+void
+privs_enter(void)
+{
+ if (seteuid(effective_uid) == -1)
+ privs_fail("Cannot get user privs");
+ if (setegid(effective_gid) == -1)
+ privs_fail("Cannot get group privs");
+}
+
+void
+privs_exit(void)
+{
+ if (setegid(real_gid) == -1)
+ privs_fail("Cannot relinquish group privs");
+ if (seteuid(real_uid) == -1)
+ privs_fail("Cannot relinquish user privs");
+}
+
+void
+privs_relinquish(void)
+{
+ real_uid = getuid();
+ effective_uid = geteuid();
+ real_gid = getgid();
+ effective_gid = getegid();
+ privs_exit();
+}
+
+void
+privs_relinquish_root(uid_t ruid, gid_t rgid)
+{
+ real_uid = ruid;
+ real_gid = rgid;
+ effective_uid = geteuid();
+ effective_gid = getegid();
+ privs_exit();
+}
diff --git a/usr.bin/at/privs.h b/usr.bin/at/privs.h
new file mode 100644
index 0000000..4344cbe
--- /dev/null
+++ b/usr.bin/at/privs.h
@@ -0,0 +1,76 @@
+/* $NetBSD: privs.h,v 1.10 2016/03/13 00:33:12 dholland Exp $ */
+
+/*
+ * privs.h - header for privileged operations
+ * Copyright (C) 1993 Thomas Koenig
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author(s) may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * From: OpenBSD: privs.h,v 1.4 1997/03/01 23:40:12 millert Exp
+ */
+
+#ifndef _PRIVS_H_
+#define _PRIVS_H_
+
+/*
+ * Used by: usr.bin/at
+ * Used by: libexec/atrun
+ */
+
+/*
+ * Relinquish privileges temporarily for a setuid or setgid program
+ * with the option of getting them back later. This is done by
+ * using POSIX saved user and groups ids. Call RELINQUISH_PRIVS once
+ * at the beginning of the main program. This will cause all operations
+ * to be executed with the real userid. When you need the privileges
+ * of the setuid/setgid invocation, call PRIV_START; when you no longer
+ * need it, call PRIV_END. Note that it is an error to call PRIV_START
+ * and not PRIV_END within the same function.
+ *
+ * Use RELINQUISH_PRIVS_ROOT(a,b) if your program started out running
+ * as root, and you want to drop back the effective userid to a
+ * and the effective group id to b, with the option to get them back
+ * later.
+ *
+ * Problems: Do not use return between PRIV_START and PRIV_END; this
+ * will cause the program to continue running in an unprivileged
+ * state.
+ *
+ * It is NOT safe to call exec(), system() or popen() with a user-
+ * supplied program (i.e. without carefully checking PATH and any
+ * library load paths) with relinquished privileges; the called program
+ * can acquire them just as easily. Set both effective and real userid
+ * to the real userid before calling any of them.
+ */
+
+extern uid_t real_uid, effective_uid;
+extern gid_t real_gid, effective_gid;
+
+void privs_relinquish(void);
+void privs_relinquish_root(uid_t ruid, gid_t rgid);
+
+void privs_enter(void);
+void privs_exit(void);
+
+/* caller provides this */
+__dead void privs_fail(const char *msg);
+
+#endif /* _PRIV_H_ */
diff --git a/usr.bin/at/stime.c b/usr.bin/at/stime.c
new file mode 100644
index 0000000..567b848
--- /dev/null
+++ b/usr.bin/at/stime.c
@@ -0,0 +1,116 @@
+/* $NetBSD: stime.c,v 1.7 2008/07/21 14:19:21 lukem Exp $ */
+
+/*
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)touch.c 8.2 (Berkeley) 4/28/95";
+#endif
+__RCSID("$NetBSD: stime.c,v 1.7 2008/07/21 14:19:21 lukem Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <tzfile.h>
+
+#include "stime.h"
+
+#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
+
+time_t
+stime(char *arg)
+{
+ struct tm *t;
+ time_t tmptime;
+ int yearset;
+ char *p;
+ /* Start with the current time. */
+ if (time(&tmptime) == (time_t)-1)
+ err(EXIT_FAILURE, "time");
+
+ if ((t = localtime(&tmptime)) == NULL)
+ err(EXIT_FAILURE, "localtime");
+ /* [[CC]YY]MMDDhhmm[.SS] */
+ if ((p = strchr(arg, '.')) == NULL)
+ t->tm_sec = 0; /* Seconds defaults to 0. */
+ else {
+ if (strlen(p + 1) != 2)
+ goto terr;
+ *p++ = '\0';
+ t->tm_sec = ATOI2(p);
+ }
+
+ yearset = 0;
+ switch (strlen(arg)) {
+ case 12: /* CCYYMMDDhhmm */
+ t->tm_year = ATOI2(arg) * 100 - TM_YEAR_BASE;
+ yearset = 1;
+ /* FALLTHROUGH */
+ case 10: /* YYMMDDhhmm */
+ if (yearset) {
+ t->tm_year += ATOI2(arg);
+ } else {
+ yearset = ATOI2(arg);
+ t->tm_year = conv_2dig_year(yearset) - TM_YEAR_BASE;
+ }
+ /* FALLTHROUGH */
+ case 8: /* MMDDhhmm */
+ t->tm_mon = ATOI2(arg);
+ --t->tm_mon; /* Convert from 01-12 to 00-11 */
+ /* FALLTHROUGH */
+ case 6:
+ t->tm_mday = ATOI2(arg);
+ /* FALLTHROUGH */
+ case 4:
+ t->tm_hour = ATOI2(arg);
+ /* FALLTHROUGH */
+ case 2:
+ t->tm_min = ATOI2(arg);
+ break;
+ default:
+ goto terr;
+ }
+
+ t->tm_isdst = -1; /* Figure out DST. */
+ tmptime = mktime(t);
+ if (tmptime == (time_t)-1) {
+ terr: errx(EXIT_FAILURE,
+ "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
+ }
+ return tmptime;
+}
diff --git a/usr.bin/at/stime.h b/usr.bin/at/stime.h
new file mode 100644
index 0000000..266953e
--- /dev/null
+++ b/usr.bin/at/stime.h
@@ -0,0 +1,53 @@
+/* $NetBSD: stime.h,v 1.4 2008/04/05 16:26:57 christos Exp $ */
+
+/*
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _STIME_H_
+#define _STIME_H_
+
+/*
+ * Convert a 2-digit year to a valid year as:
+ * 00-68 <-> 2000-2068
+ * 69-99 <-> 1969-1999
+ *
+ * Note: We choose the earliest year as 1969 rather than 1970 so that
+ * dates near the Epoch (Jan 1, 1970 00:00:00 UTC) can be represented
+ * with 2-digit years in all time zones. This is the convention used
+ * in parsedate(3), though it is not consistently applied in the
+ * source tree.
+ *
+ * XXX - move this (or something like it) to time.h and use it
+ * consistently in the tree.
+ */
+#define conv_2dig_year(y) ((y) < 69 ? (y) + 2000 : (y) + 1900)
+
+time_t stime(char *);
+
+#endif /* _STIME_H_ */
diff --git a/usr.bin/basename/basename.1 b/usr.bin/basename/basename.1
new file mode 100644
index 0000000..166b506
--- /dev/null
+++ b/usr.bin/basename/basename.1
@@ -0,0 +1,92 @@
+.\" $NetBSD: basename.1,v 1.18 2017/07/03 21:34:18 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)basename.1 8.2 (Berkeley) 4/18/94
+.\"
+.Dd April 18, 1994
+.Dt BASENAME 1
+.Os
+.Sh NAME
+.Nm basename ,
+.Nm dirname
+.Nd return filename or directory portion of pathname
+.Sh SYNOPSIS
+.Nm
+.Ar string
+.Op Ar suffix
+.Nm dirname
+.Ar string
+.Sh DESCRIPTION
+.Nm
+deletes any prefix ending with the last slash
+.Ql \&/
+character present in
+.Ar string ,
+and a
+.Ar suffix ,
+if given.
+The resulting filename is written to the standard output.
+A non-existent suffix is ignored.
+.Pp
+.Nm dirname
+deletes the filename portion, beginning
+with the last slash
+.Ql \&/
+character to the end of
+.Ar string ,
+and writes the result to the standard output.
+.Sh EXIT STATUS
+Both the
+.Nm
+and
+.Nm dirname
+utilities
+exit 0 on success, and >0 if an error occurs.
+.Sh EXAMPLES
+The following line sets the shell variable
+.Ev FOO
+to
+.Pa /usr/bin .
+.Pp
+.Dl FOO=`dirname /usr/bin/trail`
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr sh 1 ,
+.Xr basename 3 ,
+.Xr dirname 3
+.Sh STANDARDS
+The
+.Nm
+and
+.Nm dirname
+utilities conform to
+.St -p1003.2-92 .
diff --git a/usr.bin/basename/basename.c b/usr.bin/basename/basename.c
new file mode 100644
index 0000000..78b5d8b
--- /dev/null
+++ b/usr.bin/basename/basename.c
@@ -0,0 +1,103 @@
+/* $NetBSD: basename.c,v 1.16 2019/02/01 08:29:04 mrg Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)basename.c 8.4 (Berkeley) 5/4/95";
+#endif
+__RCSID("$NetBSD: basename.c,v 1.16 2019/02/01 08:29:04 mrg Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <libgen.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+__dead static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ char *p;
+ int ch;
+
+ setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "")) != -1)
+ switch (ch) {
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1 && argc != 2)
+ usage();
+
+ if (**argv == '\0') {
+ (void)printf("\n");
+ exit(0);
+ }
+ if ((p = basename(*argv)) == NULL)
+ err(1, "%s", *argv);
+ if (*++argv != NULL) {
+ int suffixlen, stringlen, off;
+
+ suffixlen = strlen(*argv);
+ stringlen = strlen(p);
+
+ if (suffixlen < stringlen) {
+ off = stringlen - suffixlen;
+ if (strcmp(p + off, *argv) == 0)
+ p[off] = '\0';
+ }
+ }
+ (void)printf("%s\n", p);
+ exit(0);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: basename string [suffix]\n");
+ exit(1);
+}
diff --git a/usr.bin/c99/c99.1 b/usr.bin/c99/c99.1
new file mode 100644
index 0000000..0ac95ae
--- /dev/null
+++ b/usr.bin/c99/c99.1
@@ -0,0 +1,81 @@
+.\" $NetBSD: c99.1,v 1.1 2016/01/14 04:27:26 christos Exp $
+.\"
+.\" Copyright (c) 1999-2008 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd September 24, 2008
+.Dt C99 1
+.Os
+.Sh NAME
+.Nm c99
+.Nd ANSI (1999) C compiler
+.Sh SYNOPSIS
+.Nm
+.Op Fl pedantic
+.Op Fl pedantic-errors
+.Op Fl D_ANSI_SOURCE
+.Op options ...
+.Sh DESCRIPTION
+Calls the C compiler (cc) with the given
+.Ar options ,
+using a C language environment compatible with the
+.St -isoC-99
+specification.
+.Pp
+This includes
+inline functions, variable-length arrays, support for one-line
+comments beginning with //,
+disabling non-ANSI compiler features (such as
+.Ar asm ,
+.Ar typeof ,
+and the $ character in identifiers),
+and definition of the preprocessor symbol
+.Ev __STRICT_ANSI__ .
+.Pp
+The following options are available:
+.Bl -tag -width "-pedantic-errorsxx"
+.It Fl pedantic
+Issue extra warnings defined by ANSI for use of non-ANSI features.
+.It Fl pedantic-errors
+Issue errors instead of warnings that normally would be presented by
+.Fl pedantic .
+.It Fl D_ANSI_SOURCE
+Tell the system header file set to use an ANSI-conformant "clean" namespace.
+.El
+.Sh SEE ALSO
+.Xr cc 1
+.Sh STANDARDS
+.Nm
+conforms to
+.St -p1003.2-92 .
+.Sh HISTORY
+.Nm
+first appeared in
+.Nx 5.0 .
+.Sh BUGS
+Since
+.Nm
+is a shell wrapper script to
+.Ar cc ,
+compile errors are prefixed by "cc:".
diff --git a/usr.bin/c99/c99.sh b/usr.bin/c99/c99.sh
new file mode 100644
index 0000000..15cab47
--- /dev/null
+++ b/usr.bin/c99/c99.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/bin/cc -std=c99 "$@"
diff --git a/usr.bin/cal/README b/usr.bin/cal/README
new file mode 100644
index 0000000..638ac9d
--- /dev/null
+++ b/usr.bin/cal/README
@@ -0,0 +1,42 @@
+The cal(1) date routines were written from scratch, basically from first
+principles. The algorithm for calculating the day of week from any
+Gregorian date was "reverse engineered". This was necessary as most of
+the documented algorithms have to do with date calculations for other
+calendars (e.g. julian) and are only accurate when converted to gregorian
+within a narrow range of dates.
+
+1 Jan 1 is a Saturday because that's what cal says and I couldn't change
+that even if I was dumb enough to try. From this we can easily calculate
+the day of week for any date. The algorithm for a zero based day of week:
+
+ calculate the number of days in all prior years (year-1)*365
+ add the number of leap years (days?) since year 1
+ (not including this year as that is covered later)
+ add the day number within the year
+ this compensates for the non-inclusive leap year
+ calculation
+ if the day in question occurs before the gregorian reformation
+ (3 sep 1752 for our purposes), then simply return
+ (value so far - 1 + SATURDAY's value of 6) modulo 7.
+ if the day in question occurs during the reformation (3 sep 1752
+ to 13 sep 1752 inclusive) return THURSDAY. This is my
+ idea of what happened then. It does not matter much as
+ this program never tries to find day of week for any day
+ that is not the first of a month.
+ otherwise, after the reformation, use the same formula as the
+ days before with the additional step of subtracting the
+ number of days (11) that were adjusted out of the calendar
+ just before taking the modulo.
+
+It must be noted that the number of leap years calculation is sensitive
+to the date for which the leap year is being calculated. A year that occurs
+before the reformation is determined to be a leap year if its modulo of
+4 equals zero. But after the reformation, a year is only a leap year if
+its modulo of 4 equals zero and its modulo of 100 does not. Of course,
+there is an exception for these century years. If the modulo of 400 equals
+zero, then the year is a leap year anyway. This is, in fact, what the
+gregorian reformation was all about (a bit of error in the old algorithm
+that caused the calendar to be inaccurate.)
+
+Once we have the day in year for the first of the month in question, the
+rest is trivial.
diff --git a/usr.bin/cal/cal.1 b/usr.bin/cal/cal.1
new file mode 100644
index 0000000..c99cd5d
--- /dev/null
+++ b/usr.bin/cal/cal.1
@@ -0,0 +1,164 @@
+.\" $NetBSD: cal.1,v 1.23 2018/05/29 08:37:33 wiz Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Kim Letkeman.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)cal.1 8.2 (Berkeley) 4/28/95
+.\"
+.Dd May 29, 2018
+.Dt CAL 1
+.Os
+.Sh NAME
+.Nm cal
+.Nd displays a calendar
+.Sh SYNOPSIS
+.Nm
+.Op Fl 3hjry
+.Op Fl A Ar after
+.Op Fl B Ar before
+.Op Fl C Ar context
+.Op Fl d Ar day-of-week
+.Op Fl R Ar reform-spec
+.Op Oo Ar month Oc Ar year
+.Sh DESCRIPTION
+.Nm
+displays a simple calendar.
+If arguments are not specified,
+the current month is displayed.
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl 3
+Same as
+.Dq Fl A Ar 1 Fl B Ar 1 .
+.It Fl A Ar after
+Display
+.Ar after
+months after the specified month.
+.It Fl B Ar before
+Display
+.Ar before
+months before the specified month.
+.It Fl C Ar context
+Display
+.Ar context
+months before and after the specified month.
+.It Fl d Ar day-of-week
+Specifies the day of the week on which the calendar should start.
+Valid values are 0 through 6, presenting Sunday through Saturday,
+inclusively.
+The default output starts on Sundays.
+.It Fl h
+Highlight the current day, if present in the displayed calendar.
+If output is to a terminal, then the appropriate terminal sequences
+are used, otherwise overstriking is used.
+If more than one
+.Fl h
+is used and output is to a terminal, the current date will be
+highlighted in inverse video instead of bold.
+.It Fl j
+Display Julian dates (days one-based, numbered from January 1).
+.It Fl R Ar reform-spec
+Selects an alternate Gregorian reform point from the default of
+September 3rd, 1752.
+The
+.Ar reform-spec
+can be selected by one of the built-in names (see
+.Sx NOTES
+for a list) or by a date of the form YYYY/MM/DD.
+The date and month may be omitted, provided that what is specified
+uniquely selects a given built-in reform point.
+If an exact date is specified, then that date is taken to be the first
+missing date of the Gregorian Reform to be applied.
+.It Fl r
+Display the month in which the Gregorian Reform adjustment was
+applied, if no other
+.Ar month
+or
+.Ar year
+information is given.
+If used in conjunction with
+.Fl y ,
+then the entire year is displayed.
+.It Fl y
+Display a calendar for the current year.
+.El
+.Pp
+If no parameters are specified, the current month's calendar is
+displayed.
+A single parameter specifies the year and optionally the month
+in ISO format:
+.Dq Li cal 2007-12
+Two parameters denote the month (1 - 12) and year.
+Note that the century must be included in the year.
+.Pp
+A year starts on Jan 1.
+.Sh NOTES
+In the USA and Great Britain the Gregorian Reformation occurred in 1752.
+By this time, most countries had recognized the reformation (although a
+few did not recognize it until the 1900's.)
+Eleven days following September 2, 1752 were eliminated by the reformation,
+so the calendar for that month is a bit unusual.
+.Pp
+In view of the chaotic way the Gregorian calendar was adopted throughout
+the world in the years between 1582 and 1928 make sure to take into account
+the date of the Gregorian Reformation in your region if you are checking a
+calendar for a very old date.
+.Pp
+.Nm
+has a decent built-in list of Gregorian Reform dates and the names of
+the countries where the reform was adopted:
+.Bd -literal
+ Italy Oct. 5, 1582 Denmark Feb. 19, 1700
+ Spain Oct. 5, 1582 Great Britain Sep. 3, 1752
+ Portugal Oct. 5, 1582 Sweden Feb. 18, 1753
+ Poland Oct. 5, 1582 Finland Feb. 18, 1753
+ France Dec. 12, 1582 Japan Dec. 20, 1872
+ Luxembourg Dec. 22, 1582 China Nov. 7, 1911
+ Netherlands Dec. 22, 1582 Bulgaria Apr. 1, 1916
+ Bavaria Oct. 6, 1583 U.S.S.R. Feb. 1, 1918
+ Austria Jan. 7, 1584 Serbia Jan. 19, 1919
+ Switzerland Jan. 12, 1584 Romania Jan. 19, 1919
+ Hungary Oct. 22, 1587 Greece Mar. 10, 1924
+ Germany Feb. 19, 1700 Turkey Dec. 19, 1925
+ Norway Feb. 19, 1700 Egypt Sep. 18, 1928
+.Ed
+.Pp
+The country known as
+.Em Great Britain
+can also be referred to as
+.Em England
+since that has less letters and no spaces in it.
+This is meant only as a measure of expediency, not as a possible
+slight to anyone involved.
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v1 .
diff --git a/usr.bin/cal/cal.c b/usr.bin/cal/cal.c
new file mode 100644
index 0000000..712fa59
--- /dev/null
+++ b/usr.bin/cal/cal.c
@@ -0,0 +1,924 @@
+/* $NetBSD: cal.c,v 1.29 2015/06/16 22:54:10 christos Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kim Letkeman.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cal.c 8.4 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: cal.c,v 1.29 2015/06/16 22:54:10 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <term.h>
+#include <time.h>
+#include <tzfile.h>
+#include <unistd.h>
+
+#define SATURDAY 6 /* 1 Jan 1 was a Saturday */
+
+#define FIRST_MISSING_DAY reform->first_missing_day
+#define NUMBER_MISSING_DAYS reform->missing_days
+
+#define MAXDAYS 42 /* max slots in a month array */
+#define SPACE -1 /* used in day array */
+
+static int days_in_month[2][13] = {
+ {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+ {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+};
+
+static int empty[MAXDAYS] = {
+ SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
+ SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
+ SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
+ SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
+ SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
+ SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
+};
+static int shift_days[2][4][MAXDAYS + 1];
+
+static const char *month_names[12] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December",
+};
+
+static const char *day_headings = " S M Tu W Th F S";
+static const char *j_day_headings = " S M Tu W Th F S";
+
+/* leap years according to the julian calendar */
+#define j_leap_year(y, m, d) \
+ (((m) > 2) && \
+ !((y) % 4))
+
+/* leap years according to the gregorian calendar */
+#define g_leap_year(y, m, d) \
+ (((m) > 2) && \
+ ((!((y) % 4) && ((y) % 100)) || \
+ !((y) % 400)))
+
+/* leap year -- account for gregorian reformation at some point */
+#define leap_year(yr) \
+ ((yr) <= reform->year ? j_leap_year((yr), 3, 1) : \
+ g_leap_year((yr), 3, 1))
+
+/* number of julian leap days that have passed by a given date */
+#define j_leap_days(y, m, d) \
+ ((((y) - 1) / 4) + j_leap_year(y, m, d))
+
+/* number of gregorian leap days that have passed by a given date */
+#define g_leap_days(y, m, d) \
+ ((((y) - 1) / 4) - (((y) - 1) / 100) + (((y) - 1) / 400) + \
+ g_leap_year(y, m, d))
+
+/*
+ * Subtracting the gregorian leap day count (for a given date) from
+ * the julian leap day count (for the same date) describes the number
+ * of days from the date before the shift to the next date that
+ * appears in the calendar. Since we want to know the number of
+ * *missing* days, not the number of days that the shift spans, we
+ * subtract 2.
+ *
+ * Alternately...
+ *
+ * There's a reason they call the Dark ages the Dark Ages. Part of it
+ * is that we don't have that many records of that period of time.
+ * One of the reasons for this is that a lot of the Dark Ages never
+ * actually took place. At some point in the first millenium A.D., a
+ * ruler of some power decided that he wanted the number of the year
+ * to be different than what it was, so he changed it to coincide
+ * nicely with some event (a birthday or anniversary, perhaps a
+ * wedding, or maybe a centennial for a largish city). One of the
+ * side effects of this upon the Gregorian reform is that two Julian
+ * leap years (leap days celebrated during centennial years that are
+ * not quatro-centennial years) were skipped.
+ */
+#define GREGORIAN_MAGIC 2
+
+/* number of centuries since the reform, not inclusive */
+#define centuries_since_reform(yr) \
+ ((yr) > reform->year ? ((yr) / 100) - (reform->year / 100) : 0)
+
+/* number of centuries since the reform whose modulo of 400 is 0 */
+#define quad_centuries_since_reform(yr) \
+ ((yr) > reform->year ? ((yr) / 400) - (reform->year / 400) : 0)
+
+/* number of leap years between year 1 and this year, not inclusive */
+#define leap_years_since_year_1(yr) \
+ ((yr) / 4 - centuries_since_reform(yr) + quad_centuries_since_reform(yr))
+
+static struct reform {
+ const char *country;
+ int ambiguity, year, month, date;
+ long first_missing_day;
+ int missing_days;
+ /*
+ * That's 2 for standard/julian display, 4 for months possibly
+ * affected by the Gregorian shift, and MAXDAYS + 1 for the
+ * days that get displayed, plus a crib slot.
+ */
+} *reform, reforms[] = {
+ { "DEFAULT", 0, 1752, 9, 3, 0, 0 },
+ { "Italy", 1, 1582, 10, 5, 0, 0 },
+ { "Spain", 1, 1582, 10, 5, 0, 0 },
+ { "Portugal", 1, 1582, 10, 5, 0, 0 },
+ { "Poland", 1, 1582, 10, 5, 0, 0 },
+ { "France", 2, 1582, 12, 10, 0, 0 },
+ { "Luxembourg", 2, 1582, 12, 22, 0, 0 },
+ { "Netherlands", 2, 1582, 12, 22, 0, 0 },
+ { "Bavaria", 0, 1583, 10, 6, 0, 0 },
+ { "Austria", 2, 1584, 1, 7, 0, 0 },
+ { "Switzerland", 2, 1584, 1, 12, 0, 0 },
+ { "Hungary", 0, 1587, 10, 22, 0, 0 },
+ { "Germany", 0, 1700, 2, 19, 0, 0 },
+ { "Norway", 0, 1700, 2, 19, 0, 0 },
+ { "Denmark", 0, 1700, 2, 19, 0, 0 },
+ { "Great Britain", 0, 1752, 9, 3, 0, 0 },
+ { "England", 0, 1752, 9, 3, 0, 0 },
+ { "America", 0, 1752, 9, 3, 0, 0 },
+ { "Sweden", 0, 1753, 2, 18, 0, 0 },
+ { "Finland", 0, 1753, 2, 18, 0, 0 },
+ { "Japan", 0, 1872, 12, 20, 0, 0 },
+ { "China", 0, 1911, 11, 7, 0, 0 },
+ { "Bulgaria", 0, 1916, 4, 1, 0, 0 },
+ { "U.S.S.R.", 0, 1918, 2, 1, 0, 0 },
+ { "Serbia", 0, 1919, 1, 19, 0, 0 },
+ { "Romania", 0, 1919, 1, 19, 0, 0 },
+ { "Greece", 0, 1924, 3, 10, 0, 0 },
+ { "Turkey", 0, 1925, 12, 19, 0, 0 },
+ { "Egypt", 0, 1928, 9, 18, 0, 0 },
+ { NULL, 0, 0, 0, 0, 0, 0 },
+};
+
+static int julian;
+static int dow;
+static int hilite;
+static const char *md, *me;
+
+static void init_hilite(void);
+static int getnum(const char *);
+static void gregorian_reform(const char *);
+static void reform_day_array(int, int, int *, int *, int *,int *,int *,int *);
+static int ascii_day(char *, int);
+static void center(const char *, int, int);
+static void day_array(int, int, int *);
+static int day_in_week(int, int, int);
+static int day_in_year(int, int, int);
+static void monthrange(int, int, int, int, int);
+static void trim_trailing_spaces(char *);
+__dead static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ struct tm *local_time;
+ time_t now;
+ int ch, yflag;
+ long month, year;
+ int before, after, use_reform;
+ int yearly = 0;
+ char *when, *eoi;
+
+ before = after = 0;
+ use_reform = yflag = year = 0;
+ when = NULL;
+ while ((ch = getopt(argc, argv, "A:B:C:d:hjR:ry3")) != -1) {
+ switch (ch) {
+ case 'A':
+ after = getnum(optarg);
+ if (after < 0)
+ errx(1, "Argument to -A must be positive");
+ break;
+ case 'B':
+ before = getnum(optarg);
+ if (before < 0)
+ errx(1, "Argument to -B must be positive");
+ break;
+ case 'C':
+ after = before = getnum(optarg);
+ if (after < 0)
+ errx(1, "Argument to -C must be positive");
+ break;
+ case 'd':
+ dow = getnum(optarg);
+ if (dow < 0 || dow > 6)
+ errx(1, "illegal day of week value: use 0-6");
+ break;
+ case 'h':
+ init_hilite();
+ break;
+ case 'j':
+ julian = 1;
+ break;
+ case 'R':
+ when = optarg;
+ break;
+ case 'r':
+ use_reform = 1;
+ break;
+ case 'y':
+ yflag = 1;
+ break;
+ case '3':
+ before = after = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (when != NULL)
+ gregorian_reform(when);
+ if (reform == NULL)
+ gregorian_reform("DEFAULT");
+
+ month = 0;
+ switch (argc) {
+ case 2:
+ month = strtol(*argv++, &eoi, 10);
+ if (month < 1 || month > 12 || *eoi != '\0')
+ errx(1, "illegal month value: use 1-12");
+ year = strtol(*argv, &eoi, 10);
+ if (year < 1 || year > 9999 || *eoi != '\0')
+ errx(1, "illegal year value: use 1-9999");
+ break;
+ case 1:
+ year = strtol(*argv, &eoi, 10);
+ if (year < 1 || year > 9999 || (*eoi != '\0' && *eoi != '/' && *eoi != '-'))
+ errx(1, "illegal year value: use 1-9999");
+ if (*eoi != '\0') {
+ month = strtol(eoi + 1, &eoi, 10);
+ if (month < 1 || month > 12 || *eoi != '\0')
+ errx(1, "illegal month value: use 1-12");
+ }
+ break;
+ case 0:
+ (void)time(&now);
+ local_time = localtime(&now);
+ if (use_reform)
+ year = reform->year;
+ else
+ year = local_time->tm_year + TM_YEAR_BASE;
+ if (!yflag) {
+ if (use_reform)
+ month = reform->month;
+ else
+ month = local_time->tm_mon + 1;
+ }
+ break;
+ default:
+ usage();
+ }
+
+ if (!month) {
+ /* yearly */
+ month = 1;
+ before = 0;
+ after = 11;
+ yearly = 1;
+ }
+
+ monthrange(month, year, before, after, yearly);
+
+ exit(0);
+}
+
+#define DAY_LEN 3 /* 3 spaces per day */
+#define J_DAY_LEN 4 /* 4 spaces per day */
+#define WEEK_LEN 20 /* 7 * 3 - one space at the end */
+#define J_WEEK_LEN 27 /* 7 * 4 - one space at the end */
+#define HEAD_SEP 2 /* spaces between day headings */
+#define J_HEAD_SEP 2
+#define MONTH_PER_ROW 3 /* how many monthes in a row */
+#define J_MONTH_PER_ROW 2
+
+static void
+monthrange(int month, int year, int before, int after, int yearly)
+{
+ int startmonth, startyear;
+ int endmonth, endyear;
+ int i, row;
+ int days[3][MAXDAYS];
+ char lineout[256];
+ int inayear;
+ int newyear;
+ int day_len, week_len, head_sep;
+ int month_per_row;
+ int skip, r_off, w_off;
+
+ if (julian) {
+ day_len = J_DAY_LEN;
+ week_len = J_WEEK_LEN;
+ head_sep = J_HEAD_SEP;
+ month_per_row = J_MONTH_PER_ROW;
+ }
+ else {
+ day_len = DAY_LEN;
+ week_len = WEEK_LEN;
+ head_sep = HEAD_SEP;
+ month_per_row = MONTH_PER_ROW;
+ }
+
+ month--;
+
+ startyear = year - (before + 12 - 1 - month) / 12;
+ startmonth = 12 - 1 - ((before + 12 - 1 - month) % 12);
+ endyear = year + (month + after) / 12;
+ endmonth = (month + after) % 12;
+
+ if (startyear < 0 || endyear > 9999) {
+ errx(1, "year should be in 1-9999");
+ }
+
+ year = startyear;
+ month = startmonth;
+ inayear = newyear = (year != endyear || yearly);
+ if (inayear) {
+ skip = month % month_per_row;
+ month -= skip;
+ }
+ else {
+ skip = 0;
+ }
+
+ do {
+ if (newyear) {
+ (void)snprintf(lineout, sizeof(lineout), "%d", year);
+ center(lineout, week_len * month_per_row +
+ head_sep * (month_per_row - 1), 0);
+ (void)printf("\n\n");
+ newyear = 0;
+ }
+
+ for (i = 0; i < skip; i++)
+ center("", week_len, head_sep);
+
+ for (; i < month_per_row; i++) {
+ int sep;
+
+ if (year == endyear && month + i > endmonth)
+ break;
+
+ sep = (i == month_per_row - 1) ? 0 : head_sep;
+ day_array(month + i + 1, year, days[i]);
+ if (inayear) {
+ center(month_names[month + i], week_len, sep);
+ }
+ else {
+ snprintf(lineout, sizeof(lineout), "%s %d",
+ month_names[month + i], year);
+ center(lineout, week_len, sep);
+ }
+ }
+ printf("\n");
+
+ for (i = 0; i < skip; i++)
+ center("", week_len, head_sep);
+
+ for (; i < month_per_row; i++) {
+ int sep;
+
+ if (year == endyear && month + i > endmonth)
+ break;
+
+ sep = (i == month_per_row - 1) ? 0 : head_sep;
+ if (dow) {
+ printf("%s ", (julian) ?
+ j_day_headings + 4 * dow :
+ day_headings + 3 * dow);
+ printf("%.*s", dow * (julian ? 4 : 3) - 1,
+ (julian) ? j_day_headings : day_headings);
+ } else
+ printf("%s", (julian) ? j_day_headings : day_headings);
+ printf("%*s", sep, "");
+ }
+ printf("\n");
+
+ for (row = 0; row < 6; row++) {
+ char *p = NULL;
+
+ memset(lineout, ' ', sizeof(lineout));
+ for (i = 0; i < skip; i++) {
+ /* nothing */
+ }
+ w_off = 0;
+ for (; i < month_per_row; i++) {
+ int col, *dp;
+
+ if (year == endyear && month + i > endmonth)
+ break;
+
+ p = lineout + i * (week_len + 2) + w_off;
+ dp = &days[i][row * 7];
+ for (col = 0; col < 7;
+ col++, p += day_len + r_off) {
+ r_off = ascii_day(p, *dp++);
+ w_off += r_off;
+ }
+ }
+ *p = '\0';
+ trim_trailing_spaces(lineout);
+ (void)printf("%s\n", lineout);
+ }
+
+ skip = 0;
+ month += month_per_row;
+ if (month >= 12) {
+ month -= 12;
+ year++;
+ newyear = 1;
+ }
+ } while (year < endyear || (year == endyear && month <= endmonth));
+}
+
+/*
+ * day_array --
+ * Fill in an array of 42 integers with a calendar. Assume for a moment
+ * that you took the (maximum) 6 rows in a calendar and stretched them
+ * out end to end. You would have 42 numbers or spaces. This routine
+ * builds that array for any month from Jan. 1 through Dec. 9999.
+ */
+static void
+day_array(int month, int year, int *days)
+{
+ int day, dw, dm;
+ time_t t;
+ struct tm *tm;
+
+ t = time(NULL);
+ tm = localtime(&t);
+ tm->tm_year += TM_YEAR_BASE;
+ tm->tm_mon++;
+ tm->tm_yday++; /* jan 1 is 1 for us, not 0 */
+
+ for (dm = month + year * 12, dw = 0; dw < 4; dw++) {
+ if (dm == shift_days[julian][dw][MAXDAYS]) {
+ memmove(days, shift_days[julian][dw],
+ MAXDAYS * sizeof(int));
+ return;
+ }
+ }
+
+ memmove(days, empty, MAXDAYS * sizeof(int));
+ dm = days_in_month[leap_year(year)][month];
+ dw = day_in_week(1, month, year);
+ day = julian ? day_in_year(1, month, year) : 1;
+ while (dm--) {
+ if (hilite && year == tm->tm_year &&
+ (julian ? (day == tm->tm_yday) :
+ (month == tm->tm_mon && day == tm->tm_mday)))
+ days[dw++] = SPACE - day++;
+ else
+ days[dw++] = day++;
+ }
+}
+
+/*
+ * day_in_year --
+ * return the 1 based day number within the year
+ */
+static int
+day_in_year(int day, int month, int year)
+{
+ int i, leap;
+
+ leap = leap_year(year);
+ for (i = 1; i < month; i++)
+ day += days_in_month[leap][i];
+ return (day);
+}
+
+/*
+ * day_in_week
+ * return the 0 based day number for any date from 1 Jan. 1 to
+ * 31 Dec. 9999. Returns the day of the week of the first
+ * missing day for any given Gregorian shift.
+ */
+static int
+day_in_week(int day, int month, int year)
+{
+ long temp;
+
+ temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1)
+ + day_in_year(day, month, year);
+ if (temp < FIRST_MISSING_DAY)
+ return ((temp - dow + 6 + SATURDAY) % 7);
+ if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS))
+ return (((temp - dow + 6 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
+ return ((FIRST_MISSING_DAY - dow + 6 + SATURDAY) % 7);
+}
+
+static int
+ascii_day(char *p, int day)
+{
+ int display, val, rc;
+ char *b;
+ static const char *aday[] = {
+ "",
+ " 1", " 2", " 3", " 4", " 5", " 6", " 7",
+ " 8", " 9", "10", "11", "12", "13", "14",
+ "15", "16", "17", "18", "19", "20", "21",
+ "22", "23", "24", "25", "26", "27", "28",
+ "29", "30", "31",
+ };
+
+ if (day == SPACE) {
+ memset(p, ' ', julian ? J_DAY_LEN : DAY_LEN);
+ return (0);
+ }
+ if (day < SPACE) {
+ b = p;
+ day = SPACE - day;
+ } else
+ b = NULL;
+ if (julian) {
+ if ((val = day / 100) != 0) {
+ day %= 100;
+ *p++ = val + '0';
+ display = 1;
+ } else {
+ *p++ = ' ';
+ display = 0;
+ }
+ val = day / 10;
+ if (val || display)
+ *p++ = val + '0';
+ else
+ *p++ = ' ';
+ *p++ = day % 10 + '0';
+ } else {
+ *p++ = aday[day][0];
+ *p++ = aday[day][1];
+ }
+
+ rc = 0;
+ if (b != NULL) {
+ const char *t;
+ char h[64];
+ int l;
+
+ l = p - b;
+ memcpy(h, b, l);
+ p = b;
+
+ if (md != NULL) {
+ for (t = md; *t; rc++)
+ *p++ = *t++;
+ memcpy(p, h, l);
+ p += l;
+ for (t = me; *t; rc++)
+ *p++ = *t++;
+ } else {
+ for (t = &h[0]; l--; t++) {
+ *p++ = *t;
+ rc++;
+ *p++ = '\b';
+ rc++;
+ *p++ = *t;
+ }
+ }
+ }
+
+ *p = ' ';
+ return (rc);
+}
+
+static void
+trim_trailing_spaces(char *s)
+{
+ char *p;
+
+ for (p = s; *p; ++p)
+ continue;
+ while (p > s && isspace((unsigned char)*--p))
+ continue;
+ if (p > s)
+ ++p;
+ *p = '\0';
+}
+
+static void
+center(const char *str, int len, int separate)
+{
+
+ len -= strlen(str);
+ (void)printf("%*s%s%*s", len / 2, "", str, len / 2 + len % 2, "");
+ if (separate)
+ (void)printf("%*s", separate, "");
+}
+
+/*
+ * gregorian_reform --
+ * Given a description of date on which the Gregorian Reform was
+ * applied. The argument can be any of the "country" names
+ * listed in the reforms array (case insensitive) or a date of
+ * the form YYYY/MM/DD. The date and month can be omitted if
+ * doing so would not select more than one different built-in
+ * reform point.
+ */
+static void
+gregorian_reform(const char *p)
+{
+ int year, month, date;
+ int i, days, diw, diy;
+ char c;
+
+ i = sscanf(p, "%d%*[/,-]%d%*[/,-]%d%c", &year, &month, &date, &c);
+ switch (i) {
+ case 4:
+ /*
+ * If the character was sscanf()ed, then there's more
+ * stuff than we need.
+ */
+ errx(1, "date specifier %s invalid", p);
+ case 0:
+ /*
+ * Not a form we can sscanf(), so void these, and we
+ * can try matching "country" names later.
+ */
+ year = month = date = -1;
+ break;
+ case 1:
+ month = 0;
+ /*FALLTHROUGH*/
+ case 2:
+ date = 0;
+ /*FALLTHROUGH*/
+ case 3:
+ /*
+ * At last, some sanity checking on the values we were
+ * given.
+ */
+ if (year < 1 || year > 9999)
+ errx(1, "%d: illegal year value: use 1-9999", year);
+ if (i > 1 && (month < 1 || month > 12))
+ errx(1, "%d: illegal month value: use 1-12", month);
+ if ((i == 3 && date < 1) || date < 0 ||
+ date > days_in_month[1][month])
+ /*
+ * What about someone specifying a leap day in
+ * a non-leap year? Well...that's a tricky
+ * one. We can't yet *say* whether the year
+ * in question is a leap year. What if the
+ * date given was, for example, 1700/2/29? is
+ * that a valid leap day?
+ *
+ * So...we punt, and hope that saying 29 in
+ * the case of February isn't too bad an idea.
+ */
+ errx(1, "%d: illegal date value: use 1-%d", date,
+ days_in_month[1][month]);
+ break;
+ }
+
+ /*
+ * A complete date was specified, so use the other pope.
+ */
+ if (date > 0) {
+ static struct reform Goestheveezl;
+
+ reform = &Goestheveezl;
+ reform->country = "Bompzidaize";
+ reform->year = year;
+ reform->month = month;
+ reform->date = date;
+ }
+
+ /*
+ * No date information was specified, so let's try to match on
+ * country name.
+ */
+ else if (year == -1) {
+ for (reform = &reforms[0]; reform->year; reform++) {
+ if (strcasecmp(p, reform->country) == 0)
+ break;
+ }
+ }
+
+ /*
+ * We have *some* date information, but not a complete date.
+ * Let's see if we have enough to pick a single entry from the
+ * list that's not ambiguous.
+ */
+ else {
+ for (reform = &reforms[0]; reform->year; reform++) {
+ if ((year == 0 || year == reform->year) &&
+ (month == 0 || month == reform->month) &&
+ (date == 0 || month == reform->date))
+ break;
+ }
+
+ if (i <= reform->ambiguity)
+ errx(1, "%s: ambiguous short reform date specification", p);
+ }
+
+ /*
+ * Oops...we reached the end of the list.
+ */
+ if (reform->year == 0)
+ errx(1, "reform name %s invalid", p);
+
+ /*
+ *
+ */
+ reform->missing_days =
+ j_leap_days(reform->year, reform->month, reform->date) -
+ g_leap_days(reform->year, reform->month, reform->date) -
+ GREGORIAN_MAGIC;
+
+ reform->first_missing_day =
+ (reform->year - 1) * 365 +
+ day_in_year(reform->date, reform->month, reform->year) +
+ date +
+ j_leap_days(reform->year, reform->month, reform->date);
+
+ /*
+ * Once we know the day of the week of the first missing day,
+ * skip back to the first of the month's day of the week.
+ */
+ diw = day_in_week(reform->date, reform->month, reform->year);
+ diw = (diw + 8 - (reform->date % 7)) % 7;
+ diy = day_in_year(1, reform->month, reform->year);
+
+ /*
+ * We might need all four of these (if you switch from Julian
+ * to Gregorian at some point after 9900, you get a gap of 73
+ * days, and that can affect four months), and it doesn't hurt
+ * all that much to precompute them, so there.
+ */
+ date = 1;
+ days = 0;
+ for (i = 0; i < 4; i++)
+ reform_day_array(reform->month + i, reform->year,
+ &days, &date, &diw, &diy,
+ shift_days[0][i],
+ shift_days[1][i]);
+}
+
+/*
+ * reform_day_array --
+ * Pre-calculates the given month's calendar (in both "standard"
+ * and "julian day" representations) with respect for days
+ * skipped during a reform period.
+ */
+static void
+reform_day_array(int month, int year, int *done, int *date, int *diw, int *diy,
+ int *scal, int *jcal)
+{
+ int mdays;
+
+ /*
+ * If the reform was in the month of october or later, then
+ * the month number from the caller could "overflow".
+ */
+ if (month > 12) {
+ month -= 12;
+ year++;
+ }
+
+ /*
+ * Erase months, and set crib number. The crib number is used
+ * later to determine if the month to be displayed is here or
+ * should be built on the fly with the generic routine
+ */
+ memmove(scal, empty, MAXDAYS * sizeof(int));
+ scal[MAXDAYS] = month + year * 12;
+ memmove(jcal, empty, MAXDAYS * sizeof(int));
+ jcal[MAXDAYS] = month + year * 12;
+
+ /*
+ * It doesn't matter what the actual month is when figuring
+ * out if this is a leap year or not, just so long as February
+ * gets the right number of days in it.
+ */
+ mdays = days_in_month[g_leap_year(year, 3, 1)][month];
+
+ /*
+ * Bounce back to the first "row" in the day array, and fill
+ * in any days that actually occur.
+ */
+ for (*diw %= 7; (*date - *done) <= mdays; (*date)++, (*diy)++) {
+ /*
+ * "date" doesn't get reset by the caller across calls
+ * to this routine, so we can actually tell that we're
+ * looking at April the 41st. Much easier than trying
+ * to calculate the absolute julian day for a given
+ * date and then checking that.
+ */
+ if (*date < reform->date ||
+ *date >= reform->date + reform->missing_days) {
+ scal[*diw] = *date - *done;
+ jcal[*diw] = *diy;
+ (*diw)++;
+ }
+ }
+ *done += mdays;
+}
+
+static int
+getnum(const char *p)
+{
+ unsigned long result;
+ char *ep;
+
+ errno = 0;
+ result = strtoul(p, &ep, 10);
+ if (p[0] == '\0' || *ep != '\0')
+ goto error;
+ if (errno == ERANGE && result == ULONG_MAX)
+ goto error;
+ if (result > INT_MAX)
+ goto error;
+
+ return (int)result;
+
+error:
+ errx(1, "bad number: %s", p);
+ /*NOTREACHED*/
+}
+
+static void
+init_hilite(void)
+{
+ const char *term;
+ int errret;
+
+ hilite++;
+
+ if (!isatty(fileno(stdout)))
+ return;
+
+ term = getenv("TERM");
+ if (term == NULL)
+ term = "dumb";
+ if (setupterm(term, fileno(stdout), &errret) != 0 && errret != 1)
+ return;
+
+ if (hilite > 1)
+ md = enter_reverse_mode;
+ else
+ md = enter_bold_mode;
+ me = exit_attribute_mode;
+ if (me == NULL || md == NULL)
+ md = me = NULL;
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "usage: cal [-3hjry] [-A after] [-B before] [-C context] [-d day-of-week] "
+ "[-R reform-spec]\n [[month] year]\n");
+ exit(1);
+}
diff --git a/usr.bin/cksum/cksum.1 b/usr.bin/cksum/cksum.1
new file mode 100644
index 0000000..57c63b1
--- /dev/null
+++ b/usr.bin/cksum/cksum.1
@@ -0,0 +1,341 @@
+.\" $NetBSD: cksum.1,v 1.48 2017/07/03 21:34:18 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)cksum.1 8.2 (Berkeley) 4/28/95
+.\"
+.Dd August 31, 2014
+.Dt CKSUM 1
+.Os
+.Sh NAME
+.Nm cksum ,
+.Nm md2 ,
+.Nm md4 ,
+.Nm md5 ,
+.Nm rmd160 ,
+.Nm sha1 ,
+.Nm sum
+.Nd display file checksums and block counts
+.Sh SYNOPSIS
+.Nm cksum
+.Op Fl n
+.Op Fl a Ar algorithm Oo Fl pqtx Oc Oo Fl s Ar string Oc
+.Op Fl o Ar 1 Ns | Ns Ar 2
+.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc
+.Nm sum
+.Op Fl n
+.Op Fl a Ar algorithm Oo Fl pqtx Oc Oo Fl s Ar string Oc
+.Op Fl o Ar 1 Ns | Ns Ar 2
+.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc
+.Nm md2
+.Op Fl npqtx
+.Op Fl s Ar string
+.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc
+.Nm md4
+.Op Fl npqtx
+.Op Fl s Ar string
+.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc
+.Nm md5
+.Op Fl npqtx
+.Op Fl s Ar string
+.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc
+.Nm rmd160
+.Op Fl npqtx
+.Op Fl s Ar string
+.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc
+.Nm sha1
+.Op Fl npqtx
+.Op Fl s Ar string
+.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc
+.Sh DESCRIPTION
+The
+.Nm
+utility writes to the standard output three whitespace separated
+fields for each input file.
+These fields are a checksum
+.Tn CRC ,
+the total number of octets in the file and the file name.
+If no file name is specified, the standard input is used and no file name
+is written.
+.Pp
+The
+.Nm sum
+utility is identical to the
+.Nm
+utility, except that it defaults to using historic algorithm 1, as
+described below.
+It is provided for compatibility only.
+.Pp
+The
+.Nm md2 ,
+.Nm md4 ,
+.Nm md5 ,
+.Nm sha1 ,
+and
+.Nm rmd160
+utilities compute cryptographic hash functions, and write to standard
+output the hexadecimal representation of the hash of their input.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a Ar algorithm
+When invoked as
+.Nm cksum ,
+use the specified
+.Ar algorithm .
+Valid algorithms are:
+.Bl -column -offset indent ".Sy Algorithm" ".Sy Bits" ".Sy Description"
+.It Sy Algorithm Ta Sy Bits Ta Sy Description
+.It Li CRC Ta 32 Ta Default CRC algorithm
+.It Li MD2 Ta 128 Ta MD2, per Li RFC1319
+.It Li MD4 Ta 128 Ta MD4, per Li RFC1320
+.It Li MD5 Ta 128 Ta MD5, per Li RFC1321
+.It Li RMD160 Ta 160 Ta RIPEMD-160
+.It Li SHA1 Ta 160 Ta SHA-1, per Li FIPS PUB 180-1
+.It Li SHA256 Ta 256 Ta SHA-2
+.It Li SHA384 Ta 384 Ta SHA-2
+.It Li SHA512 Ta 512 Ta SHA-2
+.It Li old1 Ta 16 Ta Algorithm 1, per Fl o Ar 1
+.It Li old2 Ta 16 Ta Algorithm 2, per Fl o Ar 2
+.El
+.It Fl c Op Ar sumfile
+Verify (check) files against a list of checksums.
+The list is read from
+.Ar sumfile ,
+or from stdin if no filename is given.
+E.g. first run
+.Dl Ic md5 *.tgz > MD5
+.Dl Ic sha1 *.tgz > SHA1
+to generate a list of MD5 checksums in
+.Pa MD5 ,
+then use the following command to verify them:
+.Dl Ic cat MD5 SHA1 | cksum -c
+If an error is found during checksum verification, an error
+message is printed, and the program returns an error code of 1.
+.It Fl o
+Use historic algorithms instead of the (superior) default one.
+.Pp
+Algorithm 1 is the algorithm used by historic
+.Bx
+systems as the
+.Xr sum 1
+algorithm and by historic
+.At V
+systems as the
+.Xr sum 1
+algorithm when using the
+.Fl r
+option.
+This is a 16-bit checksum, with a right rotation before each addition;
+overflow is discarded.
+.Pp
+Algorithm 2 is the algorithm used by historic
+.At V
+systems as the
+default
+.Xr sum 1
+algorithm.
+This is a 32-bit checksum, and is defined as follows:
+.Bd -unfilled -offset indent
+s = sum of all bytes;
+r = s % 2^16 + (s % 2^32) / 2^16;
+cksum = (r % 2^16) + r / 2^16;
+.Ed
+.Pp
+Both algorithm 1 and 2 write to the standard output the same fields as
+the default algorithm except that the size of the file in bytes is
+replaced with the size of the file in blocks.
+For historic reasons, the block size is 1024 for algorithm 1 and 512
+for algorithm 2.
+Partial blocks are rounded up.
+.It Fl w
+Print warnings about malformed checksum files when verifying
+checksums with
+.Fl c .
+.El
+.Pp
+The following options apply only when using the one of the message
+digest algorithms:
+.Bl -tag -width indent
+.It Fl n
+Print the hash and the filename in the normal sum output form, with
+the hash at the left and the filename following on the right.
+.It Fl p
+Echo input from standard input to standard output, and append the
+selected message digest.
+.It Fl q
+Quiet mode \(em only the checksum is printed out.
+Overrides the
+.Fl n
+option.
+.It Fl s Ar string
+Print the hash of the given string
+.Ar string .
+.It Fl t
+Run a built-in message digest time trial.
+.It Fl x
+Run a built-in message digest test script.
+The tests that are run
+are supposed to encompass all the various tests in the suites that
+accompany the algorithms' descriptions with the exception of the
+last test for the SHA-1 algorithm and the RIPEMD-160 algorithm.
+The
+last test for these is one million copies of the lower letter a.
+.El
+.Pp
+The default
+.Tn CRC
+used is based on the polynomial used for
+.Tn CRC
+error checking
+in the networking standard
+.St -iso8802-3 .
+The
+.Tn CRC
+checksum encoding is defined by the generating polynomial:
+.Pp
+.Bd -unfilled -offset indent
+G(x) = x^32 + x^26 + x^23 + x^22 + x^16 + x^12 +
+ x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1
+.Ed
+.Pp
+Mathematically, the
+.Tn CRC
+value corresponding to a given file is defined by
+the following procedure:
+.Bd -filled -offset indent
+The
+.Ar n
+bits to be evaluated are considered to be the coefficients of a mod 2
+polynomial M(x) of degree
+.Ar n Ns \-1 .
+These
+.Ar n
+bits are the bits from the file, with the most significant bit being the most
+significant bit of the first octet of the file and the last bit being the least
+significant bit of the last octet, padded with zero bits (if necessary) to
+achieve an integral number of octets, followed by one or more octets
+representing the length of the file as a binary value, least significant octet
+first.
+The smallest number of octets capable of representing this integer are used.
+.Pp
+M(x) is multiplied by x^32 (i.e., shifted left 32 bits) and divided by
+G(x) using mod 2 division, producing a remainder R(x) of degree \*[Le] 31.
+.Pp
+The coefficients of R(x) are considered to be a 32-bit sequence.
+.Pp
+The bit sequence is complemented and the result is the CRC.
+.Ed
+.Pp
+The
+.Nm
+and
+.Nm sum
+utilities exit 0 on success, and >0 if an error occurs.
+.Sh SEE ALSO
+.Xr openssl 1 ,
+.Xr mtree 8
+.Pp
+The default calculation is identical to that given in pseudo-code
+in the following
+.Tn ACM
+article.
+.Rs
+.%T "Computation of Cyclic Redundancy Checks Via Table Lookup"
+.%A Dilip V. Sarwate
+.%J "Communications of the ACM"
+.%D "August 1988"
+.Re
+.Rs
+.%A R. Rivest
+.%T The MD2 Message-Digest Algorithm
+.%O RFC 1319
+.Re
+.Rs
+.%A R. Rivest
+.%T The MD4 Message-Digest Algorithm
+.%O RFC 1186 and RFC 1320
+.Re
+.Rs
+.%A R. Rivest
+.%T The MD5 Message-Digest Algorithm
+.%O RFC 1321
+.Re
+.Rs
+.%A U.S. DOC/NIST
+.%T Secure Hash Standard
+.%O FIPS PUB 180-1
+.Re
+.Sh STANDARDS
+The
+.Nm
+utility is expected to conform to
+.St -p1003.1-2004 .
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.4 .
+.Nm md5
+was added in
+.Nx 1.3 .
+The functionality for
+.Nm md2 ,
+.Nm md4 ,
+.Nm sha1 ,
+and
+.Nm rmd160
+was added in
+.Nx 1.6 .
+Support for the SHA-2 algorithms
+.Po
+.Li SHA256 ,
+.Li SHA384 ,
+and
+.Li SHA512
+.Pc
+was added in
+.Nx 3.0 .
+The functionality to verify checksum stored in a file
+.Pq Fl c
+first appeared in
+.Nx 4.0 .
+Quiet mode
+.Pq Fl q
+was added in
+.Nx 7.0 .
+.\" .Pp
+.\" The
+.\" .Nm sum
+.\" utility appeared in
+.\" .Bx ?.?
+.\" and
+.\" .At V .
diff --git a/usr.bin/cksum/cksum.c b/usr.bin/cksum/cksum.c
new file mode 100644
index 0000000..9e7e87a
--- /dev/null
+++ b/usr.bin/cksum/cksum.c
@@ -0,0 +1,551 @@
+/* $NetBSD: cksum.c,v 1.48 2015/06/16 22:54:10 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * James W. Williams of NASA Goddard Space Flight Center.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1997 Jason R. Thorpe. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * James W. Williams of NASA Goddard Space Flight Center.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__COPYRIGHT) && !defined(lint)
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)cksum.c 8.2 (Berkeley) 4/28/95";
+#endif
+__RCSID("$NetBSD: cksum.c,v 1.48 2015/06/16 22:54:10 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <md2.h>
+#include <md4.h>
+#include <md5.h>
+#include <rmd160.h>
+#include <sha1.h>
+#include <sha2.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+#define PRINT_NORMAL 0x01
+#define PRINT_QUIET 0x02
+
+typedef char *(*_filefunc)(const char *, char *);
+
+const struct hash {
+ const char *progname;
+ const char *hashname;
+ void (*stringfunc)(const char *);
+ void (*timetrialfunc)(void);
+ void (*testsuitefunc)(void);
+ void (*filterfunc)(int);
+ char *(*filefunc)(const char *, char *);
+} hashes[] = {
+ { "md2", "MD2",
+ MD2String, MD2TimeTrial, MD2TestSuite,
+ MD2Filter, MD2File },
+ { "md4", "MD4",
+ MD4String, MD4TimeTrial, MD4TestSuite,
+ MD4Filter, MD4File },
+ { "md5", "MD5",
+ MD5String, MD5TimeTrial, MD5TestSuite,
+ MD5Filter, MD5File },
+ { "rmd160", "RMD160",
+ RMD160String, RMD160TimeTrial, RMD160TestSuite,
+ RMD160Filter, (_filefunc) RMD160File },
+ { "sha1", "SHA1",
+ SHA1String, SHA1TimeTrial, SHA1TestSuite,
+ SHA1Filter, (_filefunc) SHA1File },
+ { "sha256", "SHA256",
+ SHA256_String, SHA256_TimeTrial, SHA256_TestSuite,
+ SHA256_Filter, (_filefunc) SHA256_File },
+ { "sha384", "SHA384",
+ SHA384_String, SHA384_TimeTrial, SHA384_TestSuite,
+ SHA384_Filter, (_filefunc) SHA384_File },
+ { "sha512", "SHA512",
+ SHA512_String, SHA512_TimeTrial, SHA512_TestSuite,
+ SHA512_Filter, (_filefunc) SHA512_File },
+ { .progname = NULL, },
+};
+
+static int hash_digest_file(char *, const struct hash *, int);
+__dead static void requirehash(const char *);
+__dead static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ int ch, fd, rval, pflag, nohashstdin;
+ u_int32_t val;
+ off_t len;
+ char *fn;
+ const char *progname;
+ int (*cfncn) (int, u_int32_t *, off_t *);
+ void (*pfncn) (char *, u_int32_t, off_t);
+ const struct hash *hash;
+ int i, check_warn, do_check;
+ int print_flags;
+
+ cfncn = NULL;
+ pfncn = NULL;
+ pflag = nohashstdin = 0;
+ check_warn = 0;
+ do_check = 0;
+ print_flags = 0;
+
+ setlocale(LC_ALL, "");
+
+ progname = getprogname();
+
+ for (hash = hashes; hash->hashname != NULL; hash++)
+ if (strcmp(progname, hash->progname) == 0)
+ break;
+
+ if (hash->hashname == NULL) {
+ hash = NULL;
+
+ if (!strcmp(progname, "sum")) {
+ cfncn = csum1;
+ pfncn = psum1;
+ } else {
+ cfncn = crc;
+ pfncn = pcrc;
+ }
+ }
+
+ while ((ch = getopt(argc, argv, "a:cno:pqs:twx")) != -1)
+ switch(ch) {
+ case 'a':
+ if (hash) {
+ warnx("illegal use of -a option");
+ usage();
+ }
+ i = 0;
+ while (hashes[i].hashname != NULL) {
+ if (!strcasecmp(hashes[i].hashname, optarg)) {
+ hash = &hashes[i];
+ break;
+ }
+ i++;
+ }
+ if (hash == NULL) {
+ if (!strcasecmp(optarg, "old1")) {
+ cfncn = csum1;
+ pfncn = psum1;
+ } else if (!strcasecmp(optarg, "old2")) {
+ cfncn = csum2;
+ pfncn = psum2;
+ } else if (!strcasecmp(optarg, "crc")) {
+ cfncn = crc;
+ pfncn = pcrc;
+ } else {
+ warnx("illegal argument to -a option");
+ usage();
+ }
+ }
+ break;
+ case 'c':
+ do_check = 1;
+ break;
+ case 'n':
+ print_flags |= PRINT_NORMAL;
+ break;
+ case 'o':
+ if (hash) {
+ warnx("%s mutually exclusive with sum",
+ hash->hashname);
+ usage();
+ }
+ if (!strcmp(optarg, "1")) {
+ cfncn = csum1;
+ pfncn = psum1;
+ } else if (!strcmp(optarg, "2")) {
+ cfncn = csum2;
+ pfncn = psum2;
+ } else {
+ warnx("illegal argument to -o option");
+ usage();
+ }
+ break;
+ case 'p':
+ if (hash == NULL)
+ requirehash("-p");
+ pflag = 1;
+ break;
+ case 'q':
+ print_flags |= PRINT_QUIET;
+ break;
+ case 's':
+ if (hash == NULL)
+ requirehash("-s");
+ nohashstdin = 1;
+ hash->stringfunc(optarg);
+ break;
+ case 't':
+ if (hash == NULL)
+ requirehash("-t");
+ nohashstdin = 1;
+ hash->timetrialfunc();
+ break;
+ case 'w':
+ check_warn = 1;
+ break;
+ case 'x':
+ if (hash == NULL)
+ requirehash("-x");
+ nohashstdin = 1;
+ hash->testsuitefunc();
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (do_check) {
+ /*
+ * Verify checksums
+ */
+ FILE *f;
+ char buf[BUFSIZ];
+ char *s, *p_filename, *p_cksum;
+ int l_filename, l_cksum;
+ char filename[BUFSIZ];
+ char cksum[BUFSIZ];
+ int ok,cnt,badcnt;
+
+ rval = 0;
+ cnt = badcnt = 0;
+
+ if (argc == 0) {
+ f = fdopen(STDIN_FILENO, "r");
+ } else {
+ f = fopen(argv[0], "r");
+ }
+ if (f == NULL)
+ err(1, "Cannot read %s",
+ argc>0?argv[0]:"stdin");
+
+ while(fgets(buf, sizeof(buf), f) != NULL) {
+ s = strrchr(buf, '\n');
+ if (s)
+ *s = '\0';
+
+ p_cksum = p_filename = NULL;
+
+ p_filename = strchr(buf, '(');
+ if (p_filename) {
+ /*
+ * Assume 'normal' output if there's a '('
+ */
+ p_filename += 1;
+ print_flags &= ~(PRINT_NORMAL);
+
+ p_cksum = strrchr(p_filename, ')');
+ if (p_cksum == NULL) {
+ if (check_warn)
+ warnx("bogus format: %s. "
+ "Skipping...",
+ buf);
+ rval = 1;
+ continue;
+ }
+ p_cksum += 4;
+
+ l_cksum = strlen(p_cksum);
+ l_filename = p_cksum - p_filename - 4;
+
+ /* Sanity check, and find proper hash if
+ * it's not the same as the current program
+ */
+ if (hash == NULL ||
+ strncmp(buf, hash->hashname,
+ strlen(hash->hashname)) != 0) {
+ /*
+ * Search proper hash
+ */
+ const struct hash *nhash;
+
+ for (nhash = hashes ;
+ nhash->hashname != NULL;
+ nhash++)
+ if (strncmp(buf,
+ nhash->hashname,
+ strlen(nhash->hashname)) == 0)
+ break;
+
+
+ if (nhash->hashname == NULL) {
+ if (check_warn)
+ warnx("unknown hash: %s",
+ buf);
+ rval = 1;
+ continue;
+ } else {
+ hash = nhash;
+ }
+ }
+
+ } else {
+ if (hash) {
+ int nspaces;
+
+ /*
+ * 'normal' output, no (ck)sum
+ */
+ print_flags |= PRINT_NORMAL;
+ nspaces = 1;
+
+ p_cksum = buf;
+ p_filename = strchr(buf, ' ');
+ if (p_filename == NULL) {
+ if (check_warn)
+ warnx("no filename in %s? "
+ "Skipping...", buf);
+ rval = 1;
+ continue;
+ }
+ while (isspace((int)*++p_filename))
+ nspaces++;
+ l_filename = strlen(p_filename);
+ l_cksum = p_filename - buf - nspaces;
+ } else {
+ /*
+ * sum/cksum output format
+ */
+ p_cksum = buf;
+ s=strchr(p_cksum, ' ');
+ if (s == NULL) {
+ if (check_warn)
+ warnx("bogus format: %s."
+ " Skipping...",
+ buf);
+ rval = 1;
+ continue;
+ }
+ l_cksum = s - p_cksum;
+
+ p_filename = strrchr(buf, ' ');
+ if (p_filename == NULL) {
+ if (check_warn)
+ warnx("no filename in %s?"
+ " Skipping...",
+ buf);
+ rval = 1;
+ continue;
+ }
+ p_filename++;
+ l_filename = strlen(p_filename);
+ }
+ }
+
+ strlcpy(filename, p_filename, l_filename+1);
+ strlcpy(cksum, p_cksum, l_cksum+1);
+
+ if (hash) {
+ if (access(filename, R_OK) == 0
+ && strcmp(cksum, hash->filefunc(filename, NULL)) == 0)
+ ok = 1;
+ else
+ ok = 0;
+ } else {
+ if ((fd = open(filename, O_RDONLY, 0)) < 0) {
+ if (check_warn)
+ warn("%s", filename);
+ rval = 1;
+ ok = 0;
+ } else {
+ if (cfncn(fd, &val, &len))
+ ok = 0;
+ else {
+ u_int32_t should_val;
+
+ should_val =
+ strtoul(cksum, NULL, 10);
+ if (val == should_val)
+ ok = 1;
+ else
+ ok = 0;
+ }
+ close(fd);
+ }
+ }
+
+ if (! ok) {
+ if (hash)
+ printf("(%s) ", hash->hashname);
+ printf("%s: FAILED\n", filename);
+ badcnt++;
+ }
+ cnt++;
+
+ }
+ fclose(f);
+
+ if (badcnt > 0)
+ rval = 1;
+
+ } else {
+ /*
+ * Calculate checksums
+ */
+
+ fd = STDIN_FILENO;
+ fn = NULL;
+ rval = 0;
+ do {
+ if (*argv) {
+ fn = *argv++;
+ if (hash != NULL) {
+ if (hash_digest_file(fn, hash, print_flags)) {
+ warn("%s", fn);
+ rval = 1;
+ }
+ continue;
+ }
+ if ((fd = open(fn, O_RDONLY, 0)) < 0) {
+ warn("%s", fn);
+ rval = 1;
+ continue;
+ }
+ } else if (hash && !nohashstdin) {
+ hash->filterfunc(pflag);
+ }
+
+ if (hash == NULL) {
+ if (cfncn(fd, &val, &len)) {
+ warn("%s", fn ? fn : "stdin");
+ rval = 1;
+ } else
+ pfncn(fn, val, len);
+ (void)close(fd);
+ }
+ } while (*argv);
+ }
+ exit(rval);
+}
+
+static int
+hash_digest_file(char *fn, const struct hash *hash, int flags)
+{
+ char *cp;
+
+ cp = hash->filefunc(fn, NULL);
+ if (cp == NULL)
+ return 1;
+
+ if (flags & PRINT_QUIET)
+ printf("%s\n", cp);
+ else if (flags & PRINT_NORMAL)
+ printf("%s %s\n", cp, fn);
+ else
+ printf("%s (%s) = %s\n", hash->hashname, fn, cp);
+
+ free(cp);
+
+ return 0;
+}
+
+static void
+requirehash(const char *flg)
+{
+ warnx("%s flag requires `-a algorithm'", flg);
+ usage();
+}
+
+static void
+usage(void)
+{
+ const char fileargs[] = "[file ... | -c [-w] [sumfile]]";
+ const char sumargs[] = "[-n] [-a algorithm [-ptx] [-s string]] [-o 1|2]";
+ const char hashargs[] = "[-nptx] [-s string]";
+
+ (void)fprintf(stderr, "usage: cksum %s\n %s\n",
+ sumargs, fileargs);
+ (void)fprintf(stderr, " sum %s\n %s\n",
+ sumargs, fileargs);
+ (void)fprintf(stderr, " md2 %s %s\n", hashargs, fileargs);
+ (void)fprintf(stderr, " md4 %s %s\n", hashargs, fileargs);
+ (void)fprintf(stderr, " md5 %s %s\n", hashargs, fileargs);
+ (void)fprintf(stderr, " rmd160 %s %s\n", hashargs, fileargs);
+ (void)fprintf(stderr, " sha1 %s %s\n", hashargs, fileargs);
+ exit(1);
+}
diff --git a/usr.bin/cksum/crc.c b/usr.bin/cksum/crc.c
new file mode 100644
index 0000000..b3979db
--- /dev/null
+++ b/usr.bin/cksum/crc.c
@@ -0,0 +1,162 @@
+/* $NetBSD: crc.c,v 1.19 2014/10/29 18:09:35 uebayasi Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * James W. Williams of NASA Goddard Space Flight Center.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)crc.c 8.1 (Berkeley) 6/17/93";
+#else
+__RCSID("$NetBSD: crc.c,v 1.19 2014/10/29 18:09:35 uebayasi Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+static const u_int32_t crctab[] = {
+ 0x0,
+ 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
+ 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6,
+ 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
+ 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac,
+ 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f,
+ 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a,
+ 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
+ 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58,
+ 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033,
+ 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe,
+ 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
+ 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4,
+ 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
+ 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5,
+ 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
+ 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07,
+ 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c,
+ 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1,
+ 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
+ 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b,
+ 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698,
+ 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d,
+ 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
+ 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f,
+ 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
+ 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80,
+ 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
+ 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a,
+ 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629,
+ 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c,
+ 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
+ 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e,
+ 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65,
+ 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8,
+ 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
+ 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2,
+ 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
+ 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74,
+ 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
+ 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21,
+ 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a,
+ 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087,
+ 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
+ 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d,
+ 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce,
+ 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb,
+ 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
+ 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09,
+ 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
+ 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf,
+ 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
+};
+
+/*
+ * Compute a POSIX 1003.2 checksum. This routine has been broken out so that
+ * other programs can use it. It takes a file descriptor to read from and
+ * locations to store the crc and the number of bytes read. It returns 0 on
+ * success and 1 on failure. Errno is set on failure.
+ */
+int
+crc(int fd, u_int32_t *cval, off_t *clen)
+{
+ u_char *p;
+ ssize_t nr;
+ u_int32_t thecrc;
+ off_t len;
+ u_char buf[16 * 1024];
+
+#define COMPUTE(var, ch) (var) = (var) << 8 ^ crctab[(var) >> 24 ^ (ch)]
+
+ thecrc = 0;
+ len = 0;
+ while ((nr = read(fd, buf, sizeof(buf))) > 0)
+ for (len += nr, p = buf; nr--; ++p) {
+ COMPUTE(thecrc, *p);
+ }
+ if (nr < 0)
+ return 1;
+
+ *clen = len;
+
+ /* Include the length of the file. */
+ for (; len != 0; len >>= 8) {
+ COMPUTE(thecrc, len & 0xff);
+ }
+
+ *cval = ~thecrc;
+ return 0;
+}
+
+/* These two are rather more useful to the outside world */
+
+uint32_t
+crc_buf(uint32_t thecrc, const void *buf, size_t len)
+{
+ const uint8_t *p = buf;
+
+ for (p = buf; len; p++, len--)
+ COMPUTE(thecrc, *p);
+ return thecrc;
+}
+
+uint32_t
+crc_byte(uint32_t thecrc, unsigned int byte_val)
+{
+ COMPUTE(thecrc, byte_val & 0xff);
+ return thecrc;
+}
diff --git a/usr.bin/cksum/crc_extern.h b/usr.bin/cksum/crc_extern.h
new file mode 100644
index 0000000..b0c1c33
--- /dev/null
+++ b/usr.bin/cksum/crc_extern.h
@@ -0,0 +1,38 @@
+/* $NetBSD: crc_extern.h,v 1.1 2006/09/04 20:01:10 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ */
+
+__BEGIN_DECLS
+int crc(int, u_int32_t *, off_t *);
+uint32_t crc_buf(uint32_t, const void *, size_t);
+uint32_t crc_byte(uint32_t, unsigned int);
+__END_DECLS
diff --git a/usr.bin/cksum/extern.h b/usr.bin/cksum/extern.h
new file mode 100644
index 0000000..8384110
--- /dev/null
+++ b/usr.bin/cksum/extern.h
@@ -0,0 +1,88 @@
+/* $NetBSD: extern.h,v 1.19 2006/09/04 20:01:10 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#include "crc_extern.h"
+
+__BEGIN_DECLS
+void pcrc(char *, u_int32_t, off_t);
+void psum1(char *, u_int32_t, off_t);
+void psum2(char *, u_int32_t, off_t);
+int csum1(int, u_int32_t *, off_t *);
+int csum2(int, u_int32_t *, off_t *);
+int md5(int, u_int32_t *, u_int32_t *);
+
+void MD2String(const char *);
+void MD2TimeTrial(void);
+void MD2TestSuite(void);
+void MD2Filter(int);
+
+void MD4String(const char *);
+void MD4TimeTrial(void);
+void MD4TestSuite(void);
+void MD4Filter(int);
+
+void MD5String(const char *);
+void MD5TimeTrial(void);
+void MD5TestSuite(void);
+void MD5Filter(int);
+
+void SHA1String(const char *);
+void SHA1TimeTrial(void);
+void SHA1TestSuite(void);
+void SHA1Filter(int);
+
+void RMD160String(const char *);
+void RMD160TimeTrial(void);
+void RMD160TestSuite(void);
+void RMD160Filter(int);
+
+void SHA256_String(const char *);
+void SHA256_TimeTrial(void);
+void SHA256_TestSuite(void);
+void SHA256_Filter(int);
+
+void SHA384_String(const char *);
+void SHA384_TimeTrial(void);
+void SHA384_TestSuite(void);
+void SHA384_Filter(int);
+
+void SHA512_String(const char *);
+void SHA512_TimeTrial(void);
+void SHA512_TestSuite(void);
+void SHA512_Filter(int);
+__END_DECLS
diff --git a/usr.bin/cksum/md2.c b/usr.bin/cksum/md2.c
new file mode 100644
index 0000000..cc80b4a
--- /dev/null
+++ b/usr.bin/cksum/md2.c
@@ -0,0 +1,21 @@
+/* $NetBSD: md2.c,v 1.1 2001/03/20 18:46:26 atatat Exp $ */
+
+#include <md2.h> /* this hash type */
+#include <md5.h> /* the hash we're replacing */
+
+#define HASHTYPE "MD2"
+#define HASHLEN 32
+
+#define MD5Filter MD2Filter
+#define MD5String MD2String
+#define MD5TestSuite MD2TestSuite
+#define MD5TimeTrial MD2TimeTrial
+
+#define MD5Data MD2Data
+#define MD5Init MD2Init
+#define MD5Update MD2Update
+#define MD5End MD2End
+
+#define MD5_CTX MD2_CTX
+
+#include "md5.c"
diff --git a/usr.bin/cksum/md4.c b/usr.bin/cksum/md4.c
new file mode 100644
index 0000000..e27b416
--- /dev/null
+++ b/usr.bin/cksum/md4.c
@@ -0,0 +1,21 @@
+/* $NetBSD: md4.c,v 1.1 2001/03/20 18:46:26 atatat Exp $ */
+
+#include <md4.h> /* this hash type */
+#include <md5.h> /* the hash we're replacing */
+
+#define HASHTYPE "MD4"
+#define HASHLEN 32
+
+#define MD5Filter MD4Filter
+#define MD5String MD4String
+#define MD5TestSuite MD4TestSuite
+#define MD5TimeTrial MD4TimeTrial
+
+#define MD5Data MD4Data
+#define MD5Init MD4Init
+#define MD5Update MD4Update
+#define MD5End MD4End
+
+#define MD5_CTX MD4_CTX
+
+#include "md5.c"
diff --git a/usr.bin/cksum/md5.c b/usr.bin/cksum/md5.c
new file mode 100644
index 0000000..5547771
--- /dev/null
+++ b/usr.bin/cksum/md5.c
@@ -0,0 +1,153 @@
+/* $NetBSD: md5.c,v 1.10 2008/12/29 00:51:29 christos Exp $ */
+
+/*
+ * MDDRIVER.C - test driver for MD2, MD4 and MD5
+ */
+
+/*
+ * Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All
+ * rights reserved.
+ *
+ * RSA Data Security, Inc. makes no representations concerning either
+ * the merchantability of this software or the suitability of this
+ * software for any particular purpose. It is provided "as is"
+ * without express or implied warranty of any kind.
+ *
+ * These notices must be retained in any copies of any part of this
+ * documentation and/or software.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+__RCSID("$NetBSD: md5.c,v 1.10 2008/12/29 00:51:29 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <md5.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+void MD5Filter(int);
+void MD5String(const char *);
+void MD5TestSuite(void);
+void MD5TimeTrial(void);
+
+#ifndef HASHTYPE
+#define HASHTYPE "MD5"
+#endif
+
+#ifndef HASHLEN
+#define HASHLEN 32
+#endif
+
+/*
+ * Length of test block, number of test blocks.
+ */
+#define TEST_BLOCK_LEN 1000
+#define TEST_BLOCK_COUNT 1000
+
+/*
+ * Digests a string and prints the result.
+ */
+void
+MD5String(const char *string)
+{
+ unsigned int len = strlen(string);
+ char buf[HASHLEN + 1];
+
+ printf("%s (\"%s\") = %s\n", HASHTYPE, string,
+ MD5Data((const unsigned char *)string, len, buf));
+}
+
+/*
+ * Measures the time to digest TEST_BLOCK_COUNT TEST_BLOCK_LEN-byte blocks.
+ */
+void
+MD5TimeTrial(void)
+{
+ MD5_CTX context;
+ time_t endTime, startTime;
+ unsigned char block[TEST_BLOCK_LEN];
+ unsigned int i;
+ char *p, buf[HASHLEN + 1];
+
+ printf("%s time trial. Digesting %d %d-byte blocks ...", HASHTYPE,
+ TEST_BLOCK_LEN, TEST_BLOCK_COUNT);
+ fflush(stdout);
+
+ /* Initialize block */
+ for (i = 0; i < TEST_BLOCK_LEN; i++)
+ block[i] = (unsigned char) (i & 0xff);
+
+ /* Start timer */
+ time(&startTime);
+
+ /* Digest blocks */
+ MD5Init(&context);
+ for (i = 0; i < TEST_BLOCK_COUNT; i++)
+ MD5Update(&context, block, TEST_BLOCK_LEN);
+ p = MD5End(&context,buf);
+
+ /* Stop timer */
+ time(&endTime);
+
+ printf(" done\n");
+ printf("Digest = %s\n", p);
+ printf("Time = %ld seconds\n", (long) (endTime - startTime));
+
+ /*
+ * Be careful that endTime-startTime is not zero.
+ * (Bug fix from Ric * Anderson, ric@Artisoft.COM.)
+ */
+ printf("Speed = %lld bytes/second\n",
+ (long long) TEST_BLOCK_LEN * TEST_BLOCK_COUNT /
+ ((endTime - startTime) != 0 ? (endTime - startTime) : 1));
+}
+
+/*
+ * Digests a reference suite of strings and prints the results.
+ */
+void
+MD5TestSuite(void)
+{
+ printf("%s test suite:\n", HASHTYPE);
+
+ MD5String("");
+ MD5String("a");
+ MD5String("abc");
+ MD5String("message digest");
+ MD5String("abcdefghijklmnopqrstuvwxyz");
+ MD5String("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+ MD5String
+ ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+ MD5String
+ ("1234567890123456789012345678901234567890\
+1234567890123456789012345678901234567890");
+}
+
+/*
+ * Digests the standard input and prints the result.
+ */
+void
+MD5Filter(int pipe)
+{
+ MD5_CTX context;
+ size_t len;
+ unsigned char buffer[BUFSIZ];
+ char buf[HASHLEN + 1];
+
+ MD5Init(&context);
+ while ((len = fread(buffer, (size_t)1, (size_t)BUFSIZ, stdin)) > 0) {
+ if (pipe && (len != fwrite(buffer, (size_t)1, len, stdout)))
+ err(1, "stdout");
+ MD5Update(&context, buffer, (unsigned int)len);
+ }
+ printf("%s\n", MD5End(&context,buf));
+}
diff --git a/usr.bin/cksum/print.c b/usr.bin/cksum/print.c
new file mode 100644
index 0000000..03dbd5a
--- /dev/null
+++ b/usr.bin/cksum/print.c
@@ -0,0 +1,76 @@
+/* $NetBSD: print.c,v 1.11 2005/01/12 17:04:35 xtraeme Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)print.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: print.c,v 1.11 2005/01/12 17:04:35 xtraeme Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "extern.h"
+
+void
+pcrc(char *fn, u_int32_t val, off_t len)
+{
+ (void)printf("%lu %lld", (unsigned long)val, (long long)len);
+ if (fn)
+ (void)printf(" %s", fn);
+ (void)printf("\n");
+}
+
+void
+psum1(char *fn, u_int32_t val, off_t len)
+{
+ (void)printf("%lu %lld", (unsigned long)val,
+ (long long)(len + 1023) / 1024);
+ if (fn)
+ (void)printf(" %s", fn);
+ (void)printf("\n");
+}
+
+void
+psum2(char *fn, u_int32_t val, off_t len)
+{
+ (void)printf("%lu %lld", (unsigned long)val,
+ (long long)(len + 511) / 512);
+ if (fn)
+ (void)printf(" %s", fn);
+ (void)printf("\n");
+}
diff --git a/usr.bin/cksum/rmd160.c b/usr.bin/cksum/rmd160.c
new file mode 100644
index 0000000..f04a58d
--- /dev/null
+++ b/usr.bin/cksum/rmd160.c
@@ -0,0 +1,25 @@
+/* $NetBSD: rmd160.c,v 1.3 2006/10/30 20:22:54 christos Exp $ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <rmd160.h> /* this hash type */
+#include <md5.h> /* the hash we're replacing */
+
+#define HASHTYPE "RMD160"
+#define HASHLEN 40
+
+#define MD5Filter RMD160Filter
+#define MD5String RMD160String
+#define MD5TestSuite RMD160TestSuite
+#define MD5TimeTrial RMD160TimeTrial
+
+#define MD5Data RMD160Data
+#define MD5Init RMD160Init
+#define MD5Update RMD160Update
+#define MD5End RMD160End
+
+#define MD5_CTX RMD160_CTX
+
+#include "md5.c"
diff --git a/usr.bin/cksum/sha1.c b/usr.bin/cksum/sha1.c
new file mode 100644
index 0000000..221a6e0
--- /dev/null
+++ b/usr.bin/cksum/sha1.c
@@ -0,0 +1,21 @@
+/* $NetBSD: sha1.c,v 1.1 2001/03/20 18:46:27 atatat Exp $ */
+
+#include <sha1.h> /* this hash type */
+#include <md5.h> /* the hash we're replacing */
+
+#define HASHTYPE "SHA1"
+#define HASHLEN 40
+
+#define MD5Filter SHA1Filter
+#define MD5String SHA1String
+#define MD5TestSuite SHA1TestSuite
+#define MD5TimeTrial SHA1TimeTrial
+
+#define MD5Data SHA1Data
+#define MD5Init SHA1Init
+#define MD5Update SHA1Update
+#define MD5End SHA1End
+
+#define MD5_CTX SHA1_CTX
+
+#include "md5.c"
diff --git a/usr.bin/cksum/sha256.c b/usr.bin/cksum/sha256.c
new file mode 100644
index 0000000..41f272e
--- /dev/null
+++ b/usr.bin/cksum/sha256.c
@@ -0,0 +1,25 @@
+/* $NetBSD: sha256.c,v 1.3 2006/10/30 20:22:54 christos Exp $ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sha2.h> /* this hash type */
+#include <md5.h> /* the hash we're replacing */
+
+#define HASHTYPE "SHA256"
+#define HASHLEN 64
+
+#define MD5Filter SHA256_Filter
+#define MD5String SHA256_String
+#define MD5TestSuite SHA256_TestSuite
+#define MD5TimeTrial SHA256_TimeTrial
+
+#define MD5Data SHA256_Data
+#define MD5Init SHA256_Init
+#define MD5Update SHA256_Update
+#define MD5End SHA256_End
+
+#define MD5_CTX SHA256_CTX
+
+#include "md5.c"
diff --git a/usr.bin/cksum/sha384.c b/usr.bin/cksum/sha384.c
new file mode 100644
index 0000000..845988b
--- /dev/null
+++ b/usr.bin/cksum/sha384.c
@@ -0,0 +1,25 @@
+/* $NetBSD: sha384.c,v 1.3 2006/10/30 20:22:54 christos Exp $ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sha2.h> /* this hash type */
+#include <md5.h> /* the hash we're replacing */
+
+#define HASHTYPE "SHA384"
+#define HASHLEN 96
+
+#define MD5Filter SHA384_Filter
+#define MD5String SHA384_String
+#define MD5TestSuite SHA384_TestSuite
+#define MD5TimeTrial SHA384_TimeTrial
+
+#define MD5Data SHA384_Data
+#define MD5Init SHA384_Init
+#define MD5Update SHA384_Update
+#define MD5End SHA384_End
+
+#define MD5_CTX SHA384_CTX
+
+#include "md5.c"
diff --git a/usr.bin/cksum/sha512.c b/usr.bin/cksum/sha512.c
new file mode 100644
index 0000000..69dae77
--- /dev/null
+++ b/usr.bin/cksum/sha512.c
@@ -0,0 +1,25 @@
+/* $NetBSD: sha512.c,v 1.3 2006/10/30 20:22:54 christos Exp $ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sha2.h> /* this hash type */
+#include <md5.h> /* the hash we're replacing */
+
+#define HASHTYPE "SHA512"
+#define HASHLEN 128
+
+#define MD5Filter SHA512_Filter
+#define MD5String SHA512_String
+#define MD5TestSuite SHA512_TestSuite
+#define MD5TimeTrial SHA512_TimeTrial
+
+#define MD5Data SHA512_Data
+#define MD5Init SHA512_Init
+#define MD5Update SHA512_Update
+#define MD5End SHA512_End
+
+#define MD5_CTX SHA512_CTX
+
+#include "md5.c"
diff --git a/usr.bin/cksum/sum1.c b/usr.bin/cksum/sum1.c
new file mode 100644
index 0000000..22e466e
--- /dev/null
+++ b/usr.bin/cksum/sum1.c
@@ -0,0 +1,76 @@
+/* $NetBSD: sum1.c,v 1.13 2005/02/05 00:13:34 simonb Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)sum1.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: sum1.c,v 1.13 2005/02/05 00:13:34 simonb Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+int
+csum1(int fd, u_int32_t *cval, off_t *clen)
+{
+ off_t total;
+ int nr;
+ u_int thecrc;
+ u_char *p;
+ u_char buf[8192];
+
+ /*
+ * 16-bit checksum, rotating right before each addition;
+ * overflow is discarded.
+ */
+ thecrc = total = 0;
+ while ((nr = read(fd, buf, sizeof(buf))) > 0)
+ for (total += nr, p = buf; nr--; ++p) {
+ if (thecrc & 1)
+ thecrc |= 0x10000;
+ thecrc = ((thecrc >> 1) + *p) & 0xffff;
+ }
+ if (nr < 0)
+ return 1;
+
+ *cval = thecrc;
+ *clen = total;
+ return 0;
+}
diff --git a/usr.bin/cksum/sum2.c b/usr.bin/cksum/sum2.c
new file mode 100644
index 0000000..34b3e2e
--- /dev/null
+++ b/usr.bin/cksum/sum2.c
@@ -0,0 +1,80 @@
+/* $NetBSD: sum2.c,v 1.13 2005/02/05 00:13:34 simonb Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)sum2.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: sum2.c,v 1.13 2005/02/05 00:13:34 simonb Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+int
+csum2(int fd, u_int32_t *cval, off_t *clen)
+{
+ u_int32_t thecrc;
+ off_t total;
+ int nr;
+ u_char *p;
+ u_char buf[8192];
+
+ /*
+ * Draft 8 POSIX 1003.2:
+ *
+ * s = sum of all bytes
+ * r = s % 2^16 + (s % 2^32) / 2^16
+ * thecrc = (r % 2^16) + r / 2^16
+ */
+ thecrc = 0;
+ total = 0;
+ while ((nr = read(fd, buf, sizeof(buf))) > 0)
+ for (total += nr, p = buf; nr--; ++p)
+ thecrc += *p;
+ if (nr < 0)
+ return 1;
+
+ thecrc = (thecrc & 0xffff) + (thecrc >> 16);
+ thecrc = (thecrc & 0xffff) + (thecrc >> 16);
+
+ *cval = thecrc;
+ *clen = total;
+ return 0;
+}
diff --git a/usr.bin/cmp/cmp.1 b/usr.bin/cmp/cmp.1
new file mode 100644
index 0000000..e61f62c
--- /dev/null
+++ b/usr.bin/cmp/cmp.1
@@ -0,0 +1,122 @@
+.\" $NetBSD: cmp.1,v 1.13 2017/07/03 21:34:18 wiz Exp $
+.\"
+.\" Copyright (c) 1987, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)cmp.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd December 13, 2016
+.Dt CMP 1
+.Os
+.Sh NAME
+.Nm cmp
+.Nd compare two files
+.Sh SYNOPSIS
+.Nm
+.Op Fl c
+.Op Fl l | Fl s
+.Ar file1 file2
+.Op Ar skip1 Op Ar skip2
+.Sh DESCRIPTION
+The cmp utility compares two files of any type and writes the results
+to the standard output.
+By default,
+.Nm
+is silent if the files are the same; if they differ, the byte
+and line number at which the first difference occurred is reported.
+.Pp
+Bytes and lines are numbered beginning with one.
+.Pp
+The following options are available:
+.Bl -tag -width flag
+.It Fl c
+Do the compare using
+.Xr getc 3
+rather than
+.Xr mmap 2 .
+Combined with the
+.Fl l
+flag, this can be helpful in locating an I/O error in the underlying
+device.
+.It Fl l
+Print the byte number (decimal) and the differing
+byte values (octal) for each difference.
+.It Fl s
+Print nothing for differing files; return exit
+status only.
+.El
+.Pp
+The optional arguments
+.Ar skip1
+and
+.Ar skip2
+are the byte offsets from the beginning of
+.Ar file1
+and
+.Ar file2 ,
+respectively, where the comparison will begin.
+The offset is decimal by default, but may be expressed as an hexadecimal
+or octal value by preceding it with a leading ``0x'' or ``0''.
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Bl -tag -width 4n
+.It 0
+The files are identical.
+.It 1
+The files are different; this includes the case
+where one file is identical to the first part of
+the other.
+In the latter case, if the
+.Fl s
+option has not been specified,
+.Nm
+writes to standard output that EOF was reached in the shorter
+file (before any differences were found).
+.It >1
+An error occurred.
+.El
+.Sh SEE ALSO
+.Xr comm 1 ,
+.Xr diff 1 ,
+.Xr diff3 1 ,
+.Xr sdiff 1
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.At v1 .
diff --git a/usr.bin/cmp/cmp.c b/usr.bin/cmp/cmp.c
new file mode 100644
index 0000000..4301731
--- /dev/null
+++ b/usr.bin/cmp/cmp.c
@@ -0,0 +1,168 @@
+/* $NetBSD: cmp.c,v 1.20 2016/10/30 19:33:49 christos Exp $ */
+
+/*
+ * Copyright (c) 1987, 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1987, 1990, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cmp.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: cmp.c,v 1.20 2016/10/30 19:33:49 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <locale.h>
+
+#include "extern.h"
+
+int lflag, sflag;
+
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb1, sb2;
+ off_t skip1 = 0, skip2 = 0;
+ int ch, fd1, fd2, special;
+ const char *file1, *file2;
+
+ setlocale(LC_ALL, "");
+
+ special = 0;
+ while ((ch = getopt(argc, argv, "cls")) != -1)
+ switch (ch) {
+ case 'c':
+ special = 1; /* careful! Don't use mmap() */
+ break;
+ case 'l': /* print all differences */
+ lflag = 1;
+ break;
+ case 's': /* silent run */
+ sflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argv += optind;
+ argc -= optind;
+
+ if (lflag && sflag)
+ errx(ERR_EXIT, "only one of -l and -s may be specified");
+
+ if (argc < 2 || argc > 4)
+ usage();
+
+ /* Backward compatibility -- handle "-" meaning stdin. */
+ if (strcmp(file1 = argv[0], "-") == 0) {
+ special = 1;
+ fd1 = 0;
+ file1 = "stdin";
+ }
+ else if ((fd1 = open(file1, O_RDONLY, 0)) < 0) {
+ if (!sflag)
+ warn("%s", file1);
+ exit(ERR_EXIT);
+ }
+ if (strcmp(file2 = argv[1], "-") == 0) {
+ if (special)
+ errx(ERR_EXIT,
+ "standard input may only be specified once");
+ special = 1;
+ fd2 = 0;
+ file2 = "stdin";
+ }
+ else if ((fd2 = open(file2, O_RDONLY, 0)) < 0) {
+ if (!sflag)
+ warn("%s", file2);
+ exit(ERR_EXIT);
+ }
+
+ if (argc > 2) {
+ char *ep;
+
+ errno = 0;
+ skip1 = strtoq(argv[2], &ep, 0);
+ if (errno || ep == argv[2])
+ usage();
+
+ if (argc == 4) {
+ skip2 = strtoq(argv[3], &ep, 0);
+ if (errno || ep == argv[3])
+ usage();
+ }
+ }
+
+ if (!special) {
+ if (fstat(fd1, &sb1))
+ err(ERR_EXIT, "%s", file1);
+ if (!S_ISREG(sb1.st_mode))
+ special = 1;
+ else {
+ if (fstat(fd2, &sb2))
+ err(ERR_EXIT, "%s", file2);
+ if (!S_ISREG(sb2.st_mode))
+ special = 1;
+ }
+ }
+
+ if (special)
+ c_special(fd1, file1, skip1, fd2, file2, skip2);
+ else
+ c_regular(fd1, file1, skip1, sb1.st_size,
+ fd2, file2, skip2, sb2.st_size);
+ exit(0);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "Usage: %s [-c] [-l | -s] file1 file2 [skip1 [skip2]]\n",
+ getprogname());
+ exit(ERR_EXIT);
+}
diff --git a/usr.bin/cmp/extern.h b/usr.bin/cmp/extern.h
new file mode 100644
index 0000000..d8a72ed
--- /dev/null
+++ b/usr.bin/cmp/extern.h
@@ -0,0 +1,44 @@
+/* $NetBSD: extern.h,v 1.9 2011/08/29 14:14:11 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.3 (Berkeley) 4/2/94
+ */
+
+#define OK_EXIT 0
+#define DIFF_EXIT 1
+#define ERR_EXIT 2 /* error exit code */
+
+void c_regular(int, const char *, off_t, off_t, int, const char *, off_t, off_t);
+void c_special(int, const char *, off_t, int, const char *, off_t);
+__dead void diffmsg(const char *, const char *, off_t, off_t);
+__dead void eofmsg(const char *, off_t, off_t);
+__dead void errmsg(const char *, off_t, off_t);
+
+extern int lflag, sflag;
diff --git a/usr.bin/cmp/misc.c b/usr.bin/cmp/misc.c
new file mode 100644
index 0000000..3195de9
--- /dev/null
+++ b/usr.bin/cmp/misc.c
@@ -0,0 +1,84 @@
+/* $NetBSD: misc.c,v 1.12 2009/04/11 12:16:12 lukem Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)misc.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: misc.c,v 1.12 2009/04/11 12:16:12 lukem Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "extern.h"
+
+void
+errmsg(const char *file, off_t byte, off_t line)
+{
+ if (lflag)
+ err(ERR_EXIT, "%s: char %lld, line %lld", file,
+ (long long)byte, (long long)line);
+ else
+ err(ERR_EXIT, "%s", file);
+}
+
+void
+eofmsg(const char *file, off_t byte, off_t line)
+{
+ if (!sflag) {
+ if (!lflag)
+ warnx("EOF on %s", file);
+ else {
+ if (line > 0)
+ warnx("EOF on %s: char %lld, line %lld",
+ file, (long long)byte, (long long)line);
+ else
+ warnx("EOF on %s: char %lld",
+ file, (long long)byte);
+ }
+ }
+ exit(DIFF_EXIT);
+}
+
+void
+diffmsg(const char *file1, const char *file2, off_t byte, off_t line)
+{
+ if (!sflag)
+ (void)printf("%s %s differ: char %lld, line %lld\n",
+ file1, file2, (long long)byte, (long long)line);
+ exit(DIFF_EXIT);
+}
diff --git a/usr.bin/cmp/regular.c b/usr.bin/cmp/regular.c
new file mode 100644
index 0000000..63c663f
--- /dev/null
+++ b/usr.bin/cmp/regular.c
@@ -0,0 +1,118 @@
+/* $NetBSD: regular.c,v 1.24 2013/11/20 17:19:14 kleink Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)regular.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: regular.c,v 1.24 2013/11/20 17:19:14 kleink Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "extern.h"
+
+void
+c_regular(int fd1, const char *file1, off_t skip1, off_t len1,
+ int fd2, const char *file2, off_t skip2, off_t len2)
+{
+ u_char ch, *p1, *p2;
+ off_t byte, length, line;
+ int dfound;
+ size_t blk_sz, blk_cnt;
+
+ if (sflag && len1 != len2)
+ exit(1);
+
+ if (skip1 > len1)
+ eofmsg(file1, len1 + 1, 0);
+ len1 -= skip1;
+ if (skip2 > len2)
+ eofmsg(file2, len2 + 1, 0);
+ len2 -= skip2;
+
+ byte = line = 1;
+ dfound = 0;
+ length = MIN(len1, len2);
+ for (blk_sz = 1024 * 1024; length != 0; length -= blk_sz) {
+ if ((uintmax_t)blk_sz > (uintmax_t)length)
+ blk_sz = length;
+ p1 = mmap(NULL, blk_sz, PROT_READ, MAP_FILE|MAP_SHARED,
+ fd1, skip1);
+ if (p1 == MAP_FAILED)
+ goto mmap_failed;
+
+ p2 = mmap(NULL, blk_sz, PROT_READ, MAP_FILE|MAP_SHARED,
+ fd2, skip2);
+ if (p2 == MAP_FAILED) {
+ munmap(p1, blk_sz);
+ goto mmap_failed;
+ }
+
+ blk_cnt = blk_sz;
+ for (; blk_cnt--; ++p1, ++p2, ++byte) {
+ if ((ch = *p1) != *p2) {
+ if (!lflag) {
+ diffmsg(file1, file2, byte, line);
+ /* NOTREACHED */
+ }
+ dfound = 1;
+ (void)printf("%6lld %3o %3o\n",
+ (long long)byte, ch, *p2);
+ }
+ if (ch == '\n')
+ ++line;
+ }
+ munmap(p1 - blk_sz, blk_sz);
+ munmap(p2 - blk_sz, blk_sz);
+ skip1 += blk_sz;
+ skip2 += blk_sz;
+ }
+
+ if (len1 != len2)
+ eofmsg(len1 > len2 ? file2 : file1, byte, line);
+ if (dfound)
+ exit(DIFF_EXIT);
+ return;
+
+mmap_failed:
+ c_special(fd1, file1, skip1, fd2, file2, skip2);
+}
diff --git a/usr.bin/cmp/special.c b/usr.bin/cmp/special.c
new file mode 100644
index 0000000..0b1afc5
--- /dev/null
+++ b/usr.bin/cmp/special.c
@@ -0,0 +1,111 @@
+/* $NetBSD: special.c,v 1.14 2011/11/28 10:10:10 wiz Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)special.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: special.c,v 1.14 2011/11/28 10:10:10 wiz Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "extern.h"
+
+void
+c_special(int fd1, const char *file1, off_t skip1, int fd2, const char *file2, off_t skip2)
+{
+ int ch1, ch2;
+ off_t byte, line;
+ FILE *fp1, *fp2;
+ int dfound;
+
+ dfound = 0;
+ if ((fp1 = fdopen(fd1, "r")) == NULL)
+ err(ERR_EXIT, "%s", file1);
+ if ((fp2 = fdopen(fd2, "r")) == NULL)
+ err(ERR_EXIT, "%s", file2);
+
+ for (byte = line = 1; skip1--; byte++) {
+ ch1 = getc(fp1);
+ if (ch1 == EOF)
+ goto eof;
+ if (ch1 == '\n')
+ line++;
+ }
+ for (byte = line = 1; skip2--; byte++) {
+ ch2 = getc(fp2);
+ if (ch2 == EOF)
+ goto eof;
+ if (ch2 == '\n')
+ line++;
+ }
+ dfound = 0;
+ for (byte = line = 1;; ++byte) {
+ ch1 = getc(fp1);
+ ch2 = getc(fp2);
+ if (ch1 == EOF || ch2 == EOF)
+ break;
+ if (ch1 != ch2) {
+ if (lflag) {
+ dfound = 1;
+ (void)printf("%6lld %3o %3o\n", (long long)byte,
+ ch1, ch2);
+ } else
+ diffmsg(file1, file2, byte, line);
+ /* NOTREACHED */
+ }
+ if (ch1 == '\n')
+ ++line;
+ }
+
+ eof:
+ if (ferror(fp1))
+ errmsg(file1, byte, line);
+ if (ferror(fp2))
+ errmsg(file2, byte, line);
+ if (feof(fp1)) {
+ if (!feof(fp2))
+ eofmsg(file1, byte, line);
+ } else
+ if (feof(fp2))
+ eofmsg(file2, byte, line);
+ (void)fclose(fp1);
+ (void)fclose(fp2);
+ if (dfound)
+ exit(DIFF_EXIT);
+}
diff --git a/usr.bin/comm/comm.1 b/usr.bin/comm/comm.1
new file mode 100644
index 0000000..d304388
--- /dev/null
+++ b/usr.bin/comm/comm.1
@@ -0,0 +1,102 @@
+.\" $NetBSD: comm.1,v 1.12 2014/04/13 01:45:34 snj Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)comm.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt COMM 1
+.Os
+.Sh NAME
+.Nm comm
+.Nd select or reject lines common to two files
+.Sh SYNOPSIS
+.Nm
+.Op Fl 123f
+.Ar file1 file2
+.Sh DESCRIPTION
+The
+.Nm
+utility reads
+.Ar file1
+and
+.Ar file2 ,
+which should be
+sorted lexically, and produces three text
+columns as output: lines only in
+.Ar file1 ;
+lines only in
+.Ar file2 ;
+and lines in both files.
+.Pp
+The filename ``-'' means the standard input.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl 1
+Suppress printing of column 1.
+.It Fl 2
+Suppress printing of column 2.
+.It Fl 3
+Suppress printing of column 3.
+.It Fl f
+Fold case in line comparisons.
+.El
+.Pp
+Each column will have a number of tab characters prepended to it
+equal to the number of lower numbered columns that are being printed.
+For example, if column number two is being suppressed, lines printed
+in column number one will not have any tabs preceding them, and lines
+printed in column number three will have one.
+.Pp
+.Nm
+assumes that the files are lexically sorted; all characters
+participate in line comparisons.
+.\" .Sh ENVIRONMENT
+.\" .Bl -tag -width indent
+.\" .It Ev LANG
+.\" .It Ev LC_ALL
+.\" .It Ev LC_CTYPE
+.\" .It Ev LC_COLLATE
+.\" .It Ev LC_MESSAGES
+.\" .El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr cmp 1 ,
+.Xr diff 1 ,
+.Xr sort 1 ,
+.Xr uniq 1
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/comm/comm.c b/usr.bin/comm/comm.c
new file mode 100644
index 0000000..c1416e2
--- /dev/null
+++ b/usr.bin/comm/comm.c
@@ -0,0 +1,213 @@
+/* $NetBSD: comm.c,v 1.20 2012/09/05 04:01:23 simonb Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Case Larsen.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)comm.c 8.4 (Berkeley) 5/4/95";
+#endif
+__RCSID("$NetBSD: comm.c,v 1.20 2012/09/05 04:01:23 simonb Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define MAXLINELEN (LINE_MAX + 1)
+
+static const char *tabs[] = { "", "\t", "\t\t" };
+
+static FILE *file(const char *);
+static void show(FILE *, const char *, char *);
+__dead static void usage(void);
+static char *getnextln(char *buf, FILE *);
+
+int
+main(int argc, char **argv)
+{
+ int comp, file1done, file2done, read1, read2;
+ int ch, flag1, flag2, flag3;
+ FILE *fp1, *fp2;
+ const char *col1, *col2, *col3, **p;
+ char line1[MAXLINELEN], line2[MAXLINELEN];
+ int (*compare)(const char*,const char*);
+
+ (void)setlocale(LC_ALL, "");
+
+ file1done = file2done = 0;
+ flag1 = flag2 = flag3 = 1;
+ compare = strcoll;
+ while ((ch = getopt(argc, argv, "123f")) != -1)
+ switch(ch) {
+ case '1':
+ flag1 = 0;
+ break;
+ case '2':
+ flag2 = 0;
+ break;
+ case '3':
+ flag3 = 0;
+ break;
+ case 'f':
+ compare = strcasecmp;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 2)
+ usage();
+
+ fp1 = file(argv[0]);
+ fp2 = file(argv[1]);
+
+ /* for each column printed, add another tab offset */
+ p = tabs;
+ col1 = col2 = col3 = NULL;
+ if (flag1)
+ col1 = *p++;
+ if (flag2)
+ col2 = *p++;
+ if (flag3)
+ col3 = *p;
+
+ for (read1 = read2 = 1;;) {
+ /* read next line, check for EOF */
+ if (read1)
+ file1done = !getnextln(line1, fp1);
+ if (read2)
+ file2done = !getnextln(line2, fp2);
+
+ /* if one file done, display the rest of the other file */
+ if (file1done) {
+ if (!file2done && col2)
+ show(fp2, col2, line2);
+ break;
+ }
+ if (file2done) {
+ if (!file1done && col1)
+ show(fp1, col1, line1);
+ break;
+ }
+
+ /* lines are the same */
+ if (!(comp = compare(line1, line2))) {
+ read1 = read2 = 1;
+ if (col3)
+ if (printf("%s%s\n", col3, line1) < 0)
+ break;
+ continue;
+ }
+
+ /* lines are different */
+ if (comp < 0) {
+ read1 = 1;
+ read2 = 0;
+ if (col1)
+ if (printf("%s%s\n", col1, line1) < 0)
+ break;
+ } else {
+ read1 = 0;
+ read2 = 1;
+ if (col2)
+ if (printf("%s%s\n", col2, line2) < 0)
+ break;
+ }
+ }
+
+ if (ferror (stdout) || fclose (stdout) == EOF)
+ err(1, "stdout");
+
+ exit(0);
+}
+
+static void
+show(FILE *fp, const char *offset, char *buf)
+{
+ while (printf("%s%s\n", offset, buf) >= 0 && getnextln(buf, fp))
+ ;
+}
+
+static FILE *
+file(const char *name)
+{
+ FILE *fp;
+
+ if (!strcmp(name, "-"))
+ return (stdin);
+ if ((fp = fopen(name, "r")) == NULL)
+ err(1, "%s", name);
+ return (fp);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: comm [-123f] file1 file2\n");
+ exit(1);
+}
+
+static char *
+getnextln(char *buf, FILE *fp)
+{
+ size_t i = 0;
+ int c;
+
+ while ((c = getc(fp)) != '\n' && c != EOF) {
+ buf[i++] = c;
+
+ if (i >= MAXLINELEN)
+ i--; /* consumes extra characters till newline */
+ }
+
+ if (c == EOF && !i)
+ return NULL;
+
+ buf[i] = 0;
+ return buf;
+}
+
diff --git a/usr.bin/compress/compress.1 b/usr.bin/compress/compress.1
new file mode 100644
index 0000000..84bc47c
--- /dev/null
+++ b/usr.bin/compress/compress.1
@@ -0,0 +1,171 @@
+.\" $NetBSD: compress.1,v 1.16 2017/07/04 06:58:55 wiz Exp $
+.\"
+.\" Copyright (c) 1986, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" James A. Woods, derived from original work by Spencer Thomas
+.\" and Joseph Orost.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)compress.1 8.2 (Berkeley) 4/18/94
+.\"
+.Dd January 23, 2003
+.Dt COMPRESS 1
+.Os
+.Sh NAME
+.Nm compress ,
+.\".Nm uncompress ,
+.Nm uncompress
+.\".Nm zcat
+.Nd compress and expand data
+.Sh SYNOPSIS
+.Nm
+.Op Fl cdfv
+.Op Fl b Ar bits
+.Op Ar
+.Nm uncompress
+.Op Fl cdfv
+.Op Ar
+.\".Nm zcat
+.\".Op Ar
+.Sh DESCRIPTION
+.Nm
+reduces the size of the named files using adaptive Lempel-Ziv coding.
+Each
+.Ar file
+is renamed to the same name plus the extension
+.Dq .Z .
+As many of the modification time, access time, file flags, file mode,
+user ID, and group ID as allowed by permissions are retained in the
+new file.
+If compression would not reduce the size of a
+.Ar file ,
+the file is ignored.
+.Pp
+.Nm uncompress
+restores the compressed files to their original form, renaming the
+files by deleting the
+.Dq .Z
+extension.
+.\".Pp
+.\".Nm Zcat
+.\"is an alias for
+.\".Dq "uncompress -c" .
+.Pp
+If renaming the files would cause files to be overwritten and the standard
+input device is a terminal, the user is prompted (on the standard error
+output) for confirmation.
+If prompting is not possible or confirmation is not received, the files
+are not overwritten.
+.Pp
+If no files are specified, the standard input is compressed or uncompressed
+to the standard output.
+If either the input and output files are not regular files, the checks for
+reduction in size and file overwriting are not performed, the input file is
+not removed, and the attributes of the input file are not retained.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl b
+Specify the
+.Ar bits
+code limit (see below).
+.It Fl c
+Compressed or uncompressed output is written to the standard output.
+No files are modified.
+.It Fl d
+Force decompression.
+.It Fl f
+Force compression of
+.Ar file ,
+even if it is not actually reduced in size.
+Additionally, files are overwritten without prompting for confirmation.
+.It Fl v
+Print the percentage reduction of each file.
+.El
+.Pp
+.Nm
+uses a modified Lempel-Ziv algorithm.
+Common substrings in the file are first replaced by 9-bit codes 257 and up.
+When code 512 is reached, the algorithm switches to 10-bit codes and
+continues to use more bits until the
+limit specified by the
+.Fl b
+flag is reached (the default is 16).
+.Ar Bits
+must be between 9 and 16.
+.Pp
+After the
+.Ar bits
+limit is reached,
+.Nm
+periodically checks the compression ratio.
+If it is increasing,
+.Nm
+continues to use the existing code dictionary.
+However, if the compression ratio decreases,
+.Nm
+discards the table of substrings and rebuilds it from scratch.
+This allows the algorithm to adapt to the next "block" of the file.
+.Pp
+The
+.Fl b
+flag is omitted for
+.Ar uncompress
+since the
+.Ar bits
+parameter specified during compression
+is encoded within the output, along with
+a magic number to ensure that neither decompression of random data nor
+recompression of compressed data is attempted.
+.Pp
+The amount of compression obtained depends on the size of the
+input, the number of
+.Ar bits
+per code, and the distribution of common substrings.
+Typically, text such as source code or English is reduced by 50\-60%.
+Compression is generally much better than that achieved by Huffman
+coding (as used in the historical command pack), or adaptive Huffman
+coding (as used in the historical command compact), and takes less
+time to compute.
+.Sh EXIT STATUS
+.Ex -std compress
+.Sh SEE ALSO
+.Xr zcat 1
+.Rs
+.%A Welch, Terry A.
+.%D June, 1984
+.%T "A Technique for High Performance Data Compression"
+.%J "IEEE Computer"
+.%V 17:6
+.%P pp. 8-19
+.Re
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.3 .
diff --git a/usr.bin/compress/compress.c b/usr.bin/compress/compress.c
new file mode 100644
index 0000000..93fdc76
--- /dev/null
+++ b/usr.bin/compress/compress.c
@@ -0,0 +1,459 @@
+/* $NetBSD: compress.c,v 1.26 2011/08/30 23:08:05 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1992, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)compress.c 8.2 (Berkeley) 1/7/94";
+#else
+__RCSID("$NetBSD: compress.c,v 1.26 2011/08/30 23:08:05 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void compress(const char *, const char *, int);
+static void cwarn(const char *, ...) __printflike(1, 2);
+static void cwarnx(const char *, ...) __printflike(1, 2);
+static void decompress(const char *, const char *, int);
+static int permission(const char *);
+static void setfile(const char *, struct stat *);
+__dead static void usage(int);
+
+extern FILE *zopen(const char *fname, const char *mode, int bits);
+
+static int eval, force, verbose;
+static int isstdout, isstdin;
+
+int
+main(int argc, char **argv)
+{
+ enum {COMPRESS, DECOMPRESS} style = COMPRESS;
+ size_t len;
+ int bits, cat, ch;
+ char *p, newname[MAXPATHLEN];
+
+ if ((p = strrchr(argv[0], '/')) == NULL)
+ p = argv[0];
+ else
+ ++p;
+ if (!strcmp(p, "uncompress"))
+ style = DECOMPRESS;
+ else if (!strcmp(p, "compress"))
+ style = COMPRESS;
+ else if (!strcmp(p, "zcat")) {
+ style = DECOMPRESS;
+ cat = 1;
+ }
+ else
+ errx(1, "unknown program name");
+
+ bits = cat = 0;
+ while ((ch = getopt(argc, argv, "b:cdfv")) != -1)
+ switch(ch) {
+ case 'b':
+ bits = strtol(optarg, &p, 10);
+ if (*p)
+ errx(1, "illegal bit count -- %s", optarg);
+ break;
+ case 'c':
+ cat = 1;
+ break;
+ case 'd': /* Backward compatible. */
+ style = DECOMPRESS;
+ break;
+ case 'f':
+ force = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case '?':
+ default:
+ usage(style == COMPRESS);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ switch(style) {
+ case COMPRESS:
+ isstdout = 1;
+ isstdin = 1;
+ (void)compress("/dev/stdin", "/dev/stdout", bits);
+ break;
+ case DECOMPRESS:
+ isstdout = 1;
+ isstdin = 1;
+ (void)decompress("/dev/stdin", "/dev/stdout", bits);
+ break;
+ }
+ exit (eval);
+ }
+
+ if (cat == 1 && argc > 1)
+ errx(1, "the -c option permits only a single file argument");
+
+ for (; *argv; ++argv) {
+ isstdout = 0;
+ switch(style) {
+ case COMPRESS:
+ if (cat) {
+ isstdout = 1;
+ compress(*argv, "/dev/stdout", bits);
+ break;
+ }
+ if ((p = strrchr(*argv, '.')) != NULL &&
+ !strcmp(p, ".Z")) {
+ cwarnx("%s: name already has trailing .Z",
+ *argv);
+ break;
+ }
+ len = strlen(*argv);
+ if (len > sizeof(newname) - 3) {
+ cwarnx("%s: name too long", *argv);
+ break;
+ }
+ memmove(newname, *argv, len);
+ newname[len] = '.';
+ newname[len + 1] = 'Z';
+ newname[len + 2] = '\0';
+ compress(*argv, newname, bits);
+ break;
+ case DECOMPRESS:
+ len = strlen(*argv);
+ if ((p = strrchr(*argv, '.')) == NULL ||
+ strcmp(p, ".Z")) {
+ if (len > sizeof(newname) - 3) {
+ cwarnx("%s: name too long", *argv);
+ break;
+ }
+ memmove(newname, *argv, len);
+ newname[len] = '.';
+ newname[len + 1] = 'Z';
+ newname[len + 2] = '\0';
+ decompress(newname,
+ cat ? "/dev/stdout" : *argv, bits);
+ if (cat)
+ isstdout = 1;
+ } else {
+ if (len - 2 > sizeof(newname) - 1) {
+ cwarnx("%s: name too long", *argv);
+ break;
+ }
+ memmove(newname, *argv, len - 2);
+ newname[len - 2] = '\0';
+ decompress(*argv,
+ cat ? "/dev/stdout" : newname, bits);
+ if (cat)
+ isstdout = 1;
+ }
+ break;
+ }
+ }
+ exit (eval);
+}
+
+static void
+compress(const char *in, const char *out, int bits)
+{
+ size_t nr;
+ struct stat isb, sb;
+ const char *error = NULL;
+ FILE *ifp, *ofp;
+ int exists, isreg, oreg;
+ u_char buf[BUFSIZ];
+
+ if (!isstdout) {
+ exists = !stat(out, &sb);
+ if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
+ return;
+ oreg = !exists || S_ISREG(sb.st_mode);
+ } else
+ oreg = 0;
+
+ ifp = ofp = NULL;
+ if ((ifp = fopen(in, "r")) == NULL) {
+ cwarn("%s", in);
+ return;
+ }
+
+ if (!isstdin) {
+ if (stat(in, &isb)) { /* DON'T FSTAT! */
+ cwarn("%s", in);
+ goto err;
+ }
+ if (!S_ISREG(isb.st_mode))
+ isreg = 0;
+ else
+ isreg = 1;
+ } else
+ isreg = 0;
+
+ if ((ofp = zopen(out, "w", bits)) == NULL) {
+ cwarn("%s", out);
+ goto err;
+ }
+ oreg <<= 1;
+ while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
+ if (fwrite(buf, 1, nr, ofp) != nr) {
+ cwarn("%s", out);
+ goto err;
+ }
+
+ if (ferror(ifp))
+ error = in;
+ if (fclose(ifp))
+ if (error == NULL)
+ error = in;
+ if (fclose(ofp))
+ if (error == NULL)
+ error = out;
+ ifp = NULL;
+ ofp = NULL;
+ if (error) {
+ cwarn("%s", error);
+ goto err;
+ }
+
+ if (isreg && oreg) {
+ if (stat(out, &sb)) {
+ cwarn("%s", out);
+ goto err;
+ }
+
+ if (!force && sb.st_size >= isb.st_size) {
+ if (verbose)
+ (void)printf("%s: file would grow; left unmodified\n", in);
+ goto err;
+ }
+
+ setfile(out, &isb);
+
+ if (unlink(in))
+ cwarn("%s", in);
+
+ if (verbose) {
+ (void)printf("%s: ", out);
+ if (isb.st_size > sb.st_size)
+ (void)printf("%.0f%% compression\n",
+ ((double)sb.st_size / isb.st_size) * 100.0);
+ else
+ (void)printf("%.0f%% expansion\n",
+ ((double)isb.st_size / sb.st_size) * 100.0);
+ }
+ }
+ return;
+
+err: if (ofp)
+ (void)fclose(ofp);
+ if (oreg == 2)
+ (void)unlink(out);
+ if (ifp)
+ (void)fclose(ifp);
+}
+
+static void
+decompress(const char *in, const char *out, int bits)
+{
+ size_t nr;
+ struct stat sb;
+ FILE *ifp, *ofp;
+ int exists, isreg, oreg;
+ u_char buf[BUFSIZ];
+
+ if (!isstdout) {
+ exists = !stat(out, &sb);
+ if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
+ return;
+ oreg = !exists || S_ISREG(sb.st_mode);
+ } else
+ oreg = 0;
+
+ ifp = ofp = NULL;
+ if ((ofp = fopen(out, "w")) == NULL) {
+ cwarn("%s", out);
+ return;
+ }
+
+ if ((ifp = zopen(in, "r", bits)) == NULL) {
+ cwarn("%s", in);
+ goto err;
+ }
+ if (!isstdin) {
+ if (stat(in, &sb)) {
+ cwarn("%s", in);
+ goto err;
+ }
+ if (!S_ISREG(sb.st_mode))
+ isreg = 0;
+ else
+ isreg = 1;
+ } else
+ isreg = 0;
+
+ oreg <<= 1;
+ while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
+ if (fwrite(buf, 1, nr, ofp) != nr) {
+ cwarn("%s", out);
+ goto err;
+ }
+
+ if (ferror(ifp)) {
+ cwarn("%s", in);
+ goto err;
+ }
+ if (fclose(ifp)) {
+ ifp = NULL;
+ cwarn("%s", in);
+ goto err;
+ }
+ ifp = NULL;
+
+ if (fclose(ofp)) {
+ ofp = NULL;
+ cwarn("%s", out);
+ goto err;
+ }
+
+ if (isreg && oreg) {
+ setfile(out, &sb);
+
+ if (unlink(in))
+ cwarn("%s", in);
+ }
+ return;
+
+err: if (ofp)
+ (void)fclose(ofp);
+ if (oreg == 2)
+ (void)unlink(out);
+ if (ifp)
+ (void)fclose(ifp);
+}
+
+static void
+setfile(const char *name, struct stat *fs)
+{
+ static struct timeval tv[2];
+
+ fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
+
+ TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
+ TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
+ if (utimes(name, tv))
+ cwarn("utimes: %s", name);
+
+ /*
+ * Changing the ownership probably won't succeed, unless we're root
+ * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting
+ * the mode; current BSD behavior is to remove all setuid bits on
+ * chown. If chown fails, lose setuid/setgid bits.
+ */
+ if (chown(name, fs->st_uid, fs->st_gid)) {
+ if (errno != EPERM)
+ cwarn("chown: %s", name);
+ fs->st_mode &= ~(S_ISUID|S_ISGID);
+ }
+ if (chmod(name, fs->st_mode))
+ cwarn("chown: %s", name);
+
+ /*
+ * Restore the file's flags. However, do this only if the original
+ * file had any flags set; this avoids a warning on file-systems that
+ * do not support flags.
+ */
+ if (fs->st_flags != 0 && chflags(name, fs->st_flags))
+ cwarn("chflags: %s", name);
+}
+
+static int
+permission(const char *fname)
+{
+ int ch, first;
+
+ if (!isatty(fileno(stderr)))
+ return (0);
+ (void)fprintf(stderr, "overwrite %s? ", fname);
+ first = ch = getchar();
+ while (ch != '\n' && ch != EOF)
+ ch = getchar();
+ return (first == 'y');
+}
+
+static void
+usage(int iscompress)
+{
+ if (iscompress)
+ (void)fprintf(stderr,
+ "usage: compress [-cdfv] [-b bits] [file ...]\n");
+ else
+ (void)fprintf(stderr,
+ "usage: uncompress [-cdfv] [-b bits] [file ...]\n");
+ exit(1);
+}
+
+static void
+cwarnx(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vwarnx(fmt, ap);
+ va_end(ap);
+ eval = 1;
+}
+
+static void
+cwarn(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vwarn(fmt, ap);
+ va_end(ap);
+ eval = 1;
+}
diff --git a/usr.bin/compress/doc/NOTES b/usr.bin/compress/doc/NOTES
new file mode 100644
index 0000000..26181bf
--- /dev/null
+++ b/usr.bin/compress/doc/NOTES
@@ -0,0 +1,139 @@
+From: James A. Woods <jaw@eos.arc.nasa.gov>
+
+>From vn Fri Dec 2 18:05:27 1988
+Subject: Re: Looking for C source for RSA
+Newsgroups: sci.crypt
+
+# Illegitimi noncarborundum
+
+Patents are a tar pit.
+
+A good case can be made that most are just a license to sue, and nothing
+is illegal until a patent is upheld in court.
+
+For example, if you receive netnews by means other than 'nntp',
+these very words are being modulated by 'compress',
+a variation on the patented Lempel-Ziv-Welch algorithm.
+
+Original Ziv-Lempel is patent number 4,464,650, and the more powerful
+LZW method is #4,558,302. Yet despite any similarities between 'compress'
+and LZW (the public-domain 'compress' code was designed and given to the
+world before the ink on the Welch patent was dry), no attorneys from Sperry
+(the assignee) have asked you to unplug your Usenet connection.
+
+Why? I can't speak for them, but it is possible the claims are too broad,
+or, just as bad, not broad enough. ('compress' does things not mentioned
+in the Welch patent.) Maybe they realize that they can commercialize
+LZW better by selling hardware implementations rather than by licensing
+software. Again, the LZW software delineated in the patent is *not*
+the same as that of 'compress'.
+
+At any rate, court-tested software patents are a different animal;
+corporate patents in a portfolio are usually traded like baseball cards
+to shut out small fry rather than actually be defended before
+non-technical juries. Perhaps RSA will undergo this test successfully,
+although the grant to "exclude others from making, using, or selling"
+the invention would then only apply to the U.S. (witness the
+Genentech patent of the TPA molecule in the U.S. but struck down
+in Great Britain as too broad.)
+
+The concept is still exotic for those who learned in school the rule of thumb
+that one may patent "apparatus" but not an "idea".
+Apparently this all changed in Diamond v. Diehr (1981) when the U. S. Supreme
+Court reversed itself.
+
+Scholars should consult the excellent article in the Washington and Lee
+Law Review (fall 1984, vol. 41, no. 4) by Anthony and Colwell for a
+comprehensive survey of an area which will remain murky for some time.
+
+Until the dust clears, how you approach ideas which are patented depends
+on how paranoid you are of a legal onslaught. Arbitrary? Yes. But
+the patent bar the CCPA (Court of Customs and Patent Appeals)
+thanks you for any uncertainty as they, at least, stand to gain
+from any trouble.
+
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+From: James A. Woods <jaw@eos.arc.nasa.gov>
+Subject: Re: Looking for C source for RSA (actually 'compress' patents)
+
+ In article <2042@eos.UUCP> you write:
+ >The concept is still exotic for those who learned in school the rule of thumb
+ >that one may patent "apparatus" but not an "idea".
+
+A rule of thumb that has never been completely valid, as any chemical
+engineer can tell you. (Chemical processes were among the earliest patents,
+as I recall.)
+
+ ah yes -- i date myself when relaying out-of-date advice from elderly
+ attorneys who don't even specialize in patents. one other interesting
+ class of patents include the output of optical lens design programs,
+ which yield formulae which can then fairly directly can be molded
+ into glass. although there are restrictions on patenting equations,
+ the "embedded systems" seem to fly past the legal gauntlets.
+
+ anyway, i'm still learning about intellectual property law after
+ several conversations from a unisys (nee sperry) lawyer re 'compress'.
+
+ it's more complicated than this, but they're letting (oral
+ communication only) software versions of 'compress' slide
+ as far as licensing fees go. this includes 'arc', 'stuffit',
+ and other commercial wrappers for 'compress'. yet they are
+ signing up licensees for hardware chips. hewlett-packard
+ supposedly has an active vlsi project, and unisys has
+ board-level lzw-based tape controllers. (to build lzw into
+ a disk controller would be strange, as you'd have to build
+ in a filesystem too!)
+
+ it's byzantine
+ that unisys is in a tiff with hp regarding the patents,
+ after discovering some sort of "compress" button on some
+ hp terminal product. why? well, professor abraham lempel jumped
+ from being department chairman of computer science at technion in
+ israel to sperry (where he got the first patent), but then to work
+ at hewlett-packard on sabbatical. the second welch patent
+ is only weakly derivative of the first, so they want chip
+ licenses and hp relented. however, everyone agrees something
+ like the current unix implementation is the way to go with
+ software, so hp (and ucb) long ago asked spencer thomas and i to sign
+ off on copyright permission (although they didn't need to, it being pd).
+ lempel, hp, and unisys grumbles they can't make money off the
+ software since a good free implementation (not the best --
+ i have more ideas!) escaped via usenet. (lempel's own pascal
+ code was apparently horribly slow.)
+ i don't follow the ibm 'arc' legal bickering; my impression
+ is that the pc folks are making money off the archiver/wrapper
+ look/feel of the thing [if ms-dos can be said to have a look and feel].
+
+ now where is telebit with the compress firmware? in a limbo
+ netherworld, probably, with sperry still welcoming outfits
+ to sign patent licenses, a common tactic to bring other small fry
+ into the fold. the guy who crammed 12-bit compess into the modem
+ there left. also what is transpiring with 'compress' and sys 5 rel 4?
+ beats me, but if sperry got a hold of them on these issues,
+ at&t would likely re-implement another algorithm if they
+ thought 'compress' infringes. needful to say, i don't think
+ it does after the abovementioned legal conversation.
+ my own beliefs on whether algorithms should be patentable at all
+ change with the weather. if the courts finally nail down
+ patent protection for algorithms, academic publication in
+ textbooks will be somewhat at odds with the engineering world,
+ where the textbook codes will simply be a big tease to get
+ money into the patent holder coffers...
+
+ oh, if you implement lzw from the patent, you won't get
+ good rates because it doesn't mention adaptive table reset,
+ lack thereof being *the* serious deficiency of thomas' first version.
+
+ now i know that patent law generally protects against independent
+ re-invention (like the 'xor' hash function pleasantly mentioned
+ in the patent [but not the paper]).
+ but the upshot is that if anyone ever wanted to sue us,
+ we're partially covered with
+ independently-developed twists, plus the fact that some of us work
+ in a bureacratic morass (as contractor to a public agency in my case).
+
+ quite a mess, huh? i've wanted to tell someone this stuff
+ for a long time, for posterity if nothing else.
+
+james
+
diff --git a/usr.bin/compress/doc/README b/usr.bin/compress/doc/README
new file mode 100644
index 0000000..6803287
--- /dev/null
+++ b/usr.bin/compress/doc/README
@@ -0,0 +1,283 @@
+
+ @(#)README 8.1 (Berkeley) 6/9/93
+
+Compress version 4.0 improvements over 3.0:
+ o compress() speedup (10-50%) by changing division hash to xor
+ o decompress() speedup (5-10%)
+ o Memory requirements reduced (3-30%)
+ o Stack requirements reduced to less than 4kb
+ o Removed 'Big+Fast' compress code (FBITS) because of compress speedup
+ o Portability mods for Z8000 and PC/XT (but not zeus 3.2)
+ o Default to 'quiet' mode
+ o Unification of 'force' flags
+ o Manual page overhaul
+ o Portability enhancement for M_XENIX
+ o Removed text on #else and #endif
+ o Added "-V" switch to print version and options
+ o Added #defines for SIGNED_COMPARE_SLOW
+ o Added Makefile and "usermem" program
+ o Removed all floating point computations
+ o New programs: [deleted]
+
+The "usermem" script attempts to determine the maximum process size. Some
+editing of the script may be necessary (see the comments). [It should work
+fine on 4.3 bsd.] If you can't get it to work at all, just create file
+"USERMEM" containing the maximum process size in decimal.
+
+The following preprocessor symbols control the compilation of "compress.c":
+
+ o USERMEM Maximum process memory on the system
+ o SACREDMEM Amount to reserve for other proceses
+ o SIGNED_COMPARE_SLOW Unsigned compare instructions are faster
+ o NO_UCHAR Don't use "unsigned char" types
+ o BITS Overrules default set by USERMEM-SACREDMEM
+ o vax Generate inline assembler
+ o interdata Defines SIGNED_COMPARE_SLOW
+ o M_XENIX Makes arrays < 65536 bytes each
+ o pdp11 BITS=12, NO_UCHAR
+ o z8000 BITS=12
+ o pcxt BITS=12
+ o BSD4_2 Allow long filenames ( > 14 characters) &
+ Call setlinebuf(stderr)
+
+The difference "usermem-sacredmem" determines the maximum BITS that can be
+specified with the "-b" flag.
+
+memory: at least BITS
+------ -- ----- ----
+ 433,484 16
+ 229,600 15
+ 127,536 14
+ 73,464 13
+ 0 12
+
+The default is BITS=16.
+
+The maximum bits can be overrulled by specifying "-DBITS=bits" at
+compilation time.
+
+WARNING: files compressed on a large machine with more bits than allowed by
+a version of compress on a smaller machine cannot be decompressed! Use the
+"-b12" flag to generate a file on a large machine that can be uncompressed
+on a 16-bit machine.
+
+The output of compress 4.0 is fully compatible with that of compress 3.0.
+In other words, the output of compress 4.0 may be fed into uncompress 3.0 or
+the output of compress 3.0 may be fed into uncompress 4.0.
+
+The output of compress 4.0 not compatible with that of
+compress 2.0. However, compress 4.0 still accepts the output of
+compress 2.0. To generate output that is compatible with compress
+2.0, use the undocumented "-C" flag.
+
+ -from mod.sources, submitted by vax135!petsd!joe (Joe Orost), 8/1/85
+--------------------------------
+
+Enclosed is compress version 3.0 with the following changes:
+
+1. "Block" compression is performed. After the BITS run out, the
+ compression ratio is checked every so often. If it is decreasing,
+ the table is cleared and a new set of substrings are generated.
+
+ This makes the output of compress 3.0 not compatible with that of
+ compress 2.0. However, compress 3.0 still accepts the output of
+ compress 2.0. To generate output that is compatible with compress
+ 2.0, use the undocumented "-C" flag.
+
+2. A quiet "-q" flag has been added for use by the news system.
+
+3. The character chaining has been deleted and the program now uses
+ hashing. This improves the speed of the program, especially
+ during decompression. Other speed improvements have been made,
+ such as using putc() instead of fwrite().
+
+4. A large table is used on large machines when a relatively small
+ number of bits is specified. This saves much time when compressing
+ for a 16-bit machine on a 32-bit virtual machine. Note that the
+ speed improvement only occurs when the input file is > 30000
+ characters, and the -b BITS is less than or equal to the cutoff
+ described below.
+
+Most of these changes were made by James A. Woods (ames!jaw). Thank you
+James!
+
+To compile compress:
+
+ cc -O -DUSERMEM=usermem -o compress compress.c
+
+Where "usermem" is the amount of physical user memory available (in bytes).
+If any physical memory is to be reserved for other processes, put in
+"-DSACREDMEM sacredmem", where "sacredmem" is the amount to be reserved.
+
+The difference "usermem-sacredmem" determines the maximum BITS that can be
+specified, and the cutoff bits where the large+fast table is used.
+
+memory: at least BITS cutoff
+------ -- ----- ---- ------
+ 4,718,592 16 13
+ 2,621,440 16 12
+ 1,572,864 16 11
+ 1,048,576 16 10
+ 631,808 16 --
+ 329,728 15 --
+ 178,176 14 --
+ 99,328 13 --
+ 0 12 --
+
+The default memory size is 750,000 which gives a maximum BITS=16 and no
+large+fast table.
+
+The maximum bits can be overruled by specifying "-DBITS=bits" at
+compilation time.
+
+If your machine doesn't support unsigned characters, define "NO_UCHAR"
+when compiling.
+
+If your machine has "int" as 16-bits, define "SHORT_INT" when compiling.
+
+After compilation, move "compress" to a standard executable location, such
+as /usr/local. Then:
+ cd /usr/local
+ ln compress uncompress
+ ln compress zcat
+
+On machines that have a fixed stack size (such as Perkin-Elmer), set the
+stack to at least 12kb. ("setstack compress 12" on Perkin-Elmer).
+
+Next, install the manual (compress.l).
+ cp compress.l /usr/man/manl
+ cd /usr/man/manl
+ ln compress.l uncompress.l
+ ln compress.l zcat.l
+
+ - or -
+
+ cp compress.l /usr/man/man1/compress.1
+ cd /usr/man/man1
+ ln compress.1 uncompress.1
+ ln compress.1 zcat.1
+
+ regards,
+ petsd!joe
+
+Here is a note from the net:
+
+>From hplabs!pesnta!amd!turtlevax!ken Sat Jan 5 03:35:20 1985
+Path: ames!hplabs!pesnta!amd!turtlevax!ken
+From: ken@turtlevax.UUCP (Ken Turkowski)
+Newsgroups: net.sources
+Subject: Re: Compress release 3.0 : sample Makefile
+Organization: CADLINC, Inc. @ Menlo Park, CA
+
+In the compress 3.0 source recently posted to mod.sources, there is a
+#define variable which can be set for optimum performance on a machine
+with a large amount of memory. A program (usermem) to calculate the
+useable amount of physical user memory is enclosed, as well as a sample
+4.2bsd Vax Makefile for compress.
+
+Here is the README file from the previous version of compress (2.0):
+
+>Enclosed is compress.c version 2.0 with the following bugs fixed:
+>
+>1. The packed files produced by compress are different on different
+> machines and dependent on the vax sysgen option.
+> The bug was in the different byte/bit ordering on the
+> various machines. This has been fixed.
+>
+> This version is NOT compatible with the original vax posting
+> unless the '-DCOMPATIBLE' option is specified to the C
+> compiler. The original posting has a bug which I fixed,
+> causing incompatible files. I recommend you NOT to use this
+> option unless you already have a lot of packed files from
+> the original posting by thomas.
+>2. The exit status is not well defined (on some machines) causing the
+> scripts to fail.
+> The exit status is now 0,1 or 2 and is documented in
+> compress.l.
+>3. The function getopt() is not available in all C libraries.
+> The function getopt() is no longer referenced by the
+> program.
+>4. Error status is not being checked on the fwrite() and fflush() calls.
+> Fixed.
+>
+>The following enhancements have been made:
+>
+>1. Added facilities of "compact" into the compress program. "Pack",
+> "Unpack", and "Pcat" are no longer required (no longer supplied).
+>2. Installed work around for C compiler bug with "-O".
+>3. Added a magic number header (\037\235). Put the bits specified
+> in the file.
+>4. Added "-f" flag to force overwrite of output file.
+>5. Added "-c" flag and "zcat" program. 'ln compress zcat' after you
+> compile.
+>6. The 'uncompress' script has been deleted; simply
+> 'ln compress uncompress' after you compile and it will work.
+>7. Removed extra bit masking for machines that support unsigned
+> characters. If your machine doesn't support unsigned characters,
+> define "NO_UCHAR" when compiling.
+>
+>Compile "compress.c" with "-O -o compress" flags. Move "compress" to a
+>standard executable location, such as /usr/local. Then:
+> cd /usr/local
+> ln compress uncompress
+> ln compress zcat
+>
+>On machines that have a fixed stack size (such as Perkin-Elmer), set the
+>stack to at least 12kb. ("setstack compress 12" on Perkin-Elmer).
+>
+>Next, install the manual (compress.l).
+> cp compress.l /usr/man/manl - or -
+> cp compress.l /usr/man/man1/compress.1
+>
+>Here is the README that I sent with my first posting:
+>
+>>Enclosed is a modified version of compress.c, along with scripts to make it
+>>run identically to pack(1), unpack(1), an pcat(1). Here is what I
+>>(petsd!joe) and a colleague (petsd!peora!srd) did:
+>>
+>>1. Removed VAX dependencies.
+>>2. Changed the struct to separate arrays; saves mucho memory.
+>>3. Did comparisons in unsigned, where possible. (Faster on Perkin-Elmer.)
+>>4. Sorted the character next chain and changed the search to stop
+>>prematurely. This saves a lot on the execution time when compressing.
+>>
+>>This version is totally compatible with the original version. Even though
+>>lint(1) -p has no complaints about compress.c, it won't run on a 16-bit
+>>machine, due to the size of the arrays.
+>>
+>>Here is the README file from the original author:
+>>
+>>>Well, with all this discussion about file compression (for news batching
+>>>in particular) going around, I decided to implement the text compression
+>>>algorithm described in the June Computer magazine. The author claimed
+>>>blinding speed and good compression ratios. It's certainly faster than
+>>>compact (but, then, what wouldn't be), but it's also the same speed as
+>>>pack, and gets better compression than both of them. On 350K bytes of
+>>>unix-wizards, compact took about 8 minutes of CPU, pack took about 80
+>>>seconds, and compress (herein) also took 80 seconds. But, compact and
+>>>pack got about 30% compression, whereas compress got over 50%. So, I
+>>>decided I had something, and that others might be interested, too.
+>>>
+>>>As is probably true of compact and pack (although I haven't checked),
+>>>the byte order within a word is probably relevant here, but as long as
+>>>you stay on a single machine type, you should be ok. (Can anybody
+>>>elucidate on this?) There are a couple of asm's in the code (extv and
+>>>insv instructions), so anyone porting it to another machine will have to
+>>>deal with this anyway (and could probably make it compatible with Vax
+>>>byte order at the same time). Anyway, I've linted the code (both with
+>>>and without -p), so it should run elsewhere. Note the longs in the
+>>>code, you can take these out if you reduce BITS to <= 15.
+>>>
+>>>Have fun, and as always, if you make good enhancements, or bug fixes,
+>>>I'd like to see them.
+>>>
+>>>=Spencer (thomas@utah-20, {harpo,hplabs,arizona}!utah-cs!thomas)
+>>
+>> regards,
+>> joe
+>>
+>>--
+>>Full-Name: Joseph M. Orost
+>>UUCP: ..!{decvax,ucbvax,ihnp4}!vax135!petsd!joe
+>>US Mail: MS 313; Perkin-Elmer; 106 Apple St; Tinton Falls, NJ 07724
+>>Phone: (201) 870-5844
diff --git a/usr.bin/compress/doc/revision.log b/usr.bin/compress/doc/revision.log
new file mode 100644
index 0000000..16ca49d
--- /dev/null
+++ b/usr.bin/compress/doc/revision.log
@@ -0,0 +1,116 @@
+/*
+ * $Hdr: compress.c,v 4.0 85/07/30 12:50:00 joe Release $
+ * $Lg: compress.c,v $
+ * Revision 4.0 85/07/30 12:50:00 joe
+ * Removed ferror() calls in output routine on every output except first.
+ * Prepared for release to the world.
+ *
+ * Revision 3.6 85/07/04 01:22:21 joe
+ * Remove much wasted storage by overlaying hash table with the tables
+ * used by decompress: tab_suffix[1<<BITS], stack[8000]. Updated USERMEM
+ * computations. Fixed dump_tab() DEBUG routine.
+ *
+ * Revision 3.5 85/06/30 20:47:21 jaw
+ * Change hash function to use exclusive-or. Rip out hash cache. These
+ * speedups render the megamemory version defunct, for now. Make decoder
+ * stack global. Parts of the RCS trunks 2.7, 2.6, and 2.1 no longer apply.
+ *
+ * Revision 3.4 85/06/27 12:00:00 ken
+ * Get rid of all floating-point calculations by doing all compression ratio
+ * calculations in fixed point.
+ *
+ * Revision 3.3 85/06/24 21:53:24 joe
+ * Incorporate portability suggestion for M_XENIX. Got rid of text on #else
+ * and #endif lines. Cleaned up #ifdefs for vax and interdata.
+ *
+ * Revision 3.2 85/06/06 21:53:24 jaw
+ * Incorporate portability suggestions for Z8000, IBM PC/XT from mailing list.
+ * Default to "quiet" output (no compression statistics).
+ *
+ * Revision 3.1 85/05/12 18:56:13 jaw
+ * Integrate decompress() stack speedups (from early pointer mods by McKie).
+ * Repair multi-file USERMEM gaffe. Unify 'force' flags to mimic semantics
+ * of SVR2 'pack'. Streamline block-compress table clear logic. Increase
+ * output byte count by magic number size.
+ *
+ * Revision 3.0 84/11/27 11:50:00 petsd!joe
+ * Set HSIZE depending on BITS. Set BITS depending on USERMEM. Unrolled
+ * loops in clear routines. Added "-C" flag for 2.0 compatibility. Used
+ * unsigned compares on Perkin-Elmer. Fixed foreground check.
+ *
+ * Revision 2.7 84/11/16 19:35:39 ames!jaw
+ * Cache common hash codes based on input statistics; this improves
+ * performance for low-density raster images. Pass on #ifdef bundle
+ * from Turkowski.
+ *
+ * Revision 2.6 84/11/05 19:18:21 ames!jaw
+ * Vary size of hash tables to reduce time for small files.
+ * Tune PDP-11 hash function.
+ *
+ * Revision 2.5 84/10/30 20:15:14 ames!jaw
+ * Junk chaining; replace with the simpler (and, on the VAX, faster)
+ * double hashing, discussed within. Make block compression standard.
+ *
+ * Revision 2.4 84/10/16 11:11:11 ames!jaw
+ * Introduce adaptive reset for block compression, to boost the rate
+ * another several percent. (See mailing list notes.)
+ *
+ * Revision 2.3 84/09/22 22:00:00 petsd!joe
+ * Implemented "-B" block compress. Implemented REVERSE sorting of tab_next.
+ * Bug fix for last bits. Changed fwrite to putchar loop everywhere.
+ *
+ * Revision 2.2 84/09/18 14:12:21 ames!jaw
+ * Fold in news changes, small machine typedef from thomas,
+ * #ifdef interdata from joe.
+ *
+ * Revision 2.1 84/09/10 12:34:56 ames!jaw
+ * Configured fast table lookup for 32-bit machines.
+ * This cuts user time in half for b <= FBITS, and is useful for news batching
+ * from VAX to PDP sites. Also sped up decompress() [fwrite->putc] and
+ * added signal catcher [plus beef in writeerr()] to delete effluvia.
+ *
+ * Revision 2.0 84/08/28 22:00:00 petsd!joe
+ * Add check for foreground before prompting user. Insert maxbits into
+ * compressed file. Force file being uncompressed to end with ".Z".
+ * Added "-c" flag and "zcat". Prepared for release.
+ *
+ * Revision 1.10 84/08/24 18:28:00 turtlevax!ken
+ * Will only compress regular files (no directories), added a magic number
+ * header (plus an undocumented -n flag to handle old files without headers),
+ * added -f flag to force overwriting of possibly existing destination file,
+ * otherwise the user is prompted for a response. Will tack on a .Z to a
+ * filename if it doesn't have one when decompressing. Will only replace
+ * file if it was compressed.
+ *
+ * Revision 1.9 84/08/16 17:28:00 turtlevax!ken
+ * Removed scanargs(), getopt(), added .Z extension and unlimited number of
+ * filenames to compress. Flags may be clustered (-Ddvb12) or separated
+ * (-D -d -v -b 12), or combination thereof. Modes and other status is
+ * copied with copystat(). -O bug for 4.2 seems to have disappeared with
+ * 1.8.
+ *
+ * Revision 1.8 84/08/09 23:15:00 joe
+ * Made it compatible with vax version, installed jim's fixes/enhancements
+ *
+ * Revision 1.6 84/08/01 22:08:00 joe
+ * Sped up algorithm significantly by sorting the compress chain.
+ *
+ * Revision 1.5 84/07/13 13:11:00 srd
+ * Added C version of vax asm routines. Changed structure to arrays to
+ * save much memory. Do unsigned compares where possible (faster on
+ * Perkin-Elmer)
+ *
+ * Revision 1.4 84/07/05 03:11:11 thomas
+ * Clean up the code a little and lint it. (Lint complains about all
+ * the regs used in the asm, but I'm not going to "fix" this.)
+ *
+ * Revision 1.3 84/07/05 02:06:54 thomas
+ * Minor fixes.
+ *
+ * Revision 1.2 84/07/05 00:27:27 thomas
+ * Add variable bit length output.
+ *
+ */
+
+static char rcs_ident[] =
+ "$Hdr: compress.c,v 4.0 85/07/30 12:50:00 joe Release $";
diff --git a/usr.bin/compress/zopen.3 b/usr.bin/compress/zopen.3
new file mode 100644
index 0000000..32be9c4
--- /dev/null
+++ b/usr.bin/compress/zopen.3
@@ -0,0 +1,138 @@
+.\" $NetBSD: zopen.3,v 1.7 2003/08/07 11:13:29 agc Exp $
+.\"
+.\" Copyright (c) 1992, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)zopen.3 8.1 (Berkeley) 6/9/93
+.\"
+.Dd June 9, 1993
+.Dt ZOPEN 3
+.Os
+.Sh NAME
+.Nm zopen
+.Nd compressed stream open function
+.Sh SYNOPSIS
+.In stdio.h
+.Ft FILE *
+.Fn zopen "const char *path" "const char *mode" "int bits"
+.Sh DESCRIPTION
+The
+.Fn zopen
+function
+opens the compressed file whose name is the string pointed to by
+.Fa path
+and associates a stream with it.
+.Pp
+The argument
+.Fa mode
+points to one of the following one-character strings:
+.Bl -tag -width indent
+.It Dq Li r
+Open compressed file for reading.
+The stream is positioned at the beginning of the file.
+.It Dq Li w
+Truncate file to zero length or create compressed file for writing.
+The stream is positioned at the beginning of the file.
+.El
+.Pp
+Any created files will have mode
+.Pf \\*q Dv S_IRUSR
+\&|
+.Dv S_IWUSR
+\&|
+.Dv S_IRGRP
+\&|
+.Dv S_IWGRP
+\&|
+.Dv S_IROTH
+\&|
+.Dv S_IWOTH Ns \\*q
+.Pq Li 0666 ,
+as modified by the process'
+umask value (see
+.Xr umask 2 ) .
+.Pp
+Files may only be read or written.
+Seek operations are not allowed.
+.Pp
+The
+.Fa bits
+argument, if non-zero, is set to the bits code limit.
+If zero, the default is 16.
+See
+.Fn compress 1
+for more information.
+.Sh RETURN VALUES
+Upon successful completion
+.Fn zopen
+returns a
+.Tn FILE
+pointer.
+Otherwise,
+.Dv NULL
+is returned and the global variable
+.Va errno
+is set to indicate the error.
+.Sh ERRORS
+.Bl -tag -width [EINVAL]
+.It Bq Er EINVAL
+The
+.Fa mode
+or
+.Fa bits
+arguments specified to
+.Fn zopen
+were invalid.
+.It Bq Er EFTYPE
+The compressed file starts with an invalid header, or the compressed
+file is compressed with more bits than can be handled.
+.El
+.Pp
+The
+.Fn zopen
+function may also fail and set
+.Va errno
+for any of the errors specified for the routines
+.Xr fopen 3
+or
+.Xr funopen 3 .
+.Sh SEE ALSO
+.Xr compress 1 ,
+.Xr fopen 3 ,
+.Xr funopen 3
+.Sh HISTORY
+The
+.Nm zopen
+function
+first appeared in
+.Bx 4.4 .
+.Sh BUGS
+The
+.Fn zopen
+function
+may not be portable to systems other than
+.Bx .
diff --git a/usr.bin/compress/zopen.c b/usr.bin/compress/zopen.c
new file mode 100644
index 0000000..0d3f178
--- /dev/null
+++ b/usr.bin/compress/zopen.c
@@ -0,0 +1,699 @@
+/* $NetBSD: zopen.c,v 1.15 2011/08/16 13:55:01 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1985, 1986, 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis and James A. Woods, derived from original
+ * work by Spencer Thomas and Joseph Orost.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)zopen.c 8.1 (Berkeley) 6/27/93";
+#else
+static char rcsid[] = "$NetBSD: zopen.c,v 1.15 2011/08/16 13:55:01 joerg Exp $";
+#endif
+#endif /* LIBC_SCCS and not lint */
+
+/*-
+ * fcompress.c - File compression ala IEEE Computer, June 1984.
+ *
+ * Compress authors:
+ * Spencer W. Thomas (decvax!utah-cs!thomas)
+ * Jim McKie (decvax!mcvax!jim)
+ * Steve Davies (decvax!vax135!petsd!peora!srd)
+ * Ken Turkowski (decvax!decwrl!turtlevax!ken)
+ * James A. Woods (decvax!ihnp4!ames!jaw)
+ * Joe Orost (decvax!vax135!petsd!joe)
+ *
+ * Cleaned up and converted to library returning I/O streams by
+ * Diomidis Spinellis <dds@doc.ic.ac.uk>.
+ *
+ * zopen(filename, mode, bits)
+ * Returns a FILE * that can be used for read or write. The modes
+ * supported are only "r" and "w". Seeking is not allowed. On
+ * reading the file is decompressed, on writing it is compressed.
+ * The output is compatible with compress(1) with 16 bit tables.
+ * Any file produced by compress(1) can be read.
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define BITS 16 /* Default bits. */
+#define HSIZE 69001 /* 95% occupancy */
+
+/* A code_int must be able to hold 2**BITS values of type int, and also -1. */
+typedef long code_int;
+typedef long count_int;
+
+typedef u_char char_type;
+static char_type magic_header[] =
+ {'\037', '\235'}; /* 1F 9D */
+
+#define BIT_MASK 0x1f /* Defines for third byte of header. */
+#define BLOCK_MASK 0x80
+
+/*
+ * Masks 0x40 and 0x20 are free. I think 0x20 should mean that there is
+ * a fourth header byte (for expansion).
+ */
+#define INIT_BITS 9 /* Initial number of bits/code. */
+
+#define MAXCODE(n_bits) ((1 << (n_bits)) - 1)
+
+struct s_zstate {
+ FILE *zs_fp; /* File stream for I/O */
+ char zs_mode; /* r or w */
+ enum {
+ S_START, S_MIDDLE, S_EOF
+ } zs_state; /* State of computation */
+ int zs_n_bits; /* Number of bits/code. */
+ int zs_maxbits; /* User settable max # bits/code. */
+ code_int zs_maxcode; /* Maximum code, given n_bits. */
+ code_int zs_maxmaxcode; /* Should NEVER generate this code. */
+ count_int zs_htab [HSIZE];
+ u_short zs_codetab [HSIZE];
+ code_int zs_hsize; /* For dynamic table sizing. */
+ code_int zs_free_ent; /* First unused entry. */
+ /*
+ * Block compression parameters -- after all codes are used up,
+ * and compression rate changes, start over.
+ */
+ int zs_block_compress;
+ int zs_clear_flg;
+ long zs_ratio;
+ count_int zs_checkpoint;
+ int zs_offset;
+ long zs_in_count; /* Length of input. */
+ long zs_bytes_out; /* Length of compressed output. */
+ long zs_out_count; /* # of codes output (for debugging). */
+ char_type zs_buf[BITS];
+ union {
+ struct {
+ long zs_fcode;
+ code_int zs_ent;
+ code_int zs_hsize_reg;
+ int zs_hshift;
+ } w; /* Write paramenters */
+ struct {
+ char_type *zs_stackp;
+ int zs_finchar;
+ code_int zs_code, zs_oldcode, zs_incode;
+ int zs_roffset, zs_size;
+ char_type zs_gbuf[BITS];
+ } r; /* Read parameters */
+ } u;
+};
+
+/* Definitions to retain old variable names */
+#define fp zs->zs_fp
+#define zmode zs->zs_mode
+#define state zs->zs_state
+#define n_bits zs->zs_n_bits
+#define maxbits zs->zs_maxbits
+#define maxcode zs->zs_maxcode
+#define maxmaxcode zs->zs_maxmaxcode
+#define htab zs->zs_htab
+#define codetab zs->zs_codetab
+#define hsize zs->zs_hsize
+#define free_ent zs->zs_free_ent
+#define block_compress zs->zs_block_compress
+#define clear_flg zs->zs_clear_flg
+#define ratio zs->zs_ratio
+#define checkpoint zs->zs_checkpoint
+#define offset zs->zs_offset
+#define in_count zs->zs_in_count
+#define bytes_out zs->zs_bytes_out
+#define out_count zs->zs_out_count
+#define buf zs->zs_buf
+#define fcode zs->u.w.zs_fcode
+#define hsize_reg zs->u.w.zs_hsize_reg
+#define ent zs->u.w.zs_ent
+#define hshift zs->u.w.zs_hshift
+#define stackp zs->u.r.zs_stackp
+#define finchar zs->u.r.zs_finchar
+#define code zs->u.r.zs_code
+#define oldcode zs->u.r.zs_oldcode
+#define incode zs->u.r.zs_incode
+#define roffset zs->u.r.zs_roffset
+#define size zs->u.r.zs_size
+#define gbuf zs->u.r.zs_gbuf
+
+/*
+ * To save much memory, we overlay the table used by compress() with those
+ * used by decompress(). The tab_prefix table is the same size and type as
+ * the codetab. The tab_suffix table needs 2**BITS characters. We get this
+ * from the beginning of htab. The output stack uses the rest of htab, and
+ * contains characters. There is plenty of room for any possible stack
+ * (stack used to be 8000 characters).
+ */
+
+#define htabof(i) htab[i]
+#define codetabof(i) codetab[i]
+
+#define tab_prefixof(i) codetabof(i)
+#define tab_suffixof(i) ((char_type *)(htab))[i]
+#define de_stack ((char_type *)&tab_suffixof(1 << BITS))
+
+#define CHECK_GAP 10000 /* Ratio check interval. */
+
+/*
+ * the next two codes should not be changed lightly, as they must not
+ * lie within the contiguous general code space.
+ */
+#define FIRST 257 /* First free entry. */
+#define CLEAR 256 /* Table clear output code. */
+
+static int cl_block(struct s_zstate *);
+static code_int getcode(struct s_zstate *);
+static int output(struct s_zstate *, code_int);
+static int zclose(void *);
+FILE *zopen(const char *, const char *, int);
+static int zread(void *, char *, int);
+static int zwrite(void *, const char *, int);
+
+/*-
+ * Algorithm from "A Technique for High Performance Data Compression",
+ * Terry A. Welch, IEEE Computer Vol 17, No 6 (June 1984), pp 8-19.
+ *
+ * Algorithm:
+ * Modified Lempel-Ziv method (LZW). Basically finds common
+ * substrings and replaces them with a variable size code. This is
+ * deterministic, and can be done on the fly. Thus, the decompression
+ * procedure needs no input table, but tracks the way the table was built.
+ */
+
+/*-
+ * compress write
+ *
+ * Algorithm: use open addressing double hashing (no chaining) on the
+ * prefix code / next character combination. We do a variant of Knuth's
+ * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
+ * secondary probe. Here, the modular division first probe is gives way
+ * to a faster exclusive-or manipulation. Also do block compression with
+ * an adaptive reset, whereby the code table is cleared when the compression
+ * ratio decreases, but after the table fills. The variable-length output
+ * codes are re-sized at this point, and a special CLEAR code is generated
+ * for the decompressor. Late addition: construct the table according to
+ * file size for noticeable speed improvement on small files. Please direct
+ * questions about this implementation to ames!jaw.
+ */
+static int
+zwrite(void *cookie, const char *wbp, int num)
+{
+ code_int i;
+ int c, disp;
+ struct s_zstate *zs;
+ const u_char *bp;
+ u_char tmp;
+ int count;
+
+ if (num == 0)
+ return (0);
+
+ zs = cookie;
+ count = num;
+ bp = (const u_char *)wbp;
+ if (state == S_MIDDLE)
+ goto middle;
+ state = S_MIDDLE;
+
+ maxmaxcode = 1L << maxbits;
+ if (fwrite(magic_header,
+ sizeof(char), sizeof(magic_header), fp) != sizeof(magic_header))
+ return (-1);
+ tmp = (u_char)(maxbits | block_compress);
+ if (fwrite(&tmp, sizeof(char), sizeof(tmp), fp) != sizeof(tmp))
+ return (-1);
+
+ offset = 0;
+ bytes_out = 3; /* Includes 3-byte header mojo. */
+ out_count = 0;
+ clear_flg = 0;
+ ratio = 0;
+ in_count = 1;
+ checkpoint = CHECK_GAP;
+ maxcode = MAXCODE(n_bits = INIT_BITS);
+ free_ent = ((block_compress) ? FIRST : 256);
+
+ ent = *bp++;
+ --count;
+
+ hshift = 0;
+ for (fcode = (long)hsize; fcode < 65536L; fcode *= 2L)
+ hshift++;
+ hshift = 8 - hshift; /* Set hash code range bound. */
+
+ hsize_reg = hsize;
+ memset(htab, 0xff, hsize_reg * sizeof(count_int));
+
+middle: while (count--) {
+ c = *bp++;
+ in_count++;
+ fcode = (long)(((long)c << maxbits) + ent);
+ i = ((c << hshift) ^ ent); /* Xor hashing. */
+
+ if (htabof(i) == fcode) {
+ ent = codetabof(i);
+ continue;
+ } else if ((long)htabof(i) < 0) /* Empty slot. */
+ goto nomatch;
+ disp = hsize_reg - i; /* Secondary hash (after G. Knott). */
+ if (i == 0)
+ disp = 1;
+probe: if ((i -= disp) < 0)
+ i += hsize_reg;
+
+ if (htabof(i) == fcode) {
+ ent = codetabof(i);
+ continue;
+ }
+ if ((long)htabof(i) >= 0)
+ goto probe;
+nomatch: if (output(zs, (code_int) ent) == -1)
+ return (-1);
+ out_count++;
+ ent = c;
+ if (free_ent < maxmaxcode) {
+ codetabof(i) = free_ent++; /* code -> hashtable */
+ htabof(i) = fcode;
+ } else if ((count_int)in_count >=
+ checkpoint && block_compress) {
+ if (cl_block(zs) == -1)
+ return (-1);
+ }
+ }
+ return (num);
+}
+
+static int
+zclose(void *cookie)
+{
+ struct s_zstate *zs;
+ int rval;
+
+ zs = cookie;
+ if (zmode == 'w') { /* Put out the final code. */
+ if (output(zs, (code_int) ent) == -1) {
+ (void)fclose(fp);
+ free(zs);
+ return (-1);
+ }
+ out_count++;
+ if (output(zs, (code_int) - 1) == -1) {
+ (void)fclose(fp);
+ free(zs);
+ return (-1);
+ }
+ }
+ rval = fclose(fp) == EOF ? -1 : 0;
+ free(zs);
+ return (rval);
+}
+
+/*-
+ * Output the given code.
+ * Inputs:
+ * code: A n_bits-bit integer. If == -1, then EOF. This assumes
+ * that n_bits =< (long)wordsize - 1.
+ * Outputs:
+ * Outputs code to the file.
+ * Assumptions:
+ * Chars are 8 bits long.
+ * Algorithm:
+ * Maintain a BITS character long buffer (so that 8 codes will
+ * fit in it exactly). Use the VAX insv instruction to insert each
+ * code in turn. When the buffer fills up empty it and start over.
+ */
+
+static char_type lmask[9] =
+ {0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00};
+static char_type rmask[9] =
+ {0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff};
+
+static int
+output(struct s_zstate *zs, code_int ocode)
+{
+ int bits, r_off;
+ char_type *bp;
+
+ r_off = offset;
+ bits = n_bits;
+ bp = buf;
+ if (ocode >= 0) {
+ /* Get to the first byte. */
+ bp += (r_off >> 3);
+ r_off &= 7;
+ /*
+ * Since ocode is always >= 8 bits, only need to mask the first
+ * hunk on the left.
+ */
+ *bp = (*bp & rmask[r_off]) | ((ocode << r_off) & lmask[r_off]);
+ bp++;
+ bits -= (8 - r_off);
+ ocode >>= 8 - r_off;
+ /* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */
+ if (bits >= 8) {
+ *bp++ = ocode;
+ ocode >>= 8;
+ bits -= 8;
+ }
+ /* Last bits. */
+ if (bits)
+ *bp = ocode;
+ offset += n_bits;
+ if (offset == (n_bits << 3)) {
+ bp = buf;
+ bits = n_bits;
+ bytes_out += bits;
+ if (fwrite(bp, sizeof(char), bits, fp) != (size_t)bits)
+ return (-1);
+ bp += bits;
+ bits = 0;
+ offset = 0;
+ }
+ /*
+ * If the next entry is going to be too big for the ocode size,
+ * then increase it, if possible.
+ */
+ if (free_ent > maxcode || (clear_flg > 0)) {
+ /*
+ * Write the whole buffer, because the input side won't
+ * discover the size increase until after it has read it.
+ */
+ if (offset > 0) {
+ if (fwrite(buf, 1, n_bits, fp) != (size_t)n_bits)
+ return (-1);
+ bytes_out += n_bits;
+ }
+ offset = 0;
+
+ if (clear_flg) {
+ maxcode = MAXCODE(n_bits = INIT_BITS);
+ clear_flg = 0;
+ } else {
+ n_bits++;
+ if (n_bits == maxbits)
+ maxcode = maxmaxcode;
+ else
+ maxcode = MAXCODE(n_bits);
+ }
+ }
+ } else {
+ /* At EOF, write the rest of the buffer. */
+ if (offset > 0) {
+ offset = (offset + 7) / 8;
+ if (fwrite(buf, 1, offset, fp) != (size_t)offset)
+ return (-1);
+ bytes_out += offset;
+ }
+ offset = 0;
+ }
+ return (0);
+}
+
+/*
+ * Decompress read. This routine adapts to the codes in the file building
+ * the "string" table on-the-fly; requiring no table to be stored in the
+ * compressed file. The tables used herein are shared with those of the
+ * compress() routine. See the definitions above.
+ */
+static int
+zread(void *cookie, char *rbp, int num)
+{
+ u_int count;
+ struct s_zstate *zs;
+ u_char *bp, header[3];
+
+ if (num == 0)
+ return (0);
+
+ zs = cookie;
+ count = num;
+ bp = (u_char *)rbp;
+ switch (state) {
+ case S_START:
+ state = S_MIDDLE;
+ break;
+ case S_MIDDLE:
+ goto middle;
+ case S_EOF:
+ goto eof;
+ }
+
+ /* Check the magic number */
+ if (fread(header,
+ sizeof(char), sizeof(header), fp) != sizeof(header) ||
+ memcmp(header, magic_header, sizeof(magic_header)) != 0) {
+ errno = EFTYPE;
+ return (-1);
+ }
+ maxbits = header[2]; /* Set -b from file. */
+ block_compress = maxbits & BLOCK_MASK;
+ maxbits &= BIT_MASK;
+ maxmaxcode = 1L << maxbits;
+ if (maxbits > BITS || maxbits < 12) {
+ errno = EFTYPE;
+ return (-1);
+ }
+ /* As above, initialize the first 256 entries in the table. */
+ maxcode = MAXCODE(n_bits = INIT_BITS);
+ for (code = 255; code >= 0; code--) {
+ tab_prefixof(code) = 0;
+ tab_suffixof(code) = (char_type) code;
+ }
+ free_ent = block_compress ? FIRST : 256;
+ oldcode = -1;
+ stackp = de_stack;
+
+ while ((code = getcode(zs)) > -1) {
+
+ if ((code == CLEAR) && block_compress) {
+ for (code = 255; code >= 0; code--)
+ tab_prefixof(code) = 0;
+ clear_flg = 1;
+ free_ent = FIRST;
+ oldcode = -1;
+ continue;
+ }
+ incode = code;
+
+ /* Special case for kWkWk string. */
+ if (code >= free_ent) {
+ if (code > free_ent || oldcode == -1) {
+ /* Bad stream. */
+ errno = EINVAL;
+ return (-1);
+ }
+ *stackp++ = finchar;
+ code = oldcode;
+ }
+ /*
+ * The above condition ensures that code < free_ent.
+ * The construction of tab_prefixof in turn guarantees that
+ * each iteration decreases code and therefore stack usage is
+ * bound by 1 << BITS - 256.
+ */
+
+ /* Generate output characters in reverse order. */
+ while (code >= 256) {
+ *stackp++ = tab_suffixof(code);
+ code = tab_prefixof(code);
+ }
+ *stackp++ = finchar = tab_suffixof(code);
+
+ /* And put them out in forward order. */
+middle: do {
+ if (count-- == 0)
+ return (num);
+ *bp++ = *--stackp;
+ } while (stackp > de_stack);
+
+ /* Generate the new entry. */
+ if ((code = free_ent) < maxmaxcode && oldcode != -1) {
+ tab_prefixof(code) = (u_short) oldcode;
+ tab_suffixof(code) = finchar;
+ free_ent = code + 1;
+ }
+
+ /* Remember previous code. */
+ oldcode = incode;
+ }
+ state = S_EOF;
+eof: return (num - count);
+}
+
+/*-
+ * Read one code from the standard input. If EOF, return -1.
+ * Inputs:
+ * stdin
+ * Outputs:
+ * code or -1 is returned.
+ */
+static code_int
+getcode(struct s_zstate *zs)
+{
+ code_int gcode;
+ int r_off, bits;
+ char_type *bp;
+
+ bp = gbuf;
+ if (clear_flg > 0 || roffset >= size || free_ent > maxcode) {
+ /*
+ * If the next entry will be too big for the current gcode
+ * size, then we must increase the size. This implies reading
+ * a new buffer full, too.
+ */
+ if (free_ent > maxcode) {
+ n_bits++;
+ if (n_bits == maxbits) /* Won't get any bigger now. */
+ maxcode = maxmaxcode;
+ else
+ maxcode = MAXCODE(n_bits);
+ }
+ if (clear_flg > 0) {
+ maxcode = MAXCODE(n_bits = INIT_BITS);
+ clear_flg = 0;
+ }
+ size = fread(gbuf, 1, n_bits, fp);
+ if (size <= 0) /* End of file. */
+ return (-1);
+ roffset = 0;
+ /* Round size down to integral number of codes. */
+ size = (size << 3) - (n_bits - 1);
+ }
+ r_off = roffset;
+ bits = n_bits;
+
+ /* Get to the first byte. */
+ bp += (r_off >> 3);
+ r_off &= 7;
+
+ /* Get first part (low order bits). */
+ gcode = (*bp++ >> r_off);
+ bits -= (8 - r_off);
+ r_off = 8 - r_off; /* Now, roffset into gcode word. */
+
+ /* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */
+ if (bits >= 8) {
+ gcode |= *bp++ << r_off;
+ r_off += 8;
+ bits -= 8;
+ }
+
+ /* High order bits. */
+ gcode |= (*bp & rmask[bits]) << r_off;
+ roffset += n_bits;
+
+ return (gcode);
+}
+
+static int
+cl_block(struct s_zstate *zs) /* Table clear for block compress. */
+{
+ long rat;
+
+ checkpoint = in_count + CHECK_GAP;
+
+ if (in_count > 0x007fffff) { /* Shift will overflow. */
+ rat = bytes_out >> 8;
+ if (rat == 0) /* Don't divide by zero. */
+ rat = 0x7fffffff;
+ else
+ rat = in_count / rat;
+ } else
+ rat = (in_count << 8) / bytes_out; /* 8 fractional bits. */
+ if (rat > ratio)
+ ratio = rat;
+ else {
+ ratio = 0;
+ memset(htab, 0xff, hsize * sizeof(count_int));
+ free_ent = FIRST;
+ clear_flg = 1;
+ if (output(zs, (code_int) CLEAR) == -1)
+ return (-1);
+ }
+ return (0);
+}
+
+FILE *
+zopen(const char *fname, const char *mode, int bits)
+{
+ struct s_zstate *zs;
+
+ if ((mode[0] != 'r' && mode[0] != 'w') || mode[1] != '\0' ||
+ bits < 0 || bits > BITS) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ if ((zs = calloc(1, sizeof(struct s_zstate))) == NULL)
+ return (NULL);
+
+ maxbits = bits ? bits : BITS; /* User settable max # bits/code. */
+ maxmaxcode = 1 << maxbits; /* Should NEVER generate this code. */
+ hsize = HSIZE; /* For dynamic table sizing. */
+ free_ent = 0; /* First unused entry. */
+ block_compress = BLOCK_MASK;
+ clear_flg = 0;
+ ratio = 0;
+ checkpoint = CHECK_GAP;
+ in_count = 1; /* Length of input. */
+ out_count = 0; /* # of codes output (for debugging). */
+ state = S_START;
+ roffset = 0;
+ size = 0;
+
+ /*
+ * Layering compress on top of stdio in order to provide buffering,
+ * and ensure that reads and write work with the data specified.
+ */
+ if ((fp = fopen(fname, mode)) == NULL) {
+ free(zs);
+ return (NULL);
+ }
+ switch (*mode) {
+ case 'r':
+ zmode = 'r';
+ return (funopen(zs, zread, NULL, NULL, zclose));
+ case 'w':
+ zmode = 'w';
+ return (funopen(zs, NULL, zwrite, NULL, zclose));
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
diff --git a/usr.bin/csplit/csplit.1 b/usr.bin/csplit/csplit.1
new file mode 100644
index 0000000..93f96a6
--- /dev/null
+++ b/usr.bin/csplit/csplit.1
@@ -0,0 +1,163 @@
+.\" $NetBSD: csplit.1,v 1.5 2014/02/04 19:48:48 wiz Exp $
+.\"
+.\" Copyright (c) 2002 Tim J. Robbins.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD: src/usr.bin/csplit/csplit.1,v 1.11 2005/01/25 22:29:51 tjr Exp $
+.\"
+.Dd February 4, 2014
+.Dt CSPLIT 1
+.Os
+.Sh NAME
+.Nm csplit
+.Nd split files based on context
+.Sh SYNOPSIS
+.Nm
+.Op Fl ks
+.Op Fl f Ar prefix
+.Op Fl n Ar number
+.Ar file args ...
+.Sh DESCRIPTION
+The
+.Nm
+utility splits
+.Ar file
+into pieces using the patterns
+.Ar args .
+If
+.Ar file
+is
+a dash
+.Pq Sq - ,
+.Nm
+reads from standard input.
+.Pp
+Files are created with a prefix of
+.Dq xx
+and two decimal digits.
+The size of each file is written to standard output
+as it is created.
+If an error occurs whilst files are being created,
+or a
+.Dv HUP ,
+.Dv INT ,
+or
+.Dv TERM
+signal is received,
+all files previously written are removed.
+.Pp
+The options are as follows:
+.Bl -tag -offset indent -width 10n
+.It Fl f Ar prefix
+Create file names beginning with
+.Ar prefix ,
+instead of
+.Dq xx .
+.It Fl k
+Do not remove previously created files if an error occurs or a
+.Dv HUP ,
+.Dv INT ,
+or
+.Dv TERM
+signal is received.
+.It Fl n Ar number
+Create file names beginning with
+.Ar number
+of decimal digits after the prefix,
+instead of 2.
+.It Fl s
+Do not write the size of each output file to standard output as it is
+created.
+.El
+.Pp
+The
+.Ar args
+operands may be a combination of the following patterns:
+.Bl -tag -offset indent -width 10n
+.It / Ns Ar regexp Ns / Ns Oo Oo Cm + Ns | Ns Cm - Oc Ns Ar offset Oc
+Create a file containing the input from the current line to (but not including)
+the next line matching the given basic regular expression.
+An optional
+.Ar offset
+from the line that matched may be specified.
+.It % Ns Ar regexp Ns % Ns Oo Oo Cm + Ns | Ns Cm - Oc Ns Ar offset Oc
+Same as above but a file is not created for the output.
+.It Ar line_no
+Create containing the input from the current line to (but not including)
+the specified line number.
+.It Brq Ar num
+Repeat the previous pattern the specified number of times.
+If it follows a line number pattern, a new file will be created for each
+.Ar line_no
+lines,
+.Ar num
+times.
+The first line of the file is line number 1 for historic reasons.
+.El
+.Pp
+After all the patterns have been processed, the remaining input data
+(if there is any) will be written to a new file.
+.Pp
+Requesting to split at a line before the current line number or past the
+end of the file will result in an error.
+.Sh ENVIRONMENT
+The
+.Ev LANG , LC_ALL , LC_COLLATE ,
+and
+.Ev LC_CTYPE
+environment variables affect the execution of
+.Nm
+as described in
+.Xr environ 7 .
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+Split the
+.Xr mdoc 7
+file
+.Pa foo.1
+into one file for each section (up to 21 plus one for the rest, if any):
+.Pp
+.Dl "$ csplit -k foo.1 '%^\e.Sh%' '/^\e.Sh/' '{20}'"
+.Pp
+Split standard input after the first 99 lines and every 100 lines thereafter:
+.Pp
+.Dl "$ csplit -k - 100 '{19}'"
+.Sh SEE ALSO
+.Xr sed 1 ,
+.Xr split 1 ,
+.Xr re_format 7
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.1-2004 .
+.Sh HISTORY
+A
+.Nm
+command appeared in PWB UNIX.
+.Sh BUGS
+Input lines are limited to
+.Dv LINE_MAX
+(2048) bytes in length.
diff --git a/usr.bin/csplit/csplit.c b/usr.bin/csplit/csplit.c
new file mode 100644
index 0000000..9308ed8
--- /dev/null
+++ b/usr.bin/csplit/csplit.c
@@ -0,0 +1,479 @@
+/* $NetBSD: csplit.c,v 1.7 2017/07/30 23:02:53 cheusov Exp $ */
+/* $FreeBSD: src/usr.bin/csplit/csplit.c,v 1.9 2004/03/22 11:15:03 tjr Exp$ */
+
+/*-
+ * Copyright (c) 2002 Tim J. Robbins.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * csplit -- split files based on context
+ *
+ * This utility splits its input into numbered output files by line number
+ * or by a regular expression. Regular expression matches have an optional
+ * offset with them, allowing the split to occur a specified number of
+ * lines before or after the match.
+ *
+ * To handle negative offsets, we stop reading when the match occurs and
+ * store the offset that the file should have been split at, then use
+ * this output file as input until all the "overflowed" lines have been read.
+ * The file is then closed and truncated to the correct length.
+ *
+ * We assume that the output files can be seeked upon (ie. they cannot be
+ * symlinks to named pipes or character devices), but make no such
+ * assumption about the input.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: csplit.c,v 1.7 2017/07/30 23:02:53 cheusov Exp $");
+#endif
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <regex.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+static void cleanup(void);
+static void do_lineno(const char *);
+static void do_rexp(const char *);
+static char *get_line(void);
+static void handlesig(int);
+static FILE *newfile(void);
+static void toomuch(FILE *, long);
+static void usage(void) __dead;
+
+/*
+ * Command line options
+ */
+static const char *prefix; /* File name prefix */
+static long sufflen; /* Number of decimal digits for suffix */
+static int sflag; /* Suppress output of file names */
+static int kflag; /* Keep output if error occurs */
+
+/*
+ * Other miscellaneous globals (XXX too many)
+ */
+static long lineno; /* Current line number in input file */
+static long reps; /* Number of repetitions for this pattern */
+static long nfiles; /* Number of files output so far */
+static long maxfiles; /* Maximum number of files we can create */
+static char currfile[PATH_MAX]; /* Current output file */
+static const char *infn; /* Name of the input file */
+static FILE *infile; /* Input file handle */
+static FILE *overfile; /* Overflow file for toomuch() */
+static off_t truncofs; /* Offset this file should be truncated at */
+static int doclean; /* Should cleanup() remove output? */
+
+int
+main(int argc, char *argv[])
+{
+ struct sigaction sa;
+ long i;
+ int ch;
+ const char *expr;
+ char *ep, *p;
+ FILE *ofp;
+
+ (void)setlocale(LC_ALL, "");
+
+ kflag = sflag = 0;
+ prefix = "xx";
+ sufflen = 2;
+ while ((ch = getopt(argc, argv, "ksf:n:")) > 0) {
+ switch (ch) {
+ case 'f':
+ prefix = optarg;
+ break;
+ case 'k':
+ kflag = 1;
+ break;
+ case 'n':
+ errno = 0;
+ sufflen = strtol(optarg, &ep, 10);
+ if (sufflen <= 0 || *ep != '\0' || errno != 0)
+ errx(1, "%s: bad suffix length", optarg);
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ default:
+ usage();
+ /*NOTREACHED*/
+ }
+ }
+
+ if (sufflen + strlen(prefix) >= PATH_MAX)
+ errx(1, "name too long");
+
+ argc -= optind;
+ argv += optind;
+
+ if ((infn = *argv++) == NULL)
+ usage();
+ if (strcmp(infn, "-") == 0) {
+ infile = stdin;
+ infn = "stdin";
+ } else if ((infile = fopen(infn, "r")) == NULL)
+ err(1, "%s", infn);
+
+ if (!kflag) {
+ doclean = 1;
+ (void)atexit(cleanup);
+ sa.sa_flags = 0;
+ sa.sa_handler = handlesig;
+ (void)sigemptyset(&sa.sa_mask);
+ (void)sigaddset(&sa.sa_mask, SIGHUP);
+ (void)sigaddset(&sa.sa_mask, SIGINT);
+ (void)sigaddset(&sa.sa_mask, SIGTERM);
+ (void)sigaction(SIGHUP, &sa, NULL);
+ (void)sigaction(SIGINT, &sa, NULL);
+ (void)sigaction(SIGTERM, &sa, NULL);
+ }
+
+ lineno = 0;
+ nfiles = 0;
+ truncofs = 0;
+ overfile = NULL;
+
+ /* Ensure 10^sufflen < LONG_MAX. */
+ for (maxfiles = 1, i = 0; i < sufflen; i++) {
+ if (maxfiles > LONG_MAX / 10)
+ errx(1, "%ld: suffix too long (limit %ld)",
+ sufflen, i);
+ maxfiles *= 10;
+ }
+
+ /* Create files based on supplied patterns. */
+ while (nfiles < maxfiles - 1 && (expr = *argv++) != NULL) {
+ /* Look ahead & see if this pattern has any repetitions. */
+ if (*argv != NULL && **argv == '{') {
+ errno = 0;
+ reps = strtol(*argv + 1, &ep, 10);
+ if (reps < 0 || *ep != '}' || errno != 0)
+ errx(1, "%s: bad repetition count", *argv + 1);
+ argv++;
+ } else
+ reps = 0;
+
+ if (*expr == '/' || *expr == '%') {
+ do
+ do_rexp(expr);
+ while (reps-- != 0 && nfiles < maxfiles - 1);
+ } else if (isdigit((unsigned char)*expr))
+ do_lineno(expr);
+ else
+ errx(1, "%s: unrecognised pattern", expr);
+ }
+
+ /* Copy the rest into a new file. */
+ if (!feof(infile)) {
+ ofp = newfile();
+ while ((p = get_line()) != NULL && fputs(p, ofp) != EOF)
+ ;
+ if (!sflag)
+ (void)printf("%jd\n", (intmax_t)ftello(ofp));
+ if (fclose(ofp) != 0)
+ err(1, "%s", currfile);
+ }
+
+ toomuch(NULL, 0L);
+ doclean = 0;
+
+ return (0);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+"Usage: %s [-ks] [-f prefix] [-n number] file args ...\n", getprogname());
+ exit(1);
+}
+
+__dead static void
+handlesig(int sig)
+{
+ char msg[BUFSIZ];
+ size_t len;
+
+ len = snprintf(msg, sizeof(msg), "%s: Caught %s, cleaning up\n",
+ getprogname(), strsignal(sig));
+ if (len < sizeof(msg))
+ (void)write(STDERR_FILENO, msg, len);
+ cleanup();
+ (void)raise_default_signal(sig);
+ _exit(2);
+}
+
+/* Create a new output file. */
+static FILE *
+newfile(void)
+{
+ FILE *fp;
+
+ if ((size_t)snprintf(currfile, sizeof(currfile), "%s%0*ld", prefix,
+ (int)sufflen, nfiles) >= sizeof(currfile))
+ errx(1, "%s: %s", currfile, strerror(ENAMETOOLONG));
+ if ((fp = fopen(currfile, "w+")) == NULL)
+ err(1, "%s", currfile);
+ nfiles++;
+
+ return (fp);
+}
+
+/* Remove partial output, called before exiting. */
+static void
+cleanup(void)
+{
+ char fnbuf[PATH_MAX];
+ long i;
+
+ if (!doclean)
+ return;
+
+ /*
+ * NOTE: One cannot portably assume to be able to call snprintf()
+ * from inside a signal handler. It does, however, appear to be safe
+ * to do on FreeBSD and NetBSD. The solution to this problem is worse
+ * than the problem itself.
+ */
+
+ for (i = 0; i < nfiles; i++) {
+ (void)snprintf(fnbuf, sizeof(fnbuf), "%s%0*ld", prefix,
+ (int)sufflen, i);
+ (void)unlink(fnbuf);
+ }
+}
+
+/* Read a line from the input into a static buffer. */
+static char *
+get_line(void)
+{
+ static char lbuf[LINE_MAX];
+ FILE *src;
+
+ src = overfile != NULL ? overfile : infile;
+
+again: if (fgets(lbuf, sizeof(lbuf), src) == NULL) {
+ if (src == overfile) {
+ src = infile;
+ goto again;
+ }
+ return (NULL);
+ }
+ if (ferror(src))
+ err(1, "%s", infn);
+ lineno++;
+
+ return (lbuf);
+}
+
+/* Conceptually rewind the input (as obtained by get_line()) back `n' lines. */
+static void
+toomuch(FILE *ofp, long n)
+{
+ char buf[BUFSIZ];
+ size_t i, nread;
+
+ if (overfile != NULL) {
+ /*
+ * Truncate the previous file we overflowed into back to
+ * the correct length, close it.
+ */
+ if (fflush(overfile) != 0)
+ err(1, "overflow");
+ if (ftruncate(fileno(overfile), truncofs) != 0)
+ err(1, "overflow");
+ if (fclose(overfile) != 0)
+ err(1, "overflow");
+ overfile = NULL;
+ }
+
+ if (n == 0)
+ /* Just tidying up */
+ return;
+
+ lineno -= n;
+
+ /*
+ * Wind the overflow file backwards to `n' lines before the
+ * current one.
+ */
+ do {
+ if (ftello(ofp) < (off_t)sizeof(buf))
+ rewind(ofp);
+ else
+ (void)fseeko(ofp, -(off_t)sizeof(buf), SEEK_CUR);
+ if (ferror(ofp))
+ errx(1, "%s: can't seek", currfile);
+ if ((nread = fread(buf, 1, sizeof(buf), ofp)) == 0)
+ errx(1, "can't read overflowed output");
+ if (fseeko(ofp, -(off_t)nread, SEEK_CUR) != 0)
+ err(1, "%s", currfile);
+ for (i = 1; i <= nread; i++)
+ if (buf[nread - i] == '\n' && n-- == 0)
+ break;
+ if (ftello(ofp) == 0)
+ break;
+ } while (n > 0);
+ if (fseeko(ofp, (off_t)nread - i + 1, SEEK_CUR) != 0)
+ err(1, "%s", currfile);
+
+ /*
+ * get_line() will read from here. Next call will truncate to
+ * truncofs in this file.
+ */
+ overfile = ofp;
+ truncofs = ftello(overfile);
+}
+
+/* Handle splits for /regexp/ and %regexp% patterns. */
+static void
+do_rexp(const char *expr)
+{
+ regex_t cre;
+ intmax_t nwritten;
+ long ofs;
+ int first;
+ char *ecopy, *ep, *p, *pofs, *re;
+ FILE *ofp;
+
+ if ((ecopy = strdup(expr)) == NULL)
+ err(1, "strdup");
+
+ re = ecopy + 1;
+ if ((pofs = strrchr(ecopy, *expr)) == NULL || pofs[-1] == '\\')
+ errx(1, "%s: missing trailing %c", expr, *expr);
+ *pofs++ = '\0';
+
+ if (*pofs != '\0') {
+ errno = 0;
+ ofs = strtol(pofs, &ep, 10);
+ if (*ep != '\0' || errno != 0)
+ errx(1, "%s: bad offset", pofs);
+ } else
+ ofs = 0;
+
+ if (regcomp(&cre, re, REG_BASIC|REG_NOSUB) != 0)
+ errx(1, "%s: bad regular expression", re);
+
+ if (*expr == '/')
+ /* /regexp/: Save results to a file. */
+ ofp = newfile();
+ else {
+ /* %regexp%: Make a temporary file for overflow. */
+ if ((ofp = tmpfile()) == NULL)
+ err(1, "tmpfile");
+ }
+
+ /* Read and output lines until we get a match. */
+ first = 1;
+ while ((p = get_line()) != NULL) {
+ if (fputs(p, ofp) == EOF)
+ break;
+ if (!first && regexec(&cre, p, 0, NULL, 0) == 0)
+ break;
+ first = 0;
+ }
+
+ if (p == NULL)
+ errx(1, "%s: no match", re);
+
+ if (ofs <= 0) {
+ /*
+ * Negative (or zero) offset: throw back any lines we should
+ * not have read yet.
+ */
+ if (p != NULL) {
+ toomuch(ofp, -ofs + 1);
+ nwritten = (intmax_t)truncofs;
+ } else
+ nwritten = (intmax_t)ftello(ofp);
+ } else {
+ /*
+ * Positive offset: copy the requested number of lines
+ * after the match.
+ */
+ while (--ofs > 0 && (p = get_line()) != NULL)
+ if (fputs(p, ofp) == EOF)
+ break;
+ toomuch(NULL, 0L);
+ nwritten = (intmax_t)ftello(ofp);
+ if (fclose(ofp) != 0)
+ err(1, "%s", currfile);
+ }
+
+ if (!sflag && *expr == '/')
+ (void)printf("%jd\n", nwritten);
+
+ regfree(&cre);
+ free(ecopy);
+}
+
+/* Handle splits based on line number. */
+static void
+do_lineno(const char *expr)
+{
+ long lastline, tgtline;
+ char *ep, *p;
+ FILE *ofp;
+
+ errno = 0;
+ tgtline = strtol(expr, &ep, 10);
+ if (tgtline <= 0 || errno != 0 || *ep != '\0')
+ errx(1, "%s: bad line number", expr);
+ lastline = tgtline;
+ if (lastline <= lineno)
+ errx(1, "%s: can't go backwards", expr);
+
+ while (nfiles < maxfiles - 1) {
+ ofp = newfile();
+ while (lineno + 1 != lastline) {
+ if ((p = get_line()) == NULL)
+ errx(1, "%ld: out of range", lastline);
+ if (fputs(p, ofp) == EOF)
+ break;
+ }
+ if (!sflag)
+ (void)printf("%jd\n", (intmax_t)ftello(ofp));
+ if (fclose(ofp) != 0)
+ err(1, "%s", currfile);
+ if (reps-- == 0)
+ break;
+ lastline += tgtline;
+ }
+}
diff --git a/usr.bin/ctags/C.c b/usr.bin/ctags/C.c
new file mode 100644
index 0000000..9d32b22
--- /dev/null
+++ b/usr.bin/ctags/C.c
@@ -0,0 +1,573 @@
+/* $NetBSD: C.c,v 1.19 2009/07/13 19:05:40 roy Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)C.c 8.4 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: C.c,v 1.19 2009/07/13 19:05:40 roy Exp $");
+#endif
+#endif /* not lint */
+
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ctags.h"
+
+static int func_entry(void);
+static void hash_entry(void);
+static void skip_string(int);
+static int str_entry(int);
+
+/*
+ * c_entries --
+ * read .c and .h files and call appropriate routines
+ */
+void
+c_entries(void)
+{
+ int c; /* current character */
+ int level; /* brace level */
+ int token; /* if reading a token */
+ int t_def; /* if reading a typedef */
+ int t_level; /* typedef's brace level */
+ char *sp; /* buffer pointer */
+ char tok[MAXTOKEN]; /* token buffer */
+
+ lineftell = ftell(inf);
+ sp = tok; token = t_def = NO; t_level = -1; level = 0; lineno = 1;
+ while (GETC(!=, EOF)) {
+ switch (c) {
+ /*
+ * Here's where it DOESN'T handle: {
+ * foo(a)
+ * {
+ * #ifdef notdef
+ * }
+ * #endif
+ * if (a)
+ * puts("hello, world");
+ * }
+ */
+ case '{':
+ ++level;
+ goto endtok;
+ case '}':
+ /*
+ * if level goes below zero, try and fix
+ * it, even though we've already messed up
+ */
+ if (--level < 0)
+ level = 0;
+ goto endtok;
+
+ case '\n':
+ SETLINE;
+ /*
+ * the above 3 cases are similar in that they
+ * are special characters that also end tokens.
+ */
+ endtok: if (sp > tok) {
+ *sp = EOS;
+ token = YES;
+ sp = tok;
+ }
+ else
+ token = NO;
+ continue;
+
+ /*
+ * We ignore quoted strings and character constants
+ * completely.
+ */
+ case '"':
+ case '\'':
+ (void)skip_string(c);
+ break;
+
+ /*
+ * comments can be fun; note the state is unchanged after
+ * return, in case we found:
+ * "foo() XX comment XX { int bar; }"
+ */
+ case '/':
+ if (GETC(==, '*')) {
+ skip_comment(c);
+ continue;
+ } else if (c == '/') {
+ skip_comment(c);
+ continue;
+ }
+ (void)ungetc(c, inf);
+ c = '/';
+ goto storec;
+
+ /* hash marks flag #define's. */
+ case '#':
+ if (sp == tok) {
+ hash_entry();
+ break;
+ }
+ goto storec;
+
+ /*
+ * if we have a current token, parenthesis on
+ * level zero indicates a function.
+ */
+ case '(':
+ do c = getc(inf);
+ while (c != EOF && iswhite(c));
+ if (c == '*')
+ break;
+ if (c != EOF)
+ ungetc(c, inf);
+ if (!level && token) {
+ int curline;
+
+ if (sp != tok)
+ *sp = EOS;
+ /*
+ * grab the line immediately, we may
+ * already be wrong, for example,
+ * foo\n
+ * (arg1,
+ */
+ get_line();
+ curline = lineno;
+ if (func_entry()) {
+ ++level;
+ pfnote(tok, curline);
+ }
+ break;
+ }
+ goto storec;
+
+ /*
+ * semi-colons indicate the end of a typedef; if we find a
+ * typedef we search for the next semi-colon of the same
+ * level as the typedef. Ignoring "structs", they are
+ * tricky, since you can find:
+ *
+ * "typedef long time_t;"
+ * "typedef unsigned int u_int;"
+ * "typedef unsigned int u_int [10];"
+ *
+ * If looking at a typedef, we save a copy of the last token
+ * found. Then, when we find the ';' we take the current
+ * token if it starts with a valid token name, else we take
+ * the one we saved. There's probably some reasonable
+ * alternative to this...
+ */
+ case ';':
+ if (t_def && level == t_level) {
+ t_def = NO;
+ get_line();
+ if (sp != tok)
+ *sp = EOS;
+ pfnote(tok, lineno);
+ break;
+ }
+ goto storec;
+
+ /*
+ * store characters until one that can't be part of a token
+ * comes along; check the current token against certain
+ * reserved words.
+ */
+ default:
+ storec: if (c == EOF)
+ break;
+ if (!intoken(c)) {
+ if (sp == tok)
+ break;
+ *sp = EOS;
+ if (tflag) {
+ /* no typedefs inside typedefs */
+ if (!t_def &&
+ !memcmp(tok, "typedef",8)) {
+ t_def = YES;
+ t_level = level;
+ break;
+ }
+ /* catch "typedef struct" */
+ if ((!t_def || t_level <= level)
+ && (!memcmp(tok, "struct", 7)
+ || !memcmp(tok, "union", 6)
+ || !memcmp(tok, "enum", 5))) {
+ /*
+ * get line immediately;
+ * may change before '{'
+ */
+ get_line();
+ if (str_entry(c))
+ ++level;
+ break;
+ /* } */
+ }
+ }
+ sp = tok;
+ }
+ else if (sp != tok || begtoken(c)) {
+ if (sp < tok + sizeof tok)
+ *sp++ = c;
+ token = YES;
+ }
+ continue;
+ }
+
+ sp = tok;
+ token = NO;
+ }
+}
+
+/*
+ * func_entry --
+ * handle a function reference
+ */
+static int
+func_entry(void)
+{
+ int c; /* current character */
+ int level = 0; /* for matching '()' */
+ static char attribute[] = "__attribute__";
+ char maybe_attribute[sizeof attribute + 1],
+ *anext;
+
+ /*
+ * Find the end of the assumed function declaration.
+ * Note that ANSI C functions can have type definitions so keep
+ * track of the parentheses nesting level.
+ */
+ while (GETC(!=, EOF)) {
+ switch (c) {
+ case '\'':
+ case '"':
+ /* skip strings and character constants */
+ skip_string(c);
+ break;
+ case '/':
+ /* skip comments */
+ if (GETC(==, '*'))
+ skip_comment(c);
+ else if (c == '/')
+ skip_comment(c);
+ break;
+ case '(':
+ level++;
+ break;
+ case ')':
+ if (level == 0)
+ goto fnd;
+ level--;
+ break;
+ case '\n':
+ SETLINE;
+ }
+ }
+ return (NO);
+fnd:
+ /*
+ * we assume that the character after a function's right paren
+ * is a token character if it's a function and a non-token
+ * character if it's a declaration. Comments don't count...
+ */
+ for (anext = maybe_attribute;;) {
+ while (GETC(!=, EOF) && iswhite(c))
+ if (c == '\n')
+ SETLINE;
+ if (c == EOF)
+ return NO;
+ /*
+ * Recognize the gnu __attribute__ extension, which would
+ * otherwise make the heuristic test DTWT
+ */
+ if (anext == maybe_attribute) {
+ if (intoken(c)) {
+ *anext++ = c;
+ continue;
+ }
+ } else {
+ if (intoken(c)) {
+ if (anext - maybe_attribute
+ < (ptrdiff_t)(sizeof attribute - 1))
+ *anext++ = c;
+ else break;
+ continue;
+ } else {
+ *anext++ = '\0';
+ if (strcmp(maybe_attribute, attribute) == 0) {
+ (void)ungetc(c, inf);
+ return NO;
+ }
+ break;
+ }
+ }
+ if (intoken(c) || c == '{')
+ break;
+ if (c == '/' && GETC(==, '*'))
+ skip_comment(c);
+ else if (c == '/')
+ skip_comment(c);
+ else { /* don't ever "read" '/' */
+ (void)ungetc(c, inf);
+ return (NO);
+ }
+ }
+ if (c != '{')
+ (void)skip_key('{');
+ return (YES);
+}
+
+/*
+ * hash_entry --
+ * handle a line starting with a '#'
+ */
+static void
+hash_entry(void)
+{
+ int c; /* character read */
+ int curline; /* line started on */
+ char *sp; /* buffer pointer */
+ char tok[MAXTOKEN]; /* storage buffer */
+
+ curline = lineno;
+ do if (GETC(==, EOF))
+ return;
+ while(c != '\n' && iswhite(c));
+ ungetc(c, inf);
+ for (sp = tok;;) { /* get next token */
+ if (GETC(==, EOF))
+ return;
+ if (iswhite(c))
+ break;
+ if (sp < tok + sizeof tok)
+ *sp++ = c;
+ }
+ if(sp >= tok + sizeof tok)
+ --sp;
+ *sp = EOS;
+ if (memcmp(tok, "define", 6)) /* only interested in #define's */
+ goto skip;
+ for (;;) { /* this doesn't handle "#define \n" */
+ if (GETC(==, EOF))
+ return;
+ if (!iswhite(c))
+ break;
+ }
+ for (sp = tok;;) { /* get next token */
+ if(sp < tok + sizeof tok)
+ *sp++ = c;
+ if (GETC(==, EOF))
+ return;
+ /*
+ * this is where it DOESN'T handle
+ * "#define \n"
+ */
+ if (!intoken(c))
+ break;
+ }
+ if(sp >= tok + sizeof tok)
+ --sp;
+ *sp = EOS;
+ if (dflag || c == '(') { /* only want macros */
+ get_line();
+ pfnote(tok, curline);
+ }
+skip: if (c == '\n') { /* get rid of rest of define */
+ SETLINE
+ if (*(sp - 1) != '\\')
+ return;
+ }
+ (void)skip_key('\n');
+}
+
+/*
+ * str_entry --
+ * handle a struct, union or enum entry
+ */
+static int
+str_entry(int c /* current character */)
+{
+ int curline; /* line started on */
+ char *sp; /* buffer pointer */
+ char tok[LINE_MAX]; /* storage buffer */
+
+ curline = lineno;
+ while (iswhite(c))
+ if (GETC(==, EOF))
+ return (NO);
+ if (c == '{') /* it was "struct {" */
+ return (YES);
+ for (sp = tok;;) { /* get next token */
+ *sp++ = c;
+ if (GETC(==, EOF))
+ return (NO);
+ if (!intoken(c))
+ break;
+ }
+ switch (c) {
+ case '{': /* it was "struct foo{" */
+ --sp;
+ break;
+ case '\n': /* it was "struct foo\n" */
+ SETLINE;
+ /*FALLTHROUGH*/
+ default: /* probably "struct foo " */
+ while (GETC(!=, EOF))
+ if (!iswhite(c))
+ break;
+ if (c != '{') {
+ (void)ungetc(c, inf);
+ return (NO);
+ }
+ }
+ *sp = EOS;
+ pfnote(tok, curline);
+ return (YES);
+}
+
+/*
+ * skip_comment --
+ * skip over comment
+ */
+void
+skip_comment(int commenttype)
+{
+ int c; /* character read */
+ int star; /* '*' flag */
+
+ for (star = 0; GETC(!=, EOF);)
+ switch(c) {
+ /* comments don't nest, nor can they be escaped. */
+ case '*':
+ star = YES;
+ break;
+ case '/':
+ if (commenttype == '*' && star)
+ return;
+ break;
+ case '\n':
+ if (commenttype == '/') {
+ /*
+ * we don't really parse C, so sometimes it
+ * is necessary to see the newline
+ */
+ ungetc(c, inf);
+ return;
+ }
+ SETLINE;
+ /*FALLTHROUGH*/
+ default:
+ star = NO;
+ break;
+ }
+}
+
+/*
+ * skip_string --
+ * skip to the end of a string or character constant.
+ */
+void
+skip_string(int key)
+{
+ int c,
+ skip;
+
+ for (skip = NO; GETC(!=, EOF); )
+ switch (c) {
+ case '\\': /* a backslash escapes anything */
+ skip = !skip; /* we toggle in case it's "\\" */
+ break;
+ case '\n':
+ SETLINE;
+ /*FALLTHROUGH*/
+ default:
+ if (c == key && !skip)
+ return;
+ skip = NO;
+ }
+}
+
+/*
+ * skip_key --
+ * skip to next char "key"
+ */
+int
+skip_key(int key)
+{
+ int c,
+ skip,
+ retval;
+
+ for (skip = retval = NO; GETC(!=, EOF);)
+ switch(c) {
+ case '\\': /* a backslash escapes anything */
+ skip = !skip; /* we toggle in case it's "\\" */
+ break;
+ case ';': /* special case for yacc; if one */
+ case '|': /* of these chars occurs, we may */
+ retval = YES; /* have moved out of the rule */
+ break; /* not used by C */
+ case '\'':
+ case '"':
+ /* skip strings and character constants */
+ skip_string(c);
+ break;
+ case '/':
+ /* skip comments */
+ if (GETC(==, '*')) {
+ skip_comment(c);
+ break;
+ } else if (c == '/') {
+ skip_comment(c);
+ break;
+ }
+ (void)ungetc(c, inf);
+ c = '/';
+ goto norm;
+ case '\n':
+ SETLINE;
+ /*FALLTHROUGH*/
+ default:
+ norm:
+ if (c == key && !skip)
+ return (retval);
+ skip = NO;
+ }
+ return (retval);
+}
diff --git a/usr.bin/ctags/ctags.1 b/usr.bin/ctags/ctags.1
new file mode 100644
index 0000000..1dd72e8
--- /dev/null
+++ b/usr.bin/ctags/ctags.1
@@ -0,0 +1,225 @@
+.\" $NetBSD: ctags.1,v 1.18 2017/07/03 21:34:19 wiz Exp $
+.\"
+.\" Copyright (c) 1987, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)ctags.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt CTAGS 1
+.Os
+.Sh NAME
+.Nm ctags
+.Nd create a tags file
+.Sh SYNOPSIS
+.Nm
+.Op Fl BFadtuwvx
+.Op Fl f Ar tagsfile
+.Ar name ...
+.Sh DESCRIPTION
+.Nm
+makes a tags file for
+.Xr ex 1
+from the specified C,
+Pascal, Fortran,
+.Tn YACC ,
+lex, and lisp sources.
+A tags file gives the locations of specified objects in a group of files.
+Each line of the tags file contains the object name, the file in which it
+is defined, and a search pattern for the object definition, separated by
+white-space.
+Using the
+.Ar tags
+file,
+.Xr ex 1
+can quickly locate these object definitions.
+Depending upon the options provided to
+.Nm ,
+objects will consist of subroutines, typedefs, defines, structs,
+enums and unions.
+.Bl -tag -width Ds
+.It Fl B
+use backward searching patterns
+.Pq Li ?...? .
+.It Fl F
+use forward searching patterns
+.Pq Li /.../
+(the default).
+.It Fl a
+append to
+.Ar tags
+file.
+.It Fl d
+create tags for
+.Li #defines
+that don't take arguments;
+.Li #defines
+that take arguments are tagged automatically.
+.It Fl f
+Places the tag descriptions in a file called
+.Ar tagsfile .
+The default behaviour is to place them in a file called
+.Ar tags .
+.It Fl t
+create tags for typedefs, structs, unions, and enums.
+.It Fl u
+update the specified files in the
+.Ar tags
+file, that is, all
+references to them are deleted, and the new values are appended to the
+file.
+(Beware: this option is implemented in a way which is rather
+slow; it is usually faster to simply rebuild the
+.Ar tags
+file.)
+.It Fl v
+An index of the form expected by
+.Xr vgrind 1
+is produced on the standard output.
+This listing
+contains the object name, file name, and page number (assuming 64
+line pages).
+Since the output will be sorted into lexicographic order,
+it may be desired to run the output through
+.Xr sort 1 .
+Sample use:
+.Bd -literal -offset indent
+ctags \-v files \&| sort \-f > index
+vgrind \-x index
+.Ed
+.It Fl w
+suppress warning diagnostics.
+.It Fl x
+.Nm
+produces a list of object
+names, the line number and file name on which each is defined, as well
+as the text of that line and prints this on the standard output.
+This
+is a simple index which can be printed out as an off-line readable
+function index.
+.El
+.Pp
+Files whose names end in
+.Sq \&.c
+or
+.Sq \&.h
+are assumed to be C
+source files and are searched for C style routine and macro definitions.
+Files whose names end in
+.Sq \&.y
+are assumed to be
+.Tn YACC
+source files.
+Files whose names end in
+.Sq \&.l
+are assumed to be lisp files if their
+first non-blank character is
+.Sq \&; ,
+.Sq \&( ,
+or
+.Sq \&[ ,
+otherwise, they are
+treated as lex files.
+Other files are first examined to see if they
+contain any Pascal or Fortran routine definitions, and, if not, are
+searched for C style definitions.
+.Pp
+The tag
+.Li main
+is treated specially in C programs.
+The tag formed
+is created by prepending
+.Ar M
+to the name of the file, with the
+trailing
+.Sq \&.c
+and any leading pathname components removed.
+This
+makes use of
+.Nm
+practical in directories with more than one
+program.
+.Pp
+Yacc and lex files each have a special tag.
+.Ar Yyparse
+is the start
+of the second section of the yacc file, and
+.Ar yylex
+is the start of
+the second section of the lex file.
+.Sh FILES
+.Bl -tag -width tags -compact
+.It Pa tags
+default output tags file
+.El
+.Sh EXIT STATUS
+.Ex -std
+Duplicate objects are not considered errors.
+.Sh SEE ALSO
+.Xr ex 1 ,
+.Xr vi 1
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 3.0 .
+.Sh BUGS
+Recognition of
+.Em functions ,
+.Em subroutines
+and
+.Em procedures
+for
+.Tn FORTRAN
+and Pascal is done in a very simpleminded way.
+No attempt
+is made to deal with block structure; if you have two Pascal procedures
+in different blocks with the same name you lose.
+.Nm
+doesn't
+understand about Pascal types.
+.Pp
+The method of deciding whether to look for C, Pascal or
+.Tn FORTRAN
+functions is a hack.
+.Pp
+.Nm
+relies on the input being well formed, and any syntactical
+errors will completely confuse it.
+It also finds some legal syntax
+confusing; for example, since it doesn't understand
+.Li #ifdef Ns 's
+(incidentally, that's a feature, not a bug), any code with unbalanced
+braces inside
+.Li #ifdef Ns 's
+will cause it to become somewhat disoriented.
+In a similar fashion, multiple line changes within a definition will
+cause it to enter the last line of the object, rather than the first, as
+the searching pattern.
+The last line of multiple line
+.Li typedef Ns 's
+will similarly be noted.
diff --git a/usr.bin/ctags/ctags.c b/usr.bin/ctags/ctags.c
new file mode 100644
index 0000000..5054b1b
--- /dev/null
+++ b/usr.bin/ctags/ctags.c
@@ -0,0 +1,275 @@
+/* $NetBSD: ctags.c,v 1.12 2008/07/21 14:19:22 lukem Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__COPYRIGHT) && !defined(lint)
+__COPYRIGHT("@(#) Copyright (c) 1987, 1993, 1994, 1995\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)ctags.c 8.4 (Berkeley) 2/7/95";
+#endif
+__RCSID("$NetBSD: ctags.c,v 1.12 2008/07/21 14:19:22 lukem Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "ctags.h"
+
+/*
+ * ctags: create a tags file
+ */
+
+NODE *head; /* head of the sorted binary tree */
+
+ /* boolean "func" (see init()) */
+bool _wht[256], _etk[256], _itk[256], _btk[256], _gd[256];
+
+FILE *inf; /* ioptr for current input file */
+FILE *outf; /* ioptr for tags file */
+
+long lineftell; /* ftell after getc( inf ) == '\n' */
+
+int lineno; /* line number of current line */
+int dflag; /* -d: non-macro defines */
+int tflag; /* -t: create tags for typedefs */
+int vflag; /* -v: vgrind style index output */
+int wflag; /* -w: suppress warnings */
+int xflag; /* -x: cxref style output */
+
+char *curfile; /* current input file name */
+char searchar = '/'; /* use /.../ searches by default */
+char lbuf[LINE_MAX];
+
+void init(void);
+void find_entries(char *);
+
+int
+main(int argc, char **argv)
+{
+ static const char *outfile = "tags"; /* output file */
+ int aflag; /* -a: append to tags */
+ int uflag; /* -u: update tags */
+ int exit_val; /* exit value */
+ int step; /* step through args */
+ int ch; /* getopts char */
+ char cmd[100]; /* too ugly to explain */
+
+ aflag = uflag = NO;
+ while ((ch = getopt(argc, argv, "BFadf:tuwvx")) != -1)
+ switch(ch) {
+ case 'B':
+ searchar = '?';
+ break;
+ case 'F':
+ searchar = '/';
+ break;
+ case 'a':
+ aflag++;
+ break;
+ case 'd':
+ dflag++;
+ break;
+ case 'f':
+ outfile = optarg;
+ break;
+ case 't':
+ tflag++;
+ break;
+ case 'u':
+ uflag++;
+ break;
+ case 'w':
+ wflag++;
+ break;
+ case 'v':
+ vflag++;
+ case 'x':
+ xflag++;
+ break;
+ case '?':
+ default:
+ goto usage;
+ }
+ argv += optind;
+ argc -= optind;
+ if (!argc) {
+usage: (void)fprintf(stderr,
+ "usage: ctags [-BFadtuwvx] [-f tagsfile] file ...\n");
+ exit(1);
+ }
+
+ init();
+
+ for (exit_val = step = 0; step < argc; ++step)
+ if (!(inf = fopen(argv[step], "r"))) {
+ warn("%s", argv[step]);
+ exit_val = 1;
+ }
+ else {
+ curfile = argv[step];
+ find_entries(argv[step]);
+ (void)fclose(inf);
+ }
+
+ if (head) {
+ if (xflag)
+ put_entries(head);
+ else {
+ if (uflag) {
+ for (step = 0; step < argc; step++) {
+ (void)snprintf(cmd, sizeof(cmd),
+ "mv %s OTAGS; fgrep -v '\t%s\t' OTAGS >%s; rm OTAGS",
+ outfile, argv[step],
+ outfile);
+ system(cmd);
+ }
+ ++aflag;
+ }
+ if (!(outf = fopen(outfile, aflag ? "a" : "w")))
+ err(exit_val, "%s", outfile);
+ put_entries(head);
+ (void)fclose(outf);
+ if (uflag) {
+ (void)snprintf(cmd, sizeof(cmd),
+ "sort -o %s %s", outfile, outfile);
+ system(cmd);
+ }
+ }
+ }
+ exit(exit_val);
+}
+
+/*
+ * init --
+ * this routine sets up the boolean psuedo-functions which work by
+ * setting boolean flags dependent upon the corresponding character.
+ * Every char which is NOT in that string is false with respect to
+ * the pseudo-function. Therefore, all of the array "_wht" is NO
+ * by default and then the elements subscripted by the chars in
+ * CWHITE are set to YES. Thus, "_wht" of a char is YES if it is in
+ * the string CWHITE, else NO.
+ */
+void
+init(void)
+{
+ int i;
+ unsigned const char *sp;
+
+ for (i = 0; i < 256; i++) {
+ _wht[i] = _etk[i] = _itk[i] = _btk[i] = NO;
+ _gd[i] = YES;
+ }
+#define CWHITE " \f\t\n"
+ for (sp = CWHITE; *sp; sp++) /* white space chars */
+ _wht[*sp] = YES;
+#define CTOKEN " \t\n\"'#()[]{}=-+%*/&|^~!<>;,.:?"
+ for (sp = CTOKEN; *sp; sp++) /* token ending chars */
+ _etk[*sp] = YES;
+#define CINTOK "ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz0123456789"
+ for (sp = CINTOK; *sp; sp++) /* valid in-token chars */
+ _itk[*sp] = YES;
+#define CBEGIN "ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"
+ for (sp = CBEGIN; *sp; sp++) /* token starting chars */
+ _btk[*sp] = YES;
+#define CNOTGD ",;"
+ for (sp = CNOTGD; *sp; sp++) /* invalid after-function chars */
+ _gd[*sp] = NO;
+}
+
+/*
+ * find_entries --
+ * this routine opens the specified file and calls the function
+ * which searches the file.
+ */
+void
+find_entries(char *file)
+{
+ char *cp;
+
+ lineno = 0; /* should be 1 ?? KB */
+ if ((cp = strrchr(file, '.')) != NULL) {
+ if (cp[1] == 'l' && !cp[2]) {
+ int c;
+
+ for (;;) {
+ if (GETC(==, EOF))
+ return;
+ if (!iswhite(c)) {
+ rewind(inf);
+ break;
+ }
+ }
+#define LISPCHR ";(["
+/* lisp */ if (strchr(LISPCHR, c)) {
+ l_entries();
+ return;
+ }
+/* lex */ else {
+ /*
+ * we search all 3 parts of a lex file
+ * for C references. This may be wrong.
+ */
+ toss_yysec();
+ (void)strlcpy(lbuf, "%%$", sizeof(lbuf));
+ pfnote("yylex", lineno);
+ rewind(inf);
+ }
+ }
+/* yacc */ else if (cp[1] == 'y' && !cp[2]) {
+ /*
+ * we search only the 3rd part of a yacc file
+ * for C references. This may be wrong.
+ */
+ toss_yysec();
+ (void)strlcpy(lbuf, "%%$", sizeof(lbuf));
+ pfnote("yyparse", lineno);
+ y_entries();
+ }
+/* fortran */ else if ((cp[1] != 'c' && cp[1] != 'h') && !cp[2]) {
+ if (PF_funcs())
+ return;
+ rewind(inf);
+ }
+ }
+/* C */ c_entries();
+}
diff --git a/usr.bin/ctags/ctags.h b/usr.bin/ctags/ctags.h
new file mode 100644
index 0000000..6aa2aa1
--- /dev/null
+++ b/usr.bin/ctags/ctags.h
@@ -0,0 +1,92 @@
+/* $NetBSD: ctags.h,v 1.9 2009/07/13 19:05:40 roy Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)ctags.h 8.3 (Berkeley) 4/2/94
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#define bool char
+
+#define YES 1
+#define NO 0
+#define EOS '\0'
+
+#define ENDLINE 50 /* max length of pattern */
+#define MAXTOKEN 250 /* max size of single token */
+
+#define SETLINE {++lineno;lineftell = ftell(inf);}
+#define GETC(op,exp) ((c = getc(inf)) op (int)exp)
+
+#define iswhite(arg) (_wht[(unsigned)arg]) /* T if char is white */
+#define begtoken(arg) (_btk[(unsigned)arg]) /* T if char can start token */
+#define intoken(arg) (_itk[(unsigned)arg]) /* T if char can be in token */
+#define endtoken(arg) (_etk[(unsigned)arg]) /* T if char ends tokens */
+#define isgood(arg) (_gd[(unsigned)arg]) /* T if char can be after ')' */
+
+typedef struct nd_st { /* sorting structure */
+ struct nd_st *left,
+ *right; /* left and right sons */
+ char *entry, /* function or type name */
+ *file, /* file name */
+ *pat; /* search pattern */
+ int lno; /* for -x option */
+ bool been_warned; /* set if noticed dup */
+} NODE;
+
+extern char *curfile; /* current input file name */
+extern NODE *head; /* head of the sorted binary tree */
+extern FILE *inf; /* ioptr for current input file */
+extern FILE *outf; /* ioptr for current output file */
+extern long lineftell; /* ftell after getc( inf ) == '\n' */
+extern int lineno; /* line number of current line */
+extern int dflag; /* -d: non-macro defines */
+extern int tflag; /* -t: create tags for typedefs */
+extern int vflag; /* -v: vgrind style index output */
+extern int wflag; /* -w: suppress warnings */
+extern int xflag; /* -x: cxref style output */
+extern bool _wht[], _etk[], _itk[], _btk[], _gd[];
+extern char lbuf[LINE_MAX];
+extern char *lbp;
+extern char searchar; /* ex search character */
+
+extern int cicmp(const char *);
+extern void get_line(void);
+extern void pfnote(const char *, int);
+extern int skip_key(int);
+extern void put_entries(NODE *);
+extern void toss_yysec(void);
+extern void l_entries(void);
+extern void y_entries(void);
+extern int PF_funcs(void);
+extern void c_entries(void);
+extern void skip_comment(int);
diff --git a/usr.bin/ctags/fortran.c b/usr.bin/ctags/fortran.c
new file mode 100644
index 0000000..2a9cefd
--- /dev/null
+++ b/usr.bin/ctags/fortran.c
@@ -0,0 +1,174 @@
+/* $NetBSD: fortran.c,v 1.11 2009/07/13 19:05:40 roy Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)fortran.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: fortran.c,v 1.11 2009/07/13 19:05:40 roy Exp $");
+#endif
+#endif /* not lint */
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ctags.h"
+
+static void takeprec(void);
+
+char *lbp; /* line buffer pointer */
+
+int
+PF_funcs(void)
+{
+ bool pfcnt; /* pascal/fortran functions found */
+ char *cp;
+ char tok[MAXTOKEN];
+
+ for (pfcnt = NO;;) {
+ lineftell = ftell(inf);
+ if (!fgets(lbuf, sizeof(lbuf), inf))
+ return (pfcnt);
+ ++lineno;
+ lbp = lbuf;
+ if (*lbp == '%') /* Ratfor escape to fortran */
+ ++lbp;
+ for (; isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ if (!*lbp)
+ continue;
+ switch (*lbp | ' ') { /* convert to lower-case */
+ case 'c':
+ if (cicmp("complex") || cicmp("character"))
+ takeprec();
+ break;
+ case 'd':
+ if (cicmp("double")) {
+ for (; isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ if (!*lbp)
+ continue;
+ if (cicmp("precision"))
+ break;
+ continue;
+ }
+ break;
+ case 'i':
+ if (cicmp("integer"))
+ takeprec();
+ break;
+ case 'l':
+ if (cicmp("logical"))
+ takeprec();
+ break;
+ case 'r':
+ if (cicmp("real"))
+ takeprec();
+ break;
+ }
+ for (; isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ if (!*lbp)
+ continue;
+ switch (*lbp | ' ') {
+ case 'f':
+ if (cicmp("function"))
+ break;
+ continue;
+ case 'p':
+ if (cicmp("program") || cicmp("procedure"))
+ break;
+ continue;
+ case 's':
+ if (cicmp("subroutine"))
+ break;
+ default:
+ continue;
+ }
+ for (; isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ if (!*lbp)
+ continue;
+ for (cp = lbp + 1; *cp && intoken(*cp); ++cp)
+ continue;
+ if ((cp = lbp + 1) != NULL)
+ continue;
+ *cp = EOS;
+ (void)strlcpy(tok, lbp, sizeof(tok));
+ get_line(); /* process line for ex(1) */
+ pfnote(tok, lineno);
+ pfcnt = YES;
+ }
+ /*NOTREACHED*/
+}
+
+/*
+ * cicmp --
+ * do case-independent strcmp
+ */
+int
+cicmp(const char *cp)
+{
+ int len;
+ char *bp;
+
+ for (len = 0, bp = lbp; *cp && (*cp &~ ' ') == (*bp++ &~ ' ');
+ ++cp, ++len)
+ continue;
+ if (!*cp) {
+ lbp += len;
+ return (YES);
+ }
+ return (NO);
+}
+
+static void
+takeprec(void)
+{
+ for (; isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ if (*lbp == '*') {
+ for (++lbp; isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ if (!isdigit((unsigned char)*lbp))
+ --lbp; /* force failure */
+ else
+ while (isdigit((unsigned char)*++lbp))
+ continue;
+ }
+}
diff --git a/usr.bin/ctags/lisp.c b/usr.bin/ctags/lisp.c
new file mode 100644
index 0000000..d143c6c
--- /dev/null
+++ b/usr.bin/ctags/lisp.c
@@ -0,0 +1,112 @@
+/* $NetBSD: lisp.c,v 1.11 2009/07/13 19:05:40 roy Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)lisp.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: lisp.c,v 1.11 2009/07/13 19:05:40 roy Exp $");
+#endif
+#endif /* not lint */
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ctags.h"
+
+/*
+ * lisp tag functions
+ * just look for (def or (DEF
+ */
+void
+l_entries(void)
+{
+ int special;
+ char *cp;
+ char savedc;
+ char tok[MAXTOKEN];
+
+ for (;;) {
+ lineftell = ftell(inf);
+ if (!fgets(lbuf, sizeof(lbuf), inf))
+ return;
+ ++lineno;
+ lbp = lbuf;
+ if (!cicmp("(def"))
+ continue;
+ special = NO;
+ switch(*lbp | ' ') {
+ case 'm':
+ if (cicmp("method"))
+ special = YES;
+ break;
+ case 'w':
+ if (cicmp("wrapper") || cicmp("whopper"))
+ special = YES;
+ }
+ for (; !isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ for (; isspace((unsigned char)*lbp); ++lbp)
+ continue;
+ for (cp = lbp; *cp && *cp != '\n'; ++cp)
+ continue;
+ *cp = EOS;
+ if (special) {
+ if (!(cp = strchr(lbp, ')')))
+ continue;
+ for (; cp >= lbp && *cp != ':'; --cp)
+ continue;
+ if (cp < lbp)
+ continue;
+ lbp = cp;
+ for (; *cp && *cp != ')' && *cp != ' '; ++cp)
+ continue;
+ }
+ else
+ for (cp = lbp + 1;
+ *cp && *cp != '(' && *cp != ' '; ++cp)
+ continue;
+ savedc = *cp;
+ *cp = EOS;
+ (void)strlcpy(tok, lbp, sizeof(tok));
+ *cp = savedc;
+ get_line();
+ pfnote(tok, lineno);
+ }
+ /*NOTREACHED*/
+}
diff --git a/usr.bin/ctags/print.c b/usr.bin/ctags/print.c
new file mode 100644
index 0000000..86fcd3a
--- /dev/null
+++ b/usr.bin/ctags/print.c
@@ -0,0 +1,121 @@
+/* $NetBSD: print.c,v 1.10 2009/07/13 19:05:40 roy Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)print.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: print.c,v 1.10 2009/07/13 19:05:40 roy Exp $");
+#endif
+#endif /* not lint */
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ctags.h"
+
+/*
+ * get_line --
+ * get the line the token of interest occurred on,
+ * prepare it for printing.
+ */
+void
+get_line(void)
+{
+ long saveftell;
+ int c;
+ int cnt;
+ char *cp;
+
+ saveftell = ftell(inf);
+ (void)fseek(inf, lineftell, SEEK_SET);
+ if (xflag)
+ for (cp = lbuf; GETC(!=, '\n'); *cp++ = c)
+ continue;
+ /*
+ * do all processing here, so we don't step through the
+ * line more than once; means you don't call this routine
+ * unless you're sure you've got a keeper.
+ */
+ else for (cnt = 0, cp = lbuf; GETC(!=, EOF) && cnt < ENDLINE; ++cnt) {
+ if (c == '\\') { /* backslashes */
+ if (cnt > ENDLINE - 2)
+ break;
+ *cp++ = '\\'; *cp++ = '\\';
+ ++cnt;
+ }
+ else if (c == (int)searchar) { /* search character */
+ if (cnt > ENDLINE - 2)
+ break;
+ *cp++ = '\\'; *cp++ = c;
+ ++cnt;
+ }
+ else if (c == '\n') { /* end of keep */
+ *cp++ = '$'; /* can find whole line */
+ break;
+ }
+ else
+ *cp++ = c;
+ }
+ *cp = EOS;
+ (void)fseek(inf, saveftell, SEEK_SET);
+}
+
+/*
+ * put_entries --
+ * write out the tags
+ */
+void
+put_entries(NODE *node)
+{
+
+ if (node->left)
+ put_entries(node->left);
+ if (vflag)
+ printf("%s %s %d\n",
+ node->entry, node->file, (node->lno + 63) / 64);
+ else if (xflag)
+ printf("%-16s%4d %-16s %s\n",
+ node->entry, node->lno, node->file, node->pat);
+ else
+ fprintf(outf, "%s\t%s\t%c^%s%c\n",
+ node->entry, node->file, searchar, node->pat, searchar);
+ if (node->right)
+ put_entries(node->right);
+}
diff --git a/usr.bin/ctags/test/ctags.test b/usr.bin/ctags/test/ctags.test
new file mode 100644
index 0000000..658a073
--- /dev/null
+++ b/usr.bin/ctags/test/ctags.test
@@ -0,0 +1,69 @@
+/* $NetBSD: ctags.test,v 1.2 1995/03/26 20:14:14 glass Exp $ */
+
+int bar = (1 + 5);
+
+FOO("here is a #define test: ) {");
+char sysent[20];
+int nsysent = sizeof (sysent) / sizeof (sysent[0]);
+/*
+ * now is the time for a comment.
+ * four lines in length...
+ */struct struct_xtra{int list;};r4(x,y){};typedef struct{int bar;}struct_xxe;
+#define FOO BAR
+struct struct_three {
+ int list;
+};
+#define SINGLE
+int BAD();
+enum color {red, green, gold, brown};
+char qq[] = " quote(one,two) {int bar;} ";
+typedef struct {
+ int bar;
+ struct struct_two {
+ int foo;
+ union union_3 {
+ struct struct_three entry;
+ char size[25];
+ };
+ struct last {
+ struct struct_three xentry;
+ char list[34];
+ };
+ };
+} struct_one;
+#define TWOLINE ((MAXLIST + FUTURE + 15) \
+ / (time_to_live ? 3 : 4))
+#if (defined(BAR))
+int bar;
+#endif
+#define MULTIPLE {\
+ multiple(one,two); \
+ lineno++; \
+ callroute(one,two); \
+}
+#if defined(BAR)
+int bar;
+#endif
+union union_one {
+ struct struct_three s3;
+ char foo[25];
+};
+#define XYZ(A,B) (A + B / 2) * (3 - 26 + l_lineno)
+routine1(one,two) /* comments here are fun... */
+ struct {
+ int entry;
+ char bar[34];
+ } *one;
+ char two[10];
+{
+typedef unsigned char u_char;
+ register struct buf *bp;
+ five(one,two);
+}
+ routine2 (one,two) { puts("hello\n"); }
+ routine3
+(one,
+two) { puts("world\n"); }
+routine4(int one, char (*two)(void)) /* test ANSI arguments */
+{
+}
diff --git a/usr.bin/ctags/tree.c b/usr.bin/ctags/tree.c
new file mode 100644
index 0000000..93f4a41
--- /dev/null
+++ b/usr.bin/ctags/tree.c
@@ -0,0 +1,145 @@
+/* $NetBSD: tree.c,v 1.12 2006/04/05 19:38:47 dsl Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)tree.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: tree.c,v 1.12 2006/04/05 19:38:47 dsl Exp $");
+#endif
+#endif /* not lint */
+
+#include <err.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ctags.h"
+
+static void add_node(NODE *, NODE *);
+static void free_tree(NODE *);
+
+/*
+ * pfnote --
+ * enter a new node in the tree
+ */
+void
+pfnote(const char *name, int ln)
+{
+ NODE *np;
+ char *fp;
+ char nbuf[MAXTOKEN];
+
+ /*NOSTRICT*/
+ if (!(np = (NODE *)malloc(sizeof(NODE)))) {
+ warnx("too many entries to sort");
+ put_entries(head);
+ free_tree(head);
+ /*NOSTRICT*/
+ if (!(head = np = (NODE *)malloc(sizeof(NODE))))
+ err(1, "out of space");
+ }
+ if (!xflag && !strcmp(name, "main")) {
+ if (!(fp = strrchr(curfile, '/')))
+ fp = curfile;
+ else
+ ++fp;
+ (void)snprintf(nbuf, sizeof(nbuf), "M%s", fp);
+ fp = strrchr(nbuf, '.');
+ if (fp && !fp[2])
+ *fp = EOS;
+ name = nbuf;
+ }
+ if (!(np->entry = strdup(name)))
+ err(1, "strdup");
+ np->file = curfile;
+ np->lno = ln;
+ np->left = np->right = 0;
+ if (!(np->pat = strdup(lbuf)))
+ err(1, "strdup");
+ if (!head)
+ head = np;
+ else
+ add_node(np, head);
+}
+
+static void
+add_node(NODE *node, NODE *cur_node)
+{
+ int dif;
+
+ dif = strcmp(node->entry, cur_node->entry);
+ if (!dif) {
+ if (node->file == cur_node->file) {
+ if (!wflag)
+ fprintf(stderr, "Duplicate entry in file %s, line %d: %s\nSecond entry ignored\n", node->file, lineno, node->entry);
+ return;
+ }
+ if (!cur_node->been_warned)
+ if (!wflag)
+ fprintf(stderr, "Duplicate entry in files %s and %s: %s (Warning only)\n", node->file, cur_node->file, node->entry);
+ cur_node->been_warned = YES;
+ }
+ else if (dif < 0) {
+ if (cur_node->left)
+ add_node(node, cur_node->left);
+ else
+ cur_node->left = node;
+ } else {
+ if (cur_node->right)
+ add_node(node, cur_node->right);
+ else
+ cur_node->right = node;
+ }
+}
+
+static void
+free_tree(NODE *node)
+{
+ NODE *nnode;
+
+ for (; node != NULL; node = nnode) {
+ nnode = node->left;
+ if (node->right) {
+ if (nnode == NULL)
+ nnode = node->right;
+ else
+ free_tree(node->right);
+ }
+ free(node);
+ }
+}
diff --git a/usr.bin/ctags/yacc.c b/usr.bin/ctags/yacc.c
new file mode 100644
index 0000000..d17fa99
--- /dev/null
+++ b/usr.bin/ctags/yacc.c
@@ -0,0 +1,160 @@
+/* $NetBSD: yacc.c,v 1.12 2009/07/13 19:05:40 roy Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)yacc.c 8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: yacc.c,v 1.12 2009/07/13 19:05:40 roy Exp $");
+#endif
+#endif /* not lint */
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ctags.h"
+
+/*
+ * y_entries:
+ * find the yacc tags and put them in.
+ */
+void
+y_entries(void)
+{
+ int c;
+ char *sp;
+ bool in_rule;
+ char tok[MAXTOKEN];
+
+ in_rule = NO;
+
+ while (GETC(!=, EOF))
+ switch (c) {
+ case '\n':
+ SETLINE;
+ /* FALLTHROUGH */
+ case ' ':
+ case '\f':
+ case '\r':
+ case '\t':
+ break;
+ case '{':
+ if (skip_key('}'))
+ in_rule = NO;
+ break;
+ case '\'':
+ case '"':
+ if (skip_key(c))
+ in_rule = NO;
+ break;
+ case '%':
+ if (GETC(==, '%'))
+ return;
+ (void)ungetc(c, inf);
+ break;
+ case '/':
+ if (GETC(==, '*'))
+ skip_comment('*');
+ else
+ (void)ungetc(c, inf);
+ break;
+ case '|':
+ case ';':
+ in_rule = NO;
+ break;
+ default:
+ if (in_rule || (!isalpha(c) && c != '.' && c != '_'))
+ break;
+ sp = tok;
+ *sp++ = c;
+ while (GETC(!=, EOF) && (intoken(c) || c == '.'))
+ *sp++ = c;
+ *sp = EOS;
+ get_line(); /* may change before ':' */
+ if (c == EOF)
+ return;
+ while (iswhite(c)) {
+ if (c == '\n')
+ SETLINE;
+ if (GETC(==, EOF))
+ return;
+ }
+ if (c == ':') {
+ pfnote(tok, lineno);
+ in_rule = YES;
+ }
+ else
+ (void)ungetc(c, inf);
+ }
+}
+
+/*
+ * toss_yysec --
+ * throw away lines up to the next "\n%%\n"
+ */
+void
+toss_yysec(void)
+{
+ int c; /* read character */
+ int state;
+
+ /*
+ * state == 0 : waiting
+ * state == 1 : received a newline
+ * state == 2 : received first %
+ * state == 3 : received second %
+ */
+ lineftell = ftell(inf);
+ for (state = 0; GETC(!=, EOF);)
+ switch (c) {
+ case '\n':
+ ++lineno;
+ lineftell = ftell(inf);
+ if (state == 3) /* done! */
+ return;
+ state = 1; /* start over */
+ break;
+ case '%':
+ if (state) /* if 1 or 2 */
+ ++state; /* goto 3 */
+ break;
+ default:
+ state = 0; /* reset */
+ break;
+ }
+}
diff --git a/usr.bin/cut/cut.1 b/usr.bin/cut/cut.1
new file mode 100644
index 0000000..a3dbf55
--- /dev/null
+++ b/usr.bin/cut/cut.1
@@ -0,0 +1,126 @@
+.\" $NetBSD: cut.1,v 1.18 2012/06/20 17:53:19 wiz Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)cut.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 12, 2012
+.Dt CUT 1
+.Os
+.Sh NAME
+.Nm cut
+.Nd select portions of each line of a file
+.Sh SYNOPSIS
+.Nm
+.Fl b Ar list
+.Op Fl n
+.Op Ar
+.Nm
+.Fl c Ar list
+.Op Ar
+.Nm
+.Fl f Ar list
+.Op Fl d Ar string
+.Op Fl s
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility selects portions of each line (as specified by
+.Ar list )
+from each
+.Ar file
+and writes them to the
+standard output.
+If the
+.Ar file
+argument is a single dash
+.Pq Sq -
+or no
+.Ar file
+arguments were specified, lines are read from the standard input.
+The items specified by
+.Ar list
+can be in terms of column position or in terms of fields delimited
+by a special character.
+Column and field numbering start from 1.
+.Pp
+.Ar list
+is a comma or whitespace separated set of increasing numbers and/or
+number ranges.
+Number ranges consist of a number, a dash
+.Pq Li \- ,
+and a second number
+and select the columns or fields from the first number to the second,
+inclusive.
+Numbers or number ranges may be preceded by a dash, which selects all
+columns or fields from 1 to the first number.
+Numbers or number ranges may be followed by a dash, which selects all
+columns or fields from the last number to the end of the line.
+Numbers and number ranges may be repeated, overlapping, and in any order.
+It is not an error to select columns or fields not present in the
+input line.
+.Pp
+The options are as follows:
+.Bl -tag -width Fl
+.It Fl b Ar list
+.Ar list
+specifies byte positions.
+.It Fl c Ar list
+.Ar list
+specifies character positions.
+.It Fl d Ar string
+Use the first character of
+.Ar string
+as the field delimiter character.
+The default is the
+.Aq TAB
+character.
+.It Fl f Ar list
+.Ar list
+specifies fields, separated by the field delimiter character.
+The selected fields are output,
+separated by the field delimiter character.
+.It Fl n
+Do not split multi-byte characters.
+.It Fl s
+Suppress lines with no field delimiter characters.
+Unless specified, lines with no delimiters are passed through unmodified.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr paste 1
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/cut/cut.c b/usr.bin/cut/cut.c
new file mode 100644
index 0000000..d84b46e
--- /dev/null
+++ b/usr.bin/cut/cut.c
@@ -0,0 +1,306 @@
+/* $NetBSD: cut.c,v 1.29 2014/02/03 20:22:19 wiz Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam S. Moskowitz of Menlo Consulting and Marciano Pitargue.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cut.c 8.3 (Berkeley) 5/4/95";
+#endif
+__RCSID("$NetBSD: cut.c,v 1.29 2014/02/03 20:22:19 wiz Exp $");
+#endif /* not lint */
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+#include <wchar.h>
+#include <sys/param.h>
+
+static int bflag;
+static int cflag;
+static char dchar;
+static int dflag;
+static int fflag;
+static int sflag;
+
+static void b_cut(FILE *, const char *);
+static void c_cut(FILE *, const char *);
+static void f_cut(FILE *, const char *);
+static void get_list(char *);
+static void usage(void) __dead;
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+ void (*fcn)(FILE *, const char *);
+ int ch, rval;
+
+ fcn = NULL;
+ (void)setlocale(LC_ALL, "");
+
+ dchar = '\t'; /* default delimiter is \t */
+
+ /* Since we don't support multi-byte characters, the -c and -b
+ options are equivalent, and the -n option is meaningless. */
+ while ((ch = getopt(argc, argv, "b:c:d:f:sn")) != -1)
+ switch(ch) {
+ case 'b':
+ fcn = b_cut;
+ get_list(optarg);
+ bflag = 1;
+ break;
+ case 'c':
+ fcn = c_cut;
+ get_list(optarg);
+ cflag = 1;
+ break;
+ case 'd':
+ dchar = *optarg;
+ dflag = 1;
+ break;
+ case 'f':
+ get_list(optarg);
+ fcn = f_cut;
+ fflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 'n':
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (fflag) {
+ if (cflag || bflag)
+ usage();
+ } else if ((!cflag && !bflag) || dflag || sflag)
+ usage();
+ else if (bflag && cflag)
+ usage();
+
+ rval = 0;
+ if (*argv)
+ for (; *argv; ++argv) {
+ if (strcmp(*argv, "-") == 0)
+ fcn(stdin, "stdin");
+ else {
+ if ((fp = fopen(*argv, "r"))) {
+ fcn(fp, *argv);
+ (void)fclose(fp);
+ } else {
+ rval = 1;
+ warn("%s", *argv);
+ }
+ }
+ }
+ else
+ fcn(stdin, "stdin");
+ return(rval);
+}
+
+static size_t autostart, autostop, maxval;
+
+static char *positions = NULL;
+static size_t numpositions = 0;
+#define ALLOC_CHUNK _POSIX2_LINE_MAX /* malloc granularity */
+
+static void
+get_list(char *list)
+{
+ size_t setautostart, start, stop;
+ char *pos;
+ char *p;
+
+ if (positions == NULL) {
+ numpositions = ALLOC_CHUNK;
+ positions = ecalloc(numpositions, sizeof(*positions));
+ }
+
+ /*
+ * set a byte in the positions array to indicate if a field or
+ * column is to be selected; use +1, it's 1-based, not 0-based.
+ * This parser is less restrictive than the Draft 9 POSIX spec.
+ * POSIX doesn't allow lists that aren't in increasing order or
+ * overlapping lists. We also handle "-3-5" although there's no
+ * real reason to.
+ */
+ for (; (p = strtok(list, ", \t")) != NULL; list = NULL) {
+ setautostart = start = stop = 0;
+ if (*p == '-') {
+ ++p;
+ setautostart = 1;
+ }
+ if (isdigit((unsigned char)*p)) {
+ start = stop = strtol(p, &p, 10);
+ if (setautostart && start > autostart)
+ autostart = start;
+ }
+ if (*p == '-') {
+ if (isdigit((unsigned char)p[1]))
+ stop = strtol(p + 1, &p, 10);
+ if (*p == '-') {
+ ++p;
+ if (!autostop || autostop > stop)
+ autostop = stop;
+ }
+ }
+ if (*p)
+ errx(1, "[-bcf] list: illegal list value");
+ if (!stop || !start)
+ errx(1, "[-bcf] list: values may not include zero");
+ if (stop + 1 > numpositions) {
+ size_t newsize;
+ newsize = roundup(stop + 1, ALLOC_CHUNK);
+ positions = erealloc(positions, newsize);
+ (void)memset(positions + numpositions, 0,
+ newsize - numpositions);
+ numpositions = newsize;
+ }
+ if (maxval < stop)
+ maxval = stop;
+ for (pos = positions + start; start++ <= stop; pos++)
+ *pos = 1;
+ }
+
+ /* overlapping ranges */
+ if (autostop && maxval > autostop)
+ maxval = autostop;
+
+ /* set autostart */
+ if (autostart)
+ (void)memset(positions + 1, '1', autostart);
+}
+
+static void
+/*ARGSUSED*/
+f_cut(FILE *fp, const char *fname __unused)
+{
+ int ch, field, isdelim;
+ char *pos, *p, sep;
+ int output;
+ size_t len;
+ char *lbuf, *tbuf;
+
+ for (sep = dchar, tbuf = NULL; (lbuf = fgetln(fp, &len)) != NULL;) {
+ output = 0;
+ if (lbuf[len - 1] != '\n') {
+ /* no newline at the end of the last line so add one */
+ if ((tbuf = (char *)malloc(len + 1)) == NULL)
+ err(1, NULL);
+ (void)memcpy(tbuf, lbuf, len);
+ tbuf[len++] = '\n';
+ lbuf = tbuf;
+ }
+ for (isdelim = 0, p = lbuf;; ++p) {
+ ch = *p;
+ /* this should work if newline is delimiter */
+ if (ch == sep)
+ isdelim = 1;
+ if (ch == '\n') {
+ if (!isdelim && !sflag)
+ (void)fwrite(lbuf, len, 1, stdout);
+ break;
+ }
+ }
+ if (!isdelim)
+ continue;
+
+ pos = positions + 1;
+ for (field = maxval, p = lbuf; field; --field, ++pos) {
+ if (*pos) {
+ if (output++)
+ (void)putchar(sep);
+ while ((ch = *p++) != '\n' && ch != sep)
+ (void)putchar(ch);
+ } else {
+ while ((ch = *p++) != '\n' && ch != sep)
+ continue;
+ }
+ if (ch == '\n')
+ break;
+ }
+ if (ch != '\n') {
+ if (autostop) {
+ if (output)
+ (void)putchar(sep);
+ for (; (ch = *p) != '\n'; ++p)
+ (void)putchar(ch);
+ } else
+ for (; (ch = *p) != '\n'; ++p);
+ }
+ (void)putchar('\n');
+ if (tbuf) {
+ free(tbuf);
+ tbuf = NULL;
+ }
+ }
+ if (tbuf)
+ free(tbuf);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage:\tcut -b list [-n] [file ...]\n"
+ "\tcut -c list [file ...]\n"
+ "\tcut -f list [-d string] [-s] [file ...]\n");
+ exit(1);
+}
+
+/* make b_put(): */
+#define CUT_BYTE 1
+#include "x_cut.c"
+#undef CUT_BYTE
+
+/* make c_put(): */
+#define CUT_BYTE 0
+#include "x_cut.c"
+#undef CUT_BYTE
diff --git a/usr.bin/cut/x_cut.c b/usr.bin/cut/x_cut.c
new file mode 100644
index 0000000..006b5af
--- /dev/null
+++ b/usr.bin/cut/x_cut.c
@@ -0,0 +1,95 @@
+/* $NetBSD: x_cut.c,v 1.2 2007/07/02 18:41:04 christos Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam S. Moskowitz of Menlo Consulting and Marciano Pitargue.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file is #include'd twice from cut.c, to generate both
+ * single- and multibyte versions of the same code.
+ *
+ * In cut.c #define:
+ * CUT_BYTE=0 to define b_cut (singlebyte), and
+ * CUT_BYTE=1 to define c_cut (multibyte).
+ *
+ */
+
+#if (CUT_BYTE == 1)
+# define CUT_FN b_cut
+# define CUT_CH_T int
+# define CUT_GETC getc
+# define CUT_EOF EOF
+# define CUT_PUTCHAR putchar
+#else
+# define CUT_FN c_cut
+# define CUT_CH_T wint_t
+# define CUT_GETC getwc
+# define CUT_EOF WEOF
+# define CUT_PUTCHAR putwchar
+#endif
+
+
+/* ARGSUSED */
+void
+CUT_FN(FILE *fp, const char *fname __unused)
+{
+ CUT_CH_T ch;
+ int col;
+ char *pos;
+
+ ch = 0;
+ for (;;) {
+ pos = positions + 1;
+ for (col = maxval; col; --col) {
+ if ((ch = CUT_GETC(fp)) == EOF)
+ return;
+ if (ch == '\n')
+ break;
+ if (*pos++)
+ (void)CUT_PUTCHAR(ch);
+ }
+ if (ch != '\n') {
+ if (autostop)
+ while ((ch = CUT_GETC(fp)) != CUT_EOF && ch != '\n')
+ (void)CUT_PUTCHAR(ch);
+ else
+ while ((ch = CUT_GETC(fp)) != CUT_EOF && ch != '\n');
+ }
+ (void)CUT_PUTCHAR('\n');
+ }
+}
+
+#undef CUT_FN
+#undef CUT_CH_T
+#undef CUT_GETC
+#undef CUT_EOF
+#undef CUT_PUTCHAR
+
diff --git a/usr.bin/dirname/dirname.c b/usr.bin/dirname/dirname.c
new file mode 100644
index 0000000..bc9d0ae
--- /dev/null
+++ b/usr.bin/dirname/dirname.c
@@ -0,0 +1,86 @@
+/* $NetBSD: dirname.c,v 1.12 2011/09/16 15:39:25 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)dirname.c 8.4 (Berkeley) 5/4/95";
+#endif
+__RCSID("$NetBSD: dirname.c,v 1.12 2011/09/16 15:39:25 joerg Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <libgen.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+__dead static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ char *p;
+ int ch;
+
+ setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "")) != -1)
+ switch (ch) {
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ if ((p = dirname(*argv)) == NULL)
+ err(1, "%s", *argv);
+ (void)printf("%s\n", p);
+ exit(0);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: dirname path\n");
+ exit(1);
+}
diff --git a/usr.bin/du/du.1 b/usr.bin/du/du.1
new file mode 100644
index 0000000..14bb436
--- /dev/null
+++ b/usr.bin/du/du.1
@@ -0,0 +1,168 @@
+.\" $NetBSD: du.1,v 1.23 2012/05/14 21:14:14 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)du.1 8.2 (Berkeley) 4/1/94
+.\"
+.Dd May 14, 2012
+.Dt DU 1
+.Os
+.Sh NAME
+.Nm du
+.Nd display disk usage statistics
+.Sh SYNOPSIS
+.Nm
+.Op Fl H | Fl L | Fl P
+.Op Fl a | Fl d Ar depth | Fl s
+.Op Fl cghikmnrx
+.Op Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility displays the file system usage for each file argument
+and for each directory in the file hierarchy rooted in each directory
+argument.
+If no file is specified, the block usage of the hierarchy rooted in
+the current directory is displayed.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl H
+Symbolic links on the command line are followed.
+(Symbolic links encountered in the tree traversal are not followed.)
+.It Fl L
+All symbolic links are followed.
+.It Fl P
+No symbolic links are followed.
+.It Fl a
+Display an entry for each file in the file hierarchy.
+.It Fl c
+Display the grand total after all the arguments have been processed.
+.It Fl d
+Display an entry files and directories
+.Ar depth
+directories deep.
+.It Fl g
+If the
+.Fl g
+flag is specified, the number displayed is the number of gigabyte
+(1024*1024*1024 bytes) blocks.
+.It Fl h
+If the
+.Fl h
+flag is specified, the numbers will be displayed in "human-readable"
+format.
+Use unit suffixes: B (Byte), K (Kilobyte), M (Megabyte), G (Gigabyte),
+T (Terabyte) and P (Petabyte).
+.It Fl i
+Output inode usage instead of blocks.
+All "human-readable" options are ignored.
+.It Fl k
+By default,
+.Nm
+displays the number of blocks as returned by the
+.Xr stat 2
+system call, i.e. 512-byte blocks.
+If the
+.Fl k
+flag is specified, the number displayed is the number of kilobyte
+(1024 bytes) blocks.
+Partial numbers of blocks are rounded up.
+.It Fl m
+If the
+.Fl m
+flag is specified, the number displayed is the number of megabyte
+(1024*1024 bytes) blocks.
+.It Fl n
+Ignore files and directories with user
+.Qq nodump
+flag
+.Pq Dv UF_NODUMP
+set.
+.It Fl r
+Generate warning messages about directories that cannot be read.
+This is the default behaviour.
+.It Fl s
+Display only the grand total for the specified files.
+.It Fl x
+Filesystem mount points are not traversed.
+.El
+.Pp
+.Nm
+counts the storage used by symbolic links and not the files they
+reference unless the
+.Fl H
+or
+.Fl L
+option is specified.
+If either the
+.Fl H
+or
+.Fl L
+options are specified, storage used by any symbolic links which are
+followed is not counted or displayed.
+The
+.Fl H ,
+.Fl L
+and
+.Fl P
+options override each other and the command's actions are determined
+by the last one specified.
+.Pp
+Files having multiple hard links are counted (and displayed) a single
+time per
+.Nm
+execution.
+.Sh ENVIRONMENT
+.Bl -tag -width BLOCKSIZE
+.It Ev BLOCKSIZE
+If the environment variable
+.Ev BLOCKSIZE
+is set, and the
+.Fl g ,
+.Fl h ,
+.Fl k ,
+and
+.Fl m
+options are not specified, the block counts will be displayed in units of that
+size block.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr df 1 ,
+.Xr chflags 2 ,
+.Xr fts 3 ,
+.Xr getbsize 3 ,
+.Xr symlink 7 ,
+.Xr quot 8
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v6 .
diff --git a/usr.bin/du/du.c b/usr.bin/du/du.c
new file mode 100644
index 0000000..5b13232
--- /dev/null
+++ b/usr.bin/du/du.c
@@ -0,0 +1,362 @@
+/* $NetBSD: du.c,v 1.36 2012/03/11 11:23:20 shattered Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Newcomb.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)du.c 8.5 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: du.c,v 1.36 2012/03/11 11:23:20 shattered Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <inttypes.h>
+#include <util.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+
+/* Count inodes or file size */
+#define COUNT (iflag ? 1 : p->fts_statp->st_blocks)
+
+static int linkchk(dev_t, ino_t);
+static void prstat(const char *, int64_t);
+__dead static void usage(void);
+
+static int hflag, iflag;
+static long blocksize;
+
+int
+main(int argc, char *argv[])
+{
+ FTS *fts;
+ FTSENT *p;
+ int64_t totalblocks;
+ int ftsoptions, listfiles;
+ int depth;
+ int Hflag, Lflag, aflag, ch, cflag, dflag, gkmflag, nflag, rval, sflag;
+ const char *noargv[2];
+
+ Hflag = Lflag = aflag = cflag = dflag = gkmflag = nflag = sflag = 0;
+ totalblocks = 0;
+ ftsoptions = FTS_PHYSICAL;
+ depth = INT_MAX;
+ while ((ch = getopt(argc, argv, "HLPacd:ghikmnrsx")) != -1)
+ switch (ch) {
+ case 'H':
+ Hflag = 1;
+ Lflag = 0;
+ break;
+ case 'L':
+ Lflag = 1;
+ Hflag = 0;
+ break;
+ case 'P':
+ Hflag = Lflag = 0;
+ break;
+ case 'a':
+ aflag = 1;
+ break;
+ case 'c':
+ cflag = 1;
+ break;
+ case 'd':
+ dflag = 1;
+ depth = atoi(optarg);
+ if (depth < 0 || depth > SHRT_MAX) {
+ warnx("invalid argument to option d: %s",
+ optarg);
+ usage();
+ }
+ break;
+ case 'g':
+ blocksize = 1024 * 1024 * 1024;
+ gkmflag = 1;
+ break;
+ case 'h':
+ hflag = 1;
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+ case 'k':
+ blocksize = 1024;
+ gkmflag = 1;
+ break;
+ case 'm':
+ blocksize = 1024 * 1024;
+ gkmflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'r':
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 'x':
+ ftsoptions |= FTS_XDEV;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ /*
+ * XXX
+ * Because of the way that fts(3) works, logical walks will not count
+ * the blocks actually used by symbolic links. We rationalize this by
+ * noting that users computing logical sizes are likely to do logical
+ * copies, so not counting the links is correct. The real reason is
+ * that we'd have to re-implement the kernel's symbolic link traversing
+ * algorithm to get this right. If, for example, you have relative
+ * symbolic links referencing other relative symbolic links, it gets
+ * very nasty, very fast. The bottom line is that it's documented in
+ * the man page, so it's a feature.
+ */
+ if (Hflag)
+ ftsoptions |= FTS_COMFOLLOW;
+ if (Lflag) {
+ ftsoptions &= ~FTS_PHYSICAL;
+ ftsoptions |= FTS_LOGICAL;
+ }
+
+ listfiles = 0;
+ if (aflag) {
+ if (sflag || dflag)
+ usage();
+ listfiles = 1;
+ } else if (sflag) {
+ if (dflag)
+ usage();
+ depth = 0;
+ }
+
+ if (!*argv) {
+ noargv[0] = ".";
+ noargv[1] = NULL;
+ argv = __UNCONST(noargv);
+ }
+
+ if (!gkmflag)
+ (void)getbsize(NULL, &blocksize);
+ blocksize /= 512;
+
+ if ((fts = fts_open(argv, ftsoptions, NULL)) == NULL)
+ err(1, "fts_open `%s'", *argv);
+
+ for (rval = 0; (p = fts_read(fts)) != NULL;) {
+ if (nflag) {
+ switch (p->fts_info) {
+ case FTS_NS:
+ case FTS_SLNONE:
+ /* nothing */
+ break;
+ default:
+ if (p->fts_statp->st_flags & UF_NODUMP) {
+ fts_set(fts, p, FTS_SKIP);
+ continue;
+ }
+ }
+ }
+ switch (p->fts_info) {
+ case FTS_D: /* Ignore. */
+ break;
+ case FTS_DP:
+ p->fts_parent->fts_number +=
+ p->fts_number += COUNT;
+ if (cflag)
+ totalblocks += COUNT;
+ /*
+ * If listing each directory, or not listing files
+ * or directories and this is post-order of the
+ * root of a traversal, display the total.
+ */
+ if (p->fts_level <= depth
+ || (!listfiles && !p->fts_level))
+ prstat(p->fts_path, p->fts_number);
+ break;
+ case FTS_DC: /* Ignore. */
+ break;
+ case FTS_DNR: /* Warn, continue. */
+ case FTS_ERR:
+ case FTS_NS:
+ warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
+ rval = 1;
+ break;
+ default:
+ if (p->fts_statp->st_nlink > 1 &&
+ linkchk(p->fts_statp->st_dev, p->fts_statp->st_ino))
+ break;
+ /*
+ * If listing each file, or a non-directory file was
+ * the root of a traversal, display the total.
+ */
+ if (listfiles || !p->fts_level)
+ prstat(p->fts_path, COUNT);
+ p->fts_parent->fts_number += COUNT;
+ if (cflag)
+ totalblocks += COUNT;
+ }
+ }
+ if (errno)
+ err(1, "fts_read");
+ if (cflag)
+ prstat("total", totalblocks);
+ exit(rval);
+}
+
+static void
+prstat(const char *fname, int64_t blocks)
+{
+ if (iflag) {
+ (void)printf("%" PRId64 "\t%s\n", blocks, fname);
+ return;
+ }
+
+ if (hflag) {
+ char buf[5];
+ int64_t sz = blocks * 512;
+
+ humanize_number(buf, sizeof(buf), sz, "", HN_AUTOSCALE,
+ HN_B | HN_NOSPACE | HN_DECIMAL);
+
+ (void)printf("%s\t%s\n", buf, fname);
+ } else
+ (void)printf("%" PRId64 "\t%s\n",
+ howmany(blocks, (int64_t)blocksize),
+ fname);
+}
+
+static int
+linkchk(dev_t dev, ino_t ino)
+{
+ static struct entry {
+ dev_t dev;
+ ino_t ino;
+ } *htable;
+ static int htshift; /* log(allocated size) */
+ static int htmask; /* allocated size - 1 */
+ static int htused; /* 2*number of insertions */
+ static int sawzero; /* Whether zero is in table or not */
+ int h, h2;
+ uint64_t tmp;
+ /* this constant is (1<<64)/((1+sqrt(5))/2)
+ * aka (word size)/(golden ratio)
+ */
+ const uint64_t HTCONST = 11400714819323198485ULL;
+ const int HTBITS = CHAR_BIT * sizeof(tmp);
+
+ /* Never store zero in hashtable */
+ if (dev == 0 && ino == 0) {
+ h = sawzero;
+ sawzero = 1;
+ return h;
+ }
+
+ /* Extend hash table if necessary, keep load under 0.5 */
+ if (htused<<1 >= htmask) {
+ struct entry *ohtable;
+
+ if (!htable)
+ htshift = 10; /* starting hashtable size */
+ else
+ htshift++; /* exponential hashtable growth */
+
+ htmask = (1 << htshift) - 1;
+ htused = 0;
+
+ ohtable = htable;
+ htable = calloc(htmask+1, sizeof(*htable));
+ if (!htable)
+ err(1, "calloc");
+
+ /* populate newly allocated hashtable */
+ if (ohtable) {
+ int i;
+ for (i = 0; i <= htmask>>1; i++)
+ if (ohtable[i].ino || ohtable[i].dev)
+ linkchk(ohtable[i].dev, ohtable[i].ino);
+ free(ohtable);
+ }
+ }
+
+ /* multiplicative hashing */
+ tmp = dev;
+ tmp <<= HTBITS>>1;
+ tmp |= ino;
+ tmp *= HTCONST;
+ h = tmp >> (HTBITS - htshift);
+ h2 = 1 | ( tmp >> (HTBITS - (htshift<<1) - 1)); /* must be odd */
+
+ /* open address hashtable search with double hash probing */
+ while (htable[h].ino || htable[h].dev) {
+ if ((htable[h].ino == ino) && (htable[h].dev == dev))
+ return 1;
+ h = (h + h2) & htmask;
+ }
+
+ /* Insert the current entry into hashtable */
+ htable[h].dev = dev;
+ htable[h].ino = ino;
+ htused++;
+ return 0;
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "usage: du [-H | -L | -P] [-a | -d depth | -s] [-cghikmnrx] [file ...]\n");
+ exit(1);
+}
diff --git a/usr.bin/env/env.1 b/usr.bin/env/env.1
new file mode 100644
index 0000000..d4597bd
--- /dev/null
+++ b/usr.bin/env/env.1
@@ -0,0 +1,122 @@
+.\" $NetBSD: env.1,v 1.12 2007/06/08 18:20:42 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1990 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)printenv.1 6.7 (Berkeley) 7/28/91
+.\" $NetBSD: env.1,v 1.12 2007/06/08 18:20:42 wiz Exp $
+.\"
+.Dd June 8, 2007
+.Dt ENV 1
+.Os
+.Sh NAME
+.Nm env
+.Nd set and print environment
+.Sh SYNOPSIS
+.Nm
+.Op Fl i
+.Op Ar name=value ...
+.Oo
+.Ar utility
+.Op argument ...
+.Oc
+.Sh DESCRIPTION
+.Nm
+executes
+.Ar utility
+after modifying the environment as
+specified on the command line.
+The option
+.Ar name=value
+specifies
+an environmental variable,
+.Ar name ,
+with a value of
+.Ar value .
+The option
+.Sq Fl i
+causes
+.Nm
+to completely ignore the environment
+it inherits.
+.Pp
+If no
+.Ar utility
+is specified,
+.Nm
+prints out the names and values
+of the variables in the environment, with one
+.Ar name=value
+pair per line.
+.Sh EXIT STATUS
+.Nm
+exits with one of the following values:
+.Bl -tag -width Ds
+.It 0
+.Ar utility
+was invoked and completed successfully.
+In this case the exit code is returned by the utility itself, not
+.Nm .
+If no utility was specified, then
+.Nm
+completed successfully and returned the exit code itself.
+.It 1
+An invalid command line option was passed to
+.Nm .
+.It 1\-125
+.Ar utility
+was invoked, but failed in some way;
+see its manual page for more information.
+In this case the exit code is returned by the utility itself, not
+.Nm .
+.It 126
+.Ar utility
+was found, but could not be invoked.
+.It 127
+.Ar utility
+could not be found.
+.El
+.Sh COMPATIBILITY
+The historic
+.Fl
+option has been deprecated but is still supported in this implementation.
+.Sh SEE ALSO
+.Xr execvp 3 ,
+.Xr environ 7
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
+.Sh BUGS
+.Nm
+doesn't handle commands with equal
+.Pq Dq =
+signs in their
+names, for obvious reasons.
diff --git a/usr.bin/env/env.c b/usr.bin/env/env.c
new file mode 100644
index 0000000..bf2430d
--- /dev/null
+++ b/usr.bin/env/env.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 1988, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+/*static char sccsid[] = "@(#)env.c 8.3 (Berkeley) 4/2/94";*/
+__RCSID("$NetBSD: env.c,v 1.20 2010/11/16 02:53:49 christos Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <locale.h>
+#include <errno.h>
+
+static void usage(void) __attribute__((__noreturn__));
+
+extern char **environ;
+
+int
+main(int argc, char **argv)
+{
+ char **ep;
+ char *cleanenv[1];
+ int ch;
+
+ setprogname(*argv);
+ (void)setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "-i")) != -1)
+ switch((char)ch) {
+ case '-': /* obsolete */
+ case 'i':
+ environ = cleanenv;
+ cleanenv[0] = NULL;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+
+ for (argv += optind; *argv && strchr(*argv, '=') != NULL; ++argv)
+ (void)putenv(*argv);
+
+ if (*argv) {
+ /* return 127 if the command to be run could not be found; 126
+ if the command was found but could not be invoked */
+
+ (void)execvp(*argv, argv);
+ err((errno == ENOENT) ? 127 : 126, "%s", *argv);
+ /* NOTREACHED */
+ }
+
+ for (ep = environ; *ep; ep++)
+ (void)printf("%s\n", *ep);
+
+ exit(0);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s [-i] [name=value ...] [command]\n",
+ getprogname());
+ exit(1);
+}
diff --git a/usr.bin/expand/expand.1 b/usr.bin/expand/expand.1
new file mode 100644
index 0000000..5f36025
--- /dev/null
+++ b/usr.bin/expand/expand.1
@@ -0,0 +1,87 @@
+.\" $NetBSD: expand.1,v 1.12 2012/05/12 14:52:57 reed Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)expand.1 8.1 (Berkeley) 6/9/93
+.\"
+.Dd December 20, 2008
+.Dt EXPAND 1
+.Os
+.Sh NAME
+.Nm expand ,
+.Nm unexpand
+.Nd expand tabs to spaces, and vice versa
+.Sh SYNOPSIS
+.Nm
+.Op Fl t Ar tabstop
+.Op Fl t Ar tab1,tab2,...,tabn
+.Op Ar
+.Nm unexpand
+.Op Fl a
+.Op Fl t Ar tab1,tab2,...,tabn
+.Op Ar
+.Sh DESCRIPTION
+.Nm
+processes the named files or the standard input writing
+the standard output with tabs changed into blanks.
+Backspace characters are preserved into the output and decrement
+the column count for tab calculations.
+.Nm
+is useful for pre-processing character files
+(before sorting, looking at specific columns, etc.) that
+contain tabs.
+.Pp
+If a single
+.Ar tabstop
+argument is given, then tabs are set
+.Ar tabstop
+spaces apart instead of the default 8.
+If multiple tabstops are given then the tabs are set at those
+specific columns.
+.Pp
+.Nm unexpand
+puts tabs back into the data from the standard input or the named
+files and writes the result on the standard output.
+.Pp
+Option (with
+.Nm unexpand
+only):
+.Bl -tag -width flag
+.It Fl a
+By default, only leading blanks and tabs
+are reconverted to maximal strings of tabs.
+If the
+.Fl a
+option is given, then tabs are inserted whenever they would compress the
+resultant file by replacing two or more characters.
+.El
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 1 .
diff --git a/usr.bin/expand/expand.c b/usr.bin/expand/expand.c
new file mode 100644
index 0000000..c69fda1
--- /dev/null
+++ b/usr.bin/expand/expand.c
@@ -0,0 +1,182 @@
+/* $NetBSD: expand.c,v 1.14 2016/09/05 00:40:28 sevan Exp $ */
+
+/*
+ * Copyright (c) 1980, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1980, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)expand.c 8.1 (Berkeley) 6/9/93";
+#endif
+__RCSID("$NetBSD: expand.c,v 1.14 2016/09/05 00:40:28 sevan Exp $");
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <err.h>
+
+/*
+ * expand - expand tabs to equivalent spaces
+ */
+size_t nstops;
+size_t tabstops[100];
+
+static void getstops(const char *);
+static void usage(void) __dead;
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ size_t n, column;
+
+ setprogname(argv[0]);
+
+ /* handle obsolete syntax */
+ while (argc > 1 &&
+ argv[1][0] == '-' && isdigit((unsigned char)argv[1][1])) {
+ getstops(&argv[1][1]);
+ argc--; argv++;
+ }
+
+ while ((c = getopt (argc, argv, "t:")) != -1) {
+ switch (c) {
+ case 't':
+ getstops(optarg);
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ do {
+ if (argc > 0) {
+ if (freopen(argv[0], "r", stdin) == NULL)
+ err(EXIT_FAILURE, "Cannot open `%s'", argv[0]);
+ argc--, argv++;
+ }
+ column = 0;
+ while ((c = getchar()) != EOF) {
+ switch (c) {
+ case '\t':
+ if (nstops == 0) {
+ do {
+ putchar(' ');
+ column++;
+ } while (column & 07);
+ continue;
+ }
+ if (nstops == 1) {
+ do {
+ putchar(' ');
+ column++;
+ } while (((column - 1) % tabstops[0])
+ != (tabstops[0] - 1));
+ continue;
+ }
+ for (n = 0; n < nstops; n++)
+ if (tabstops[n] > column)
+ break;
+ if (n == nstops) {
+ putchar(' ');
+ column++;
+ continue;
+ }
+ while (column < tabstops[n]) {
+ putchar(' ');
+ column++;
+ }
+ continue;
+
+ case '\b':
+ if (column)
+ column--;
+ putchar('\b');
+ continue;
+
+ default:
+ putchar(c);
+ column++;
+ continue;
+
+ case '\n':
+ putchar(c);
+ column = 0;
+ continue;
+ }
+ }
+ } while (argc > 0);
+ return EXIT_SUCCESS;
+}
+
+static void
+getstops(const char *spec)
+{
+ int i;
+ const char *cp = spec;
+
+ nstops = 0;
+ for (;;) {
+ i = 0;
+ while (*cp >= '0' && *cp <= '9')
+ i = i * 10 + *cp++ - '0';
+ if (i <= 0 || i > 256)
+ errx(EXIT_FAILURE, "Too large tab stop spec `%d'", i);
+ if (nstops > 0 && (size_t)i <= tabstops[nstops-1])
+ errx(EXIT_FAILURE, "Out of order tabstop spec `%d'", i);
+ if (nstops == sizeof(tabstops) / sizeof(tabstops[0]) - 1)
+ errx(EXIT_FAILURE, "Too many tabstops");
+ tabstops[nstops++] = i;
+ if (*cp == '\0')
+ break;
+ if (*cp != ',' && *cp != ' ')
+ errx(EXIT_FAILURE, "Illegal tab stop spec `%s'", spec);
+ cp++;
+ }
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "Usage: %s [-t tablist] [file ...]\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/false/false.1 b/usr.bin/false/false.1
new file mode 100644
index 0000000..175b67a
--- /dev/null
+++ b/usr.bin/false/false.1
@@ -0,0 +1,56 @@
+.\" $NetBSD: false.1,v 1.8 2003/08/07 11:13:40 agc Exp $
+.\"
+.\" Copyright (c) 1983, 1990 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)false.1 6.6 (Berkeley) 7/24/91
+.\" $NetBSD: false.1,v 1.8 2003/08/07 11:13:40 agc Exp $
+.\"
+.Dd July 24, 1991
+.Dt FALSE 1
+.Os
+.Sh NAME
+.Nm false
+.Nd return false value
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+utility always exits with a nonzero exit code.
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr sh 1 ,
+.Xr true 1
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/false/false.sh b/usr.bin/false/false.sh
new file mode 100644
index 0000000..2526efb
--- /dev/null
+++ b/usr.bin/false/false.sh
@@ -0,0 +1,2 @@
+#! /bin/sh
+exit 1
diff --git a/usr.bin/find/extern.h b/usr.bin/find/extern.h
new file mode 100644
index 0000000..5c94035
--- /dev/null
+++ b/usr.bin/find/extern.h
@@ -0,0 +1,102 @@
+/* $NetBSD: extern.h,v 1.29 2016/06/13 00:04:40 pgoyette Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)extern.h 8.3 (Berkeley) 4/16/94
+ */
+
+#include <sys/cdefs.h>
+
+void brace_subst(char *, char **, char *, int *);
+PLAN *find_create(char ***);
+int find_execute(PLAN *, char **);
+PLAN *find_formplan(char **);
+int find_traverse(PLAN *, int (*)(PLAN *, void *), void *);
+int f_expr(PLAN *, FTSENT *);
+PLAN *not_squish(PLAN *);
+PLAN *or_squish(PLAN *);
+PLAN *paren_squish(PLAN *);
+int plan_cleanup(PLAN *, void *);
+void printlong(char *, char *, struct stat *);
+int queryuser(char **);
+void show_path(int);
+
+PLAN *c_amin(char ***, int, char *);
+PLAN *c_anewer(char ***, int, char *);
+PLAN *c_asince(char ***, int, char *);
+PLAN *c_atime(char ***, int, char *);
+PLAN *c_cmin(char ***, int, char *);
+PLAN *c_cnewer(char ***, int, char *);
+PLAN *c_csince(char ***, int, char *);
+PLAN *c_ctime(char ***, int, char *);
+PLAN *c_delete(char ***, int, char *);
+PLAN *c_depth(char ***, int, char *);
+PLAN *c_empty(char ***, int, char *);
+PLAN *c_exec(char ***, int, char *);
+PLAN *c_execdir(char ***, int, char *);
+PLAN *c_exit(char ***, int, char *);
+PLAN *c_false(char ***, int, char *);
+PLAN *c_flags(char ***, int, char *);
+PLAN *c_follow(char ***, int, char *);
+PLAN *c_fprint(char ***, int, char *);
+PLAN *c_fstype(char ***, int, char *);
+PLAN *c_group(char ***, int, char *);
+PLAN *c_iname(char ***, int, char *);
+PLAN *c_inum(char ***, int, char *);
+PLAN *c_iregex(char ***, int, char *);
+PLAN *c_links(char ***, int, char *);
+PLAN *c_ls(char ***, int, char *);
+PLAN *c_maxdepth(char ***, int, char *);
+PLAN *c_mindepth(char ***, int, char *);
+PLAN *c_mmin(char ***, int, char *);
+PLAN *c_mtime(char ***, int, char *);
+PLAN *c_name(char ***, int, char *);
+PLAN *c_newer(char ***, int, char *);
+PLAN *c_nogroup(char ***, int, char *);
+PLAN *c_nouser(char ***, int, char *);
+PLAN *c_path(char ***, int, char *);
+PLAN *c_perm(char ***, int, char *);
+PLAN *c_print(char ***, int, char *);
+PLAN *c_print0(char ***, int, char *);
+PLAN *c_printx(char ***, int, char *);
+PLAN *c_prune(char ***, int, char *);
+PLAN *c_regex(char ***, int, char *);
+PLAN *c_since(char ***, int, char *);
+PLAN *c_size(char ***, int, char *);
+PLAN *c_type(char ***, int, char *);
+PLAN *c_user(char ***, int, char *);
+PLAN *c_xdev(char ***, int, char *);
+PLAN *c_openparen(char ***, int, char *);
+PLAN *c_closeparen(char ***, int, char *);
+PLAN *c_not(char ***, int, char *);
+PLAN *c_or(char ***, int, char *);
+PLAN *c_null(char ***, int, char *);
+
+extern int ftsoptions, isdeprecated, isdepth, isoutput, issort, isxargs,
+ regcomp_flags;
diff --git a/usr.bin/find/find.1 b/usr.bin/find/find.1
new file mode 100644
index 0000000..60ce94a
--- /dev/null
+++ b/usr.bin/find/find.1
@@ -0,0 +1,974 @@
+.\" $NetBSD: find.1,v 1.89 2017/07/03 21:34:57 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)find.1 8.7 (Berkeley) 5/9/95
+.\"
+.Dd June 13, 2016
+.Dt FIND 1
+.Os
+.Sh NAME
+.Nm find
+.Nd walk a file hierarchy
+.Sh SYNOPSIS
+.Nm
+.Op Fl H | Fl L | Fl P
+.Op Fl dEhsXx
+.Ar file
+.Op Ar file ...
+.Op Ar expression
+.Nm
+.Op Fl H | Fl L | Fl P
+.Op Fl dEhsXx
+.Fl f Ar file
+.Op Ar file ...
+.Op Ar expression
+.Sh DESCRIPTION
+.Nm
+recursively descends the directory tree for each
+.Ar file
+listed, evaluating an
+.Ar expression
+(composed of the
+.Dq primaries
+and
+.Dq operands
+listed below) in terms
+of each file in the tree.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl H
+Causes the file information and file type (see
+.Xr stat 2 )
+returned for each symbolic link encountered on the command line to be
+those of the file referenced by the link, not the link itself.
+If the referenced file does not exist, the file information and type will
+be for the link itself.
+File information of all symbolic links not on the command line is that
+of the link itself.
+.It Fl L
+Causes the file information and file type (see
+.Xr stat 2 )
+returned for each symbolic link to be those of the file referenced by the
+link, not the link itself.
+If the referenced file does not exist, the file information and type will
+be for the link itself.
+.It Fl P
+Causes the file information and file type (see
+.Xr stat 2 )
+returned for each symbolic link to be those of the link itself.
+.It Fl d
+Causes
+.Nm
+to perform a depth-first traversal, i.e., directories
+are visited in post-order, and all entries in a directory will be acted
+on before the directory itself.
+By default,
+.Nm
+visits directories in pre-order, i.e., before their contents.
+Note, the default is
+.Em not
+a breadth-first traversal.
+.It Fl E
+Causes
+.Ar regexp
+arguments to primaries to be interpreted as extended regular
+expressions (see
+.Xr re_format 7 ) .
+.It Fl f
+Specifies a file hierarchy for
+.Nm
+to traverse.
+File hierarchies may also be specified as the operands immediately
+following the options.
+.It Fl h
+Causes the file information and file type (see
+.Xr stat 2 )
+returned for each symbolic link to be those of the file referenced by the
+link, not the link itself.
+If the referenced file does not exist, the file information and type will
+be for the link itself.
+.It Fl s
+Causes the entries of each directory to be sorted in
+lexicographical order.
+Note that the sorting is done only inside of each directory;
+files in different directories are not sorted.
+Therefore,
+.Sq Li a/b
+appears before
+.Sq Li a.b ,
+which is different from
+.Dq Li "find ... \&| sort"
+order.
+.It Fl X
+Modifies the output to permit
+.Nm
+to be safely used in conjunction with
+.Xr xargs 1 .
+If a file name contains any of the delimiting characters used by
+.Xr xargs 1 ,
+a diagnostic message is displayed on standard error, and the file
+is skipped.
+The delimiting characters include single
+.Pq Dq \&'
+and double
+.Pq Dq \&"
+quotes, backslash
+.Pq Dq \e ,
+space, tab, and newline characters.
+Alternatively, the
+.Ic -print0
+or
+.Ic -printx
+primaries can be used to format the output in a way that
+.Xr xargs 1
+can accept.
+.It Fl x
+Restricts the search to the file system containing the
+directory specified.
+Does not list mount points to other file systems.
+.El
+.Sh PRIMARIES
+All primaries which take a numeric argument of
+.Ar n
+allow the number to be preceded by a plus sign
+.Pq Dq \&+
+or a minus sign
+.Pq Dq \- .
+A preceding plus sign means
+.Dq more than Ar n ,
+a preceding minus sign means
+.Dq less than Ar n ,
+and neither means
+.Dq exactly Ar n .
+(The argument specified for the
+.Ic -user
+and
+.Ic -group
+primaries
+are similarly treated if the value is numeric and does not correspond to a
+valid user or group name.)
+.Pp
+For primaries which take a
+.Ar timestamp
+argument, the argument must be valid input to
+.Xr parsedate 3 .
+If the argument contains multiple words, enclose the argument in quotes.
+.Pp
+.Bl -tag -width Ds -compact
+.It Ic -amin Ar n
+True if the difference between the file last access time and the time
+.Nm
+was started, rounded up to the next full minute, is
+.Ar n
+minutes.
+.Pp
+.It Ic -anewer Ar file
+True if the current file has a more recent last access time than
+.Ar file .
+.Pp
+.It Ic -asince Ar timestamp
+True if the file last access time is greater than the specified
+.Ar timestamp .
+.Pp
+.It Ic -atime Ar n
+True if the difference between the file last access time and the time
+.Nm
+was started, rounded up to the next full 24-hour period, is
+.Ar n
+24-hour periods.
+.Pp
+.It Ic -cmin Ar n
+True if the difference between the time of last change of file status
+information and the time
+.Nm
+was started, rounded up to the next full minute, is
+.Ar n
+minutes.
+.Pp
+.It Ic -cnewer Ar file
+True if the current file has a more recent last change time than
+.Ar file .
+.Pp
+.It Ic -csince Ar timestamp
+True if the file last status change time is greater than the specified
+.Ar timestamp .
+.Pp
+.It Ic -ctime Ar n
+True if the difference between the time of last change of file status
+information and the time
+.Nm
+was started, rounded up to the next full 24-hour period, is
+.Ar n
+24-hour periods.
+.Pp
+.It Ic -delete
+Delete found files, symbolic links, and directories.
+Always returns true.
+This executes from the current working directory as
+.Nm
+recurses down the tree.
+To avoid deleting unexpected files, it will ignore any filenames that
+.Xr fts 3
+returns that contain a
+.Dq /
+.Xr ( fts 3
+should not return such pathnames).
+Depth-first traversal processing is implied by this option.
+This primary can also be invoked as
+.Ic -rm .
+.Pp
+.It Ic -empty
+True if the current file or directory is empty.
+.Pp
+.It Ic -exec Ar utility Oo argument ... Oc Ic \&;
+.It Ic -exec Ar utility Oo argument ... Oc Ic {} Ic \&+
+Execute the specified
+.Ar utility
+with the specified arguments.
+.Pp
+The list of arguments for
+.Ar utility
+is terminated by a lone semicolon
+.Dq Ic \&;
+or plus
+.Dq Ic \&+
+character as a separate parameter.
+The command specified by
+.Ar utility
+will be executed with its current working directory being the directory
+from which
+.Nm
+was executed.
+.Pp
+If the list of arguments is terminated by a semicolon
+.Pq Dq Ic \&; ,
+then
+.Ar utility
+is invoked once per pathname.
+If
+the string
+.Dq Ic {}
+appears one or more times in the utility name or arguments,
+then it is replaced by the pathname of the current file
+(but it need not appear, in which case the pathname
+will not be passed to
+.Ar utility ) .
+The semicolon-terminated form of the
+.Ic -exec
+primary returns true if and only if
+.Ar utility
+exits with a zero exit status.
+Note that the semicolon will have to be escaped on the shell command line
+in order to be passed as a parameter.
+.Pp
+If the list of arguments is terminated by a plus sign
+.Pq Dq Ic \&+ ,
+then the pathnames for which the primary is evaluated are aggregated
+into sets, and
+.Ar utility
+will be invoked once per set, similar to
+.Xr xargs 1 .
+In this case the string
+.Dq Ic {}
+must appear, and must appear as the last item in the argument list,
+just before the
+.Dq Ic \&+
+parameter, and is replaced by the pathnames of the current set of files.
+Each set is limited to no more than 5,000 pathnames,
+and is also limited such that the total number of bytes in the argument
+list does not exceed
+.Dv ARG_MAX .
+The plus-terminated form of the
+.Ic -exec
+primary always returns true.
+If the plus-terminated form of the
+.Ic -exec
+primary results in any invocation of
+.Ar utility
+exiting with non-zero exit status, then
+.Nm
+will eventually exit with non-zero status as well,
+but this does not cause
+.Nm
+to exit early.
+.Pp
+.It Ic -execdir Ar utility Oo argument ... Oc Ic \&;
+The
+.Ic -execdir
+primary is similar to the semicolon-terminated
+.Pq Dq Ic \&;
+variant of the
+.Ic -exec
+primary, with the exception that
+.Ar utility
+will be executed from the directory that holds
+the current file.
+Only the base filename is substituted for the string
+.Dq Ic {} .
+Set aggregation
+.Pq Do Ic \&+ Dc termination
+is not supported.
+.Pp
+.It Ic -exit Op Ar status
+This primary causes
+.Nm
+to stop traversing the file system and exit immediately,
+with the specified numeric exit status.
+If the
+.Ar status
+value is not specified, then
+.Nm
+will exit with status zero.
+Note that any preceding primaries will be evaluated and acted upon
+before exiting.
+.Pp
+.It Ic -false
+This primary always evaluates to false.
+This can be used following a primary that caused the
+expression to be true to make the expression to be false.
+This can be useful after using a
+.Ic -fprint
+primary so it can continue to the next expression (using an
+.Cm -or
+operator, for example).
+.Pp
+.It Ic -flags Oo Fl Oc Ns Ar flags
+If
+.Ar flags
+are preceded by a dash
+.Pq Dq Ic \- ,
+this primary evaluates to true
+if at least all of the bits in
+.Ar flags
+are set in the file's flags bits.
+If
+.Ar flags
+are not preceded by a dash, this primary evaluates to true if
+the bits in
+.Ar flags
+exactly match the file's flags bits.
+If
+.Ar flags
+is
+.Dq none ,
+files with no flags bits set are matched.
+(See
+.Xr chflags 1
+for more information about file flags.)
+.Pp
+.It Ic -follow
+Follow symbolic links.
+.Pp
+.It Ic -fprint Ar filename
+This primary always evaluates to true.
+This creates
+.Ar filename
+or overwrites the file if it already exists.
+The file is created at startup.
+It writes the pathname of the current file to this file, followed
+by a newline character.
+The file will be empty if no files are matched.
+.Pp
+.It Ic -fstype Ar type
+True if the file is contained in a file system of type
+.Ar type .
+The
+.Xr sysctl 8
+command can be used to find out the types of file systems
+that are available on the system:
+.Bd -literal -offset indent
+sysctl vfs.generic.fstypes
+.Ed
+.Pp
+In addition, there are two pseudo-types,
+.Dq local
+and
+.Dq rdonly .
+The former matches any file system physically mounted on the system where
+the
+.Nm
+is being executed, and the latter matches any file system which is
+mounted read-only.
+.Pp
+.It Ic -group Ar gname
+True if the file belongs to the group
+.Ar gname .
+If
+.Ar gname
+is numeric and there is no such group name, then
+.Ar gname
+is treated as a group id (and considered a numeric argument).
+.Pp
+.It Ic -iname Ar pattern
+True if the last component of the pathname being examined matches
+.Ar pattern
+in a case-insensitive manner.
+Special shell pattern matching characters
+.Po
+.Dq \&[ ,
+.Dq \&] ,
+.Dq \&* ,
+and
+.Dq \&?
+.Pc
+may be used as part of
+.Ar pattern .
+These characters may be matched explicitly by escaping them with a
+backslash
+.Pq Dq \e .
+.Pp
+.It Ic -inum Ar n
+True if the file has inode number
+.Ar n .
+.Pp
+.It Ic -iregex Ar regexp
+True if the path name of the current file matches the case-insensitive
+basic regular expression
+.Pq see Xr re_format 7
+.Ar regexp .
+This is a match on the whole path, not a search for the regular expression
+within the path.
+.Pp
+.It Ic -links Ar n
+True if the file has
+.Ar n
+links.
+.Pp
+.It Ic -rm
+This primary is an alias for
+.Ic -delete .
+.Pp
+.It Ic -ls
+This primary always evaluates to true.
+The following information for the current file is written to standard output:
+its inode number, size in 512-byte blocks, file permissions, number of hard
+links, owner, group, size in bytes, last modification time, and pathname.
+If the file is a block or character special file, the major and minor numbers
+will be displayed instead of the size in bytes.
+If the file is a symbolic link, the pathname of the linked-to file will be
+displayed preceded by
+.Dq -> .
+The format is identical to that produced by
+.Dq ls -dgils .
+.Pp
+.It Ic -maxdepth Ar depth
+True if the current search depth is less than or equal to what is specified in
+.Ar depth .
+.Pp
+.It Ic -mindepth Ar depth
+True if the current search depth is at least what is specified in
+.Ar depth .
+.Pp
+.It Ic -mmin Ar n
+True if the difference between the file last modification time and the time
+.Nm
+was started, rounded up to the next full minute, is
+.Ar n
+minutes.
+.Pp
+.It Ic -mtime Ar n
+True if the difference between the file last modification time and the time
+.Nm
+was started, rounded up to the next full 24-hour period, is
+.Ar n
+24-hour periods.
+.Pp
+.It Ic -ok Ar utility Oo argument ... Oc Ic \&;
+The
+.Ic -ok
+primary is similar to the semicolon-terminated
+.Pq Dq \&;
+variant of the
+.Ic -exec
+primary, with the exception that
+.Nm
+requests user affirmation for the execution of
+.Ar utility
+by printing
+a message to the terminal and reading a response.
+If the response is other than
+.Dq y ,
+the command is not executed and the
+.Ic -ok
+primary evaluates to false.
+Set aggregation
+.Pq Do \&+ Dc termination
+is not supported.
+.Pp
+.It Ic -name Ar pattern
+True if the last component of the pathname being examined matches
+.Ar pattern .
+Special shell pattern matching characters
+.Po
+.Dq \&[ ,
+.Dq \&] ,
+.Dq \&* ,
+and
+.Dq \&?
+.Pc
+may be used as part of
+.Ar pattern .
+These characters may be matched explicitly by escaping them with a
+backslash
+.Pq Dq \e .
+.Pp
+.It Ic -newer Ar file
+True if the current file has a more recent last modification time than
+.Ar file .
+.Pp
+.It Ic -newerXY Ar reference
+For compatibility with Gnu findutils.
+.Bl -column -offset indent ".Sy findutils" ".Sy equivalent"
+.It Sy findutils Ta Sy find
+.It Sy option Ta Sy equivalent
+.It -neweraa Ta -anewer
+.It -newerat Ta -asince
+.It -newercc Ta -cnewer
+.It -newerct Ta -csince
+.It -newermm Ta -newer
+.It -newermt Ta -since
+.El
+.Pp
+Other option variants from findutils are not implemented.
+.Pp
+.It Ic -nouser
+True if the file belongs to an unknown user.
+.Pp
+.It Ic -nogroup
+True if the file belongs to an unknown group.
+.Pp
+.It Ic -path Ar pattern
+True if the pathname being examined matches
+.Ar pattern .
+Special shell pattern matching characters
+.Po
+.Dq \&[ ,
+.Dq \&] ,
+.Dq \&* ,
+and
+.Dq \&?
+.Pc
+may be used as part of
+.Ar pattern .
+These characters may be matched explicitly by escaping them with a
+backslash
+.Pq Dq \e .
+Slashes
+.Pq Dq /
+are treated as normal characters and do not have to be
+matched explicitly.
+.Pp
+.It Ic -perm Oo Fl Oc Ns Ar mode
+The
+.Ar mode
+may be either symbolic (see
+.Xr chmod 1 )
+or an octal number.
+If the mode is symbolic, a starting value of zero is assumed and the
+mode sets or clears permissions without regard to the process' file mode
+creation mask.
+If the mode is octal, only bits 07777
+.Pf ( Dv S_ISUID
+|
+.Dv S_ISGID
+|
+.Dv S_ISTXT
+|
+.Dv S_IRWXU
+|
+.Dv S_IRWXG
+|
+.Dv S_IRWXO )
+of the file's mode bits participate
+in the comparison.
+If the mode is preceded by a dash
+.Pq Dq Ic \- ,
+this primary evaluates to true
+if at least all of the bits in the mode are set in the file's mode bits.
+If the mode is not preceded by a dash, this primary evaluates to true if
+the bits in the mode exactly match the file's mode bits.
+Note, the first character of a symbolic mode may not be a dash
+.Pq Dq Ic \- .
+.Pp
+.It Ic -print
+This primary always evaluates to true.
+It prints the pathname of the current file to standard output, followed
+by a newline character.
+If none of
+.Ic -delete ,
+.Ic -exec ,
+.Ic -execdir ,
+.Ic -exit ,
+.Ic -fprint ,
+.Ic -ls ,
+.Ic -ok ,
+.Ic -print0 ,
+.Ic -printx ,
+nor
+.Ic -rm
+is specified, the given expression shall be effectively replaced by
+.Cm \&( Ns Ar given\& expression Ns Cm \&)
+.Ic -print .
+.Pp
+.It Ic -print0
+This primary always evaluates to true.
+It prints the pathname of the current file to standard output, followed
+by a NUL character.
+.Pp
+.It Ic -printx
+This primary always evaluates to true.
+It prints the pathname of the current file to standard output,
+with each space, tab, newline, backslash, dollar sign, and single,
+double, or back quotation mark prefixed by a backslash, so the output of
+.Nm
+can safely be used as input to
+.Xr xargs 1 .
+.Pp
+.It Ic -prune
+This primary always evaluates to true.
+It causes
+.Nm
+to not descend into the current file.
+Note, the
+.Ic -prune
+primary has no effect if the
+.Fl d
+option was specified.
+.Pp
+.It Ic -regex Ar regexp
+True if the path name of the current file matches the case-sensitive
+basic regular expression
+.Pq see Xr re_format 7
+.Ar regexp .
+This is a match on the whole path, not a search for the regular expression
+within the path.
+.Pp
+.It Ic -since Ar timestamp
+True if the file last modification time is more recent than
+.Ar timestamp .
+.Pp
+.It Ic -size Ar n Ns Op Cm c
+True if the file's size, rounded up, in 512-byte blocks is
+.Ar n .
+If
+.Ar n
+is followed by a
+.Dq Ic c ,
+then the primary is true if the file's size is
+.Ar n
+bytes.
+.Pp
+.It Ic -type Ar t
+True if the file is of the specified type.
+Possible file types are as follows:
+.Pp
+.Bl -tag -width flag -offset indent -compact
+.It Cm b
+block special
+.It Cm c
+character special
+.It Cm d
+directory
+.It Cm f
+regular file
+.It Cm l
+symbolic link
+.It Cm p
+FIFO
+.It Cm s
+socket
+.It Cm W
+whiteout
+.It Cm w
+whiteout
+.El
+.Pp
+.It Ic -user Ar username
+True if the file belongs to the user
+.Ar username .
+If
+.Ar username
+is numeric and there is no such user on the system, then
+.Ar username
+is treated as a user id (and considered a numeric argument).
+.Pp
+.It Ic -xdev
+This primary always evaluates to true.
+It causes find not to descend past directories that have a different
+device ID
+.Va ( st_dev ,
+see
+.Xr stat 2
+S5.6.2 [POSIX.1]).
+.El
+.Sh OPERATORS
+The primaries may be combined using the following operators.
+The operators are listed in order of decreasing precedence.
+.Bl -tag -width (expression)
+.It Cm \&( Ar expression Cm \&)
+This evaluates to true if the parenthesized expression evaluates to
+true.
+.It Cm \&! Ar expression
+This is the unary
+.Tn NOT
+operator.
+It evaluates to true if the expression is false.
+.It Ar expression Cm -and Ar expression
+.It Ar expression expression
+The
+.Cm -and
+operator is the logical
+.Tn AND
+operator.
+As it is implied by the juxtaposition of two expressions it does not
+have to be specified.
+The expression evaluates to true if both expressions are true.
+The second expression is not evaluated if the first expression is false.
+.It Ar expression Cm -or Ar expression
+The
+.Cm -or
+operator is the logical
+.Tn OR
+operator.
+The expression evaluates to true if either the first or the second expression
+is true.
+The second expression is not evaluated if the first expression is true.
+.El
+.Pp
+All operands and primaries must be separate arguments to
+.Nm .
+Primaries which themselves take arguments expect each argument
+to be a separate argument to
+.Nm .
+.Sh EXIT STATUS
+The
+.Nm
+utility normally exits 0 on success, and exits with 1 under certain
+internal error conditions.
+If any invocations of
+.Dq Ic -exec Ar ... Ic \&+
+primaries return non-zero exit-status, then
+.Nm
+will do so as well.
+.Sh EXAMPLES
+The following examples are shown as given to the shell:
+.Bl -tag -width findx
+.It Li "find / \e! -name \*q*.c\*q \-print"
+Print out a list of all the files whose names do not end in
+.Dq \&.c .
+.It Li "find / \-newer ttt \-user wnj \-print"
+Print out a list of all the files owned by user
+.Dq wnj
+that are newer than the file
+.Dq ttt .
+.It Li "find . \-type f \-mmin \-30 \-print \-or \-mindepth 1 \-prune"
+Print out a list of all the files in the current directory that are
+newer than 30 minutes.
+.It Li "find . \-type f \-atime +10 \-mindepth 2 \-print"
+Print out a list of all the files in any sub-directories that have not
+been accessed in the past ten days.
+.It Li "find . \-mtime +90 \-exec rm \-i {} + \-or \-mindepth 1 \-prune"
+Interactively remove all of the files in the current directory that have
+not been modified in 90 days.
+.It Li "find . \-type f \-mtime +90 \-ok mv {} {}.old \e;"
+Interactively rename all of the files in the current directory and all
+sub-directories that have not been modified in 90 days.
+.It Li "find / \e! \e( \-newer ttt \-user wnj \e) \-print"
+Print out a list of all the files which are not both newer than
+.Dq ttt
+and owned by
+.Dq wnj .
+.It Li "find / \e( \-newer ttt \-or \-user wnj \e) \-print"
+Print out a list of all the files that are either owned by
+.Dq wnj
+or that are newer than
+.Dq ttt .
+.It Li "find / \e( \-newer ttt \-or \-user wnj \e) \-exit 1"
+Return immediately with a value of 1 if any files are found that are either
+owned by
+.Dq wnj
+or that are newer than
+.Dq ttt ,
+but do not print them.
+.It Li "find / \e( \-newer ttt \-or \-user wnj \e) \-ls \-exit 1"
+Same as above, but list the first file matching the criteria before exiting
+with a value of 1.
+.It Li "find . \-type f \-exec sh \-c 'file=\*[q]$1\*[q]; ...;' - {} \;"
+Perform an arbitrarily complex shell command for every file.
+.El
+.Sh SEE ALSO
+.Xr chflags 1 ,
+.Xr chmod 1 ,
+.Xr locate 1 ,
+.Xr xargs 1 ,
+.Xr stat 2 ,
+.Xr fts 3 ,
+.Xr getgrent 3 ,
+.Xr getpwent 3 ,
+.Xr strmode 3 ,
+.Xr re_format 7 ,
+.Xr symlink 7 ,
+.Xr sysctl 8
+.Sh STANDARDS
+The
+.Nm
+utility syntax is a superset of the syntax specified by the
+.St -p1003.2
+standard.
+.Pp
+The options and the
+.Ic -amin ,
+.Ic -anewer ,
+.Ic -asince ,
+.Ic -cmin ,
+.Ic -cnewer ,
+.Ic -csince ,
+.Ic -delete ,
+.Ic -empty ,
+.Ic -execdir ,
+.Ic -follow ,
+.Ic -fstype ,
+.Ic -iname ,
+.Ic -inum ,
+.Ic -iregex ,
+.Ic -links ,
+.Ic -ls ,
+.Ic -maxdepth ,
+.Ic -mindepth ,
+.Ic -mmin ,
+.Ic -path ,
+.Ic -print0 ,
+.Ic -printx ,
+.Ic -regex ,
+.Ic -rm ,
+and
+.Ic -since
+primaries are extensions to
+.St -p1003.2 .
+.Pp
+Historically, the
+.Fl d ,
+.Fl h ,
+and
+.Fl x
+options were implemented using the primaries
+.Dq Ic -depth ,
+.Dq Ic -follow ,
+and
+.Dq Ic -xdev .
+These primaries always evaluated to true, and always
+took effect when the
+.Ar expression
+was parsed, before the file system traversal began.
+As a result, some legal expressions could be confusing.
+For example, in the expression
+.Dq Ic -print Ic -or Ic -depth ,
+.Ic -print
+always evaluates to true, so the standard meaning of
+.Ic -or
+implies that
+.Ic -depth
+would never be evaluated, but that is not what happens;
+in fact,
+.Ic -depth
+takes effect immediately, without testing whether
+.Ic -print
+returns true or false.
+.Pp
+Historically, the operator
+.Dq Ic -or
+was implemented as
+.Dq Ic -o ,
+and the operator
+.Dq Ic -and
+was implemented as
+.Dq Ic -a .
+.Pp
+Historic implementations of the
+.Dq Ic -exec
+and
+.Dq Ic -ok
+primaries did not replace the string
+.Dq Ic {}
+in the utility name or the
+utility arguments if it did not appear as a separate argument.
+This version replaces it no matter where in the utility name or arguments
+it appears.
+.Pp
+Support for
+.Dq Ic -exec Ar ... Ic \&+
+is consistent with
+.Em IEEE PASC Interpretation 1003.2 #210 ,
+though the feature originated in
+.Tn SVR4 .
+.Pp
+The
+.Ic -delete
+primary does not interact well with other options that cause the file system
+tree traversal options to be changed.
+.Sh HISTORY
+A much simpler
+.Nm
+command appeared in First Edition AT&T Unix.
+The syntax had become similar to the present version by
+the time of the Fifth Edition.
+.Sh BUGS
+The special characters used by
+.Nm
+are also special characters to many shell programs.
+In particular, the characters
+.Dq \&* ,
+.Dq \&[ ,
+.Dq \&] ,
+.Dq \&? ,
+.Dq \&( ,
+.Dq \&) ,
+.Dq \&! ,
+.Dq \e ,
+and
+.Dq \&;
+may have to be escaped from the shell.
+.Pp
+As there is no delimiter separating options and file names or file
+names and the
+.Ar expression ,
+it is difficult to specify files named
+.Dq -xdev
+or
+.Dq \&! .
+These problems are handled by the
+.Fl f
+option and the
+.Xr getopt 3
+.Dq --
+construct.
diff --git a/usr.bin/find/find.c b/usr.bin/find/find.c
new file mode 100644
index 0000000..473244b
--- /dev/null
+++ b/usr.bin/find/find.c
@@ -0,0 +1,306 @@
+/* $NetBSD: find.c,v 1.30 2016/06/13 00:04:40 pgoyette Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Cimarron D. Taylor of the University of California, Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)find.c 8.5 (Berkeley) 8/5/94";
+#else
+__RCSID("$NetBSD: find.c,v 1.30 2016/06/13 00:04:40 pgoyette Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+
+#include "find.h"
+
+static int ftscompare(const FTSENT **, const FTSENT **);
+
+/*
+ * find_formplan --
+ * process the command line and create a "plan" corresponding to the
+ * command arguments.
+ */
+PLAN *
+find_formplan(char **argv)
+{
+ PLAN *plan, *tail, *new;
+
+ /*
+ * for each argument in the command line, determine what kind of node
+ * it is, create the appropriate node type and add the new plan node
+ * to the end of the existing plan. The resulting plan is a linked
+ * list of plan nodes. For example, the string:
+ *
+ * % find . -name foo -newer bar -print
+ *
+ * results in the plan:
+ *
+ * [-name foo]--> [-newer bar]--> [-print]
+ *
+ * in this diagram, `[-name foo]' represents the plan node generated
+ * by c_name() with an argument of foo and `-->' represents the
+ * plan->next pointer.
+ */
+ for (plan = tail = NULL; *argv;) {
+ if (!(new = find_create(&argv)))
+ continue;
+ if (plan == NULL)
+ tail = plan = new;
+ else {
+ tail->next = new;
+ tail = new;
+ }
+ }
+
+ /*
+ * if the user didn't specify one of -print, -ok, -fprint, -exec, or
+ * -exit, then -print is assumed so we bracket the current expression
+ * with parens, if necessary, and add a -print node on the end.
+ */
+ if (!isoutput) {
+ if (plan == NULL) {
+ new = c_print(NULL, 0, NULL);
+ tail = plan = new;
+ } else {
+ new = c_openparen(NULL, 0, NULL);
+ new->next = plan;
+ plan = new;
+ new = c_closeparen(NULL, 0, NULL);
+ tail->next = new;
+ tail = new;
+ new = c_print(NULL, 0, NULL);
+ tail->next = new;
+ tail = new;
+ }
+ }
+
+ /*
+ * the command line has been completely processed into a search plan
+ * except for the (, ), !, and -o operators. Rearrange the plan so
+ * that the portions of the plan which are affected by the operators
+ * are moved into operator nodes themselves. For example:
+ *
+ * [!]--> [-name foo]--> [-print]
+ *
+ * becomes
+ *
+ * [! [-name foo] ]--> [-print]
+ *
+ * and
+ *
+ * [(]--> [-depth]--> [-name foo]--> [)]--> [-print]
+ *
+ * becomes
+ *
+ * [expr [-depth]-->[-name foo] ]--> [-print]
+ *
+ * operators are handled in order of precedence.
+ */
+
+ plan = paren_squish(plan); /* ()'s */
+ plan = not_squish(plan); /* !'s */
+ plan = or_squish(plan); /* -o's */
+ return (plan);
+}
+
+static int
+ftscompare(const FTSENT **e1, const FTSENT **e2)
+{
+
+ return (strcoll((*e1)->fts_name, (*e2)->fts_name));
+}
+
+static sigset_t ss;
+static bool notty;
+
+static __inline void
+sig_init(void)
+{
+ struct sigaction sa;
+ notty = !(isatty(STDIN_FILENO) || isatty(STDOUT_FILENO) ||
+ isatty(STDERR_FILENO));
+ if (notty)
+ return;
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGINFO); /* block SIGINFO */
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = show_path;
+ (void)sigaction(SIGINFO, &sa, NULL);
+
+}
+
+static __inline void
+sig_lock(sigset_t *s)
+{
+ if (notty)
+ return;
+ sigprocmask(SIG_BLOCK, &ss, s);
+}
+
+static __inline void
+sig_unlock(const sigset_t *s)
+{
+ if (notty)
+ return;
+ sigprocmask(SIG_SETMASK, s, NULL);
+}
+
+FTS *tree; /* pointer to top of FTS hierarchy */
+FTSENT *g_entry; /* shared with SIGINFO handler */
+
+/*
+ * find_execute --
+ * take a search plan and an array of search paths and executes the plan
+ * over all FTSENT's returned for the given search paths.
+ */
+int
+find_execute(PLAN *plan, char **paths)
+{
+ PLAN *p;
+ int r, rval, cval;
+ sigset_t s;
+
+ cval = 1;
+
+ if (!(tree = fts_open(paths, ftsoptions, issort ? ftscompare : NULL)))
+ err(1, "ftsopen");
+
+ sig_init();
+ sig_lock(&s);
+ for (rval = 0; cval && (g_entry = fts_read(tree)) != NULL;) {
+ switch (g_entry->fts_info) {
+ case FTS_D:
+ if (isdepth)
+ continue;
+ break;
+ case FTS_DP:
+ if (!isdepth)
+ continue;
+ break;
+ case FTS_DNR:
+ case FTS_ERR:
+ case FTS_NS:
+ sig_unlock(&s);
+ (void)fflush(stdout);
+ warnx("%s: %s",
+ g_entry->fts_path, strerror(g_entry->fts_errno));
+ rval = 1;
+ sig_lock(&s);
+ continue;
+ }
+#define BADCH " \t\n\\'\""
+ if (isxargs && strpbrk(g_entry->fts_path, BADCH)) {
+ sig_unlock(&s);
+ (void)fflush(stdout);
+ warnx("%s: illegal path", g_entry->fts_path);
+ rval = 1;
+ sig_lock(&s);
+ continue;
+ }
+
+ /*
+ * Call all the functions in the execution plan until one is
+ * false or all have been executed. This is where we do all
+ * the work specified by the user on the command line.
+ */
+ sig_unlock(&s);
+ for (p = plan; p && (p->eval)(p, g_entry); p = p->next)
+ if (p->type == N_EXIT) {
+ rval = p->exit_val;
+ cval = 0;
+ }
+ sig_lock(&s);
+ }
+
+ sig_unlock(&s);
+ if (g_entry == NULL && errno)
+ err(1, "fts_read");
+ (void)fts_close(tree);
+
+ /*
+ * Cleanup any plans with leftover state.
+ * Keep the last non-zero return value.
+ */
+ if ((r = find_traverse(plan, plan_cleanup, NULL)) != 0)
+ rval = r;
+
+ return (rval);
+}
+
+/*
+ * find_traverse --
+ * traverse the plan tree and execute func() on all plans. This
+ * does not evaluate each plan's eval() function; it is intended
+ * for operations that must run on all plans, such as state
+ * cleanup.
+ *
+ * If any func() returns non-zero, then so will find_traverse().
+ */
+int
+find_traverse(PLAN *plan, int (*func)(PLAN *, void *), void *arg)
+{
+ PLAN *p;
+ int r, rval;
+
+ rval = 0;
+ for (p = plan; p; p = p->next) {
+ if ((r = func(p, arg)) != 0)
+ rval = r;
+ if (p->type == N_EXPR || p->type == N_OR) {
+ if (p->p_data[0])
+ if ((r = find_traverse(p->p_data[0],
+ func, arg)) != 0)
+ rval = r;
+ if (p->p_data[1])
+ if ((r = find_traverse(p->p_data[1],
+ func, arg)) != 0)
+ rval = r;
+ }
+ }
+ return rval;
+}
diff --git a/usr.bin/find/find.h b/usr.bin/find/find.h
new file mode 100644
index 0000000..912e60d
--- /dev/null
+++ b/usr.bin/find/find.h
@@ -0,0 +1,138 @@
+/* $NetBSD: find.h,v 1.26 2016/06/13 00:04:40 pgoyette Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Cimarron D. Taylor of the University of California, Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)find.h 8.1 (Berkeley) 6/6/93
+ */
+
+#include <regex.h>
+#include <time.h>
+
+/* node type */
+enum ntype {
+ N_AND = 1, /* must start > 0 */
+ N_AMIN, N_ANEWER, N_ASINCE, N_ATIME, N_CLOSEPAREN, N_CMIN, N_CNEWER,
+ N_CSINCE, N_CTIME, N_DEPTH, N_EMPTY, N_EXEC, N_EXECDIR, N_EXIT,
+ N_EXPR, N_FALSE, N_FLAGS, N_FOLLOW, N_FPRINT, N_FSTYPE, N_GROUP,
+ N_INAME, N_INUM, N_IREGEX, N_LINKS, N_LS, N_MINDEPTH, N_MAXDEPTH,
+ N_MMIN, N_MTIME, N_NAME, N_NEWER, N_NOGROUP, N_NOT, N_NOUSER, N_OK,
+ N_OPENPAREN, N_OR, N_PATH, N_PERM, N_PRINT, N_PRINT0, N_PRINTX,
+ N_PRUNE, N_REGEX, N_SINCE, N_SIZE, N_TYPE, N_USER, N_XDEV, N_DELETE
+};
+
+/* node definition */
+typedef struct _plandata {
+ struct _plandata *next; /* next node */
+ int (*eval)(struct _plandata *, FTSENT *);
+ /* node evaluation function */
+#define F_EQUAL 1 /* [acm]time inum links size */
+#define F_LESSTHAN 2
+#define F_GREATER 3
+#define F_NEEDOK 1 /* exec ok */
+#define F_PLUSSET 2 /* -exec ... {} + */
+#define F_MTFLAG 1 /* fstype */
+#define F_MTTYPE 2
+#define F_ATLEAST 1 /* perm */
+ int flags; /* private flags */
+ enum ntype type; /* plan node type */
+ union {
+ u_int32_t _f_data; /* flags */
+ gid_t _g_data; /* gid */
+ ino_t _i_data; /* inode */
+ mode_t _m_data; /* mode mask */
+ nlink_t _l_data; /* link count */
+ off_t _o_data; /* file size */
+ time_t _t_data; /* time value */
+ struct timespec _ts_data; /* time value */
+ uid_t _u_data; /* uid */
+ short _mt_data; /* mount flags */
+ struct _plandata *_p_data[2]; /* PLAN trees */
+ struct _ex {
+ char **_e_argv; /* argv array */
+ char **_e_orig; /* original strings */
+ int *_e_len; /* allocated length */
+ char **_ep_bxp; /* ptr to 1st addt'l arg */
+ char *_ep_p; /* current buffer pointer */
+ char *_ep_bbp; /* begin buffer pointer */
+ char *_ep_ebp; /* end buffer pointer */
+ int _ep_maxargs; /* max #args */
+ int _ep_narg; /* # addt'l args */
+ int _ep_rval; /* return value */
+ } ex;
+ char *_a_data[2]; /* array of char pointers */
+ char *_c_data; /* char pointer */
+ int _exit_val; /* exit value */
+ int _max_data; /* tree depth */
+ int _min_data; /* tree depth */
+ regex_t _regexp_data; /* compiled regexp */
+ FILE *_fprint_file; /* file stream for -fprint */
+ } p_un;
+} PLAN;
+#define a_data p_un._a_data
+#define c_data p_un._c_data
+#define i_data p_un._i_data
+#define f_data p_un._f_data
+#define g_data p_un._g_data
+#define l_data p_un._l_data
+#define m_data p_un._m_data
+#define mt_data p_un._mt_data
+#define o_data p_un._o_data
+#define p_data p_un._p_data
+#define t_data p_un._t_data
+#define ts_data p_un._ts_data
+#define u_data p_un._u_data
+#define e_argv p_un.ex._e_argv
+#define e_orig p_un.ex._e_orig
+#define e_len p_un.ex._e_len
+#define ep_p p_un.ex._ep_p
+#define ep_bbp p_un.ex._ep_bbp
+#define ep_ebp p_un.ex._ep_ebp
+#define ep_bxp p_un.ex._ep_bxp
+#define ep_cnt p_un.ex._ep_cnt
+#define ep_maxargs p_un.ex._ep_maxargs
+#define ep_nline p_un.ex._ep_nline
+#define ep_narg p_un.ex._ep_narg
+#define ep_rval p_un.ex._ep_rval
+#define exit_val p_un._exit_val
+#define max_data p_un._max_data
+#define min_data p_un._min_data
+#define regexp_data p_un._regexp_data
+#define fprint_file p_un._fprint_file
+
+typedef struct _option {
+ const char *name; /* option name */
+ enum ntype token; /* token type */
+ PLAN *(*create)(char ***, int, char *); /* create function */
+ int arg; /* function needs arg */
+} OPTION;
+
+#include "extern.h"
diff --git a/usr.bin/find/function.c b/usr.bin/find/function.c
new file mode 100644
index 0000000..02540f2
--- /dev/null
+++ b/usr.bin/find/function.c
@@ -0,0 +1,2025 @@
+/* $NetBSD: function.c,v 1.77 2018/09/04 15:16:15 kre Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Cimarron D. Taylor of the University of California, Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)function.c 8.10 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: function.c,v 1.77 2018/09/04 15:16:15 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/mount.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <fts.h>
+#include <grp.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <tzfile.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "find.h"
+
+#define COMPARE(a, b) { \
+ switch (plan->flags) { \
+ case F_EQUAL: \
+ return (a == b); \
+ case F_LESSTHAN: \
+ return (a < b); \
+ case F_GREATER: \
+ return (a > b); \
+ default: \
+ abort(); \
+ } \
+}
+
+static int64_t find_parsenum(PLAN *, const char *, const char *, char *);
+static void run_f_exec(PLAN *);
+ int f_always_true(PLAN *, FTSENT *);
+ int f_amin(PLAN *, FTSENT *);
+ int f_anewer(PLAN *, FTSENT *);
+ int f_asince(PLAN *, FTSENT *);
+ int f_atime(PLAN *, FTSENT *);
+ int f_cmin(PLAN *, FTSENT *);
+ int f_cnewer(PLAN *, FTSENT *);
+ int f_csince(PLAN *, FTSENT *);
+ int f_ctime(PLAN *, FTSENT *);
+ int f_delete(PLAN *, FTSENT *);
+ int f_empty(PLAN *, FTSENT *);
+ int f_exec(PLAN *, FTSENT *);
+ int f_execdir(PLAN *, FTSENT *);
+ int f_false(PLAN *, FTSENT *);
+ int f_flags(PLAN *, FTSENT *);
+ int f_fprint(PLAN *, FTSENT *);
+ int f_fstype(PLAN *, FTSENT *);
+ int f_group(PLAN *, FTSENT *);
+ int f_iname(PLAN *, FTSENT *);
+ int f_inum(PLAN *, FTSENT *);
+ int f_links(PLAN *, FTSENT *);
+ int f_ls(PLAN *, FTSENT *);
+ int f_mindepth(PLAN *, FTSENT *);
+ int f_maxdepth(PLAN *, FTSENT *);
+ int f_mmin(PLAN *, FTSENT *);
+ int f_mtime(PLAN *, FTSENT *);
+ int f_name(PLAN *, FTSENT *);
+ int f_newer(PLAN *, FTSENT *);
+/*
+ * Unimplemented Gnu findutils options
+ *
+ int f_newerBB(PLAN *, FTSENT *);
+ int f_newerBa(PLAN *, FTSENT *);
+ int f_newerBc(PLAN *, FTSENT *);
+ int f_newerBm(PLAN *, FTSENT *);
+ int f_newerBt(PLAN *, FTSENT *);
+ int f_neweraB(PLAN *, FTSENT *);
+ int f_newerac(PLAN *, FTSENT *);
+ int f_neweram(PLAN *, FTSENT *);
+ int f_newerca(PLAN *, FTSENT *);
+ int f_newercm(PLAN *, FTSENT *);
+ int f_newercB(PLAN *, FTSENT *);
+ int f_newermB(PLAN *, FTSENT *);
+ int f_newerma(PLAN *, FTSENT *);
+ int f_newermc(PLAN *, FTSENT *);
+ *
+ */
+ int f_nogroup(PLAN *, FTSENT *);
+ int f_nouser(PLAN *, FTSENT *);
+ int f_path(PLAN *, FTSENT *);
+ int f_perm(PLAN *, FTSENT *);
+ int f_print(PLAN *, FTSENT *);
+ int f_print0(PLAN *, FTSENT *);
+ int f_printx(PLAN *, FTSENT *);
+ int f_prune(PLAN *, FTSENT *);
+ int f_regex(PLAN *, FTSENT *);
+ int f_since(PLAN *, FTSENT *);
+ int f_size(PLAN *, FTSENT *);
+ int f_type(PLAN *, FTSENT *);
+ int f_user(PLAN *, FTSENT *);
+ int f_not(PLAN *, FTSENT *);
+ int f_or(PLAN *, FTSENT *);
+static PLAN *c_regex_common(char ***, int, enum ntype, bool);
+static PLAN *palloc(enum ntype, int (*)(PLAN *, FTSENT *));
+
+extern int dotfd;
+extern FTS *tree;
+extern time_t now;
+
+/*
+ * find_parsenum --
+ * Parse a string of the form [+-]# and return the value.
+ */
+static int64_t
+find_parsenum(PLAN *plan, const char *option, const char *vp, char *endch)
+{
+ int64_t value;
+ const char *str;
+ char *endchar; /* Pointer to character ending conversion. */
+
+ /* Determine comparison from leading + or -. */
+ str = vp;
+ switch (*str) {
+ case '+':
+ ++str;
+ plan->flags = F_GREATER;
+ break;
+ case '-':
+ ++str;
+ plan->flags = F_LESSTHAN;
+ break;
+ default:
+ plan->flags = F_EQUAL;
+ break;
+ }
+
+ /*
+ * Convert the string with strtol(). Note, if strtol() returns zero
+ * and endchar points to the beginning of the string we know we have
+ * a syntax error.
+ */
+ value = strtoq(str, &endchar, 10);
+ if (value == 0 && endchar == str)
+ errx(1, "%s: %s: illegal numeric value", option, vp);
+ if (endchar[0] && (endch == NULL || endchar[0] != *endch))
+ errx(1, "%s: %s: illegal trailing character", option, vp);
+ if (endch)
+ *endch = endchar[0];
+ return (value);
+}
+
+/*
+ * find_parsedate --
+ *
+ * Validate the timestamp argument or report an error
+ */
+static time_t
+find_parsedate(PLAN *plan, const char *option, const char *vp)
+{
+ time_t timestamp;
+
+ errno = 0;
+ timestamp = parsedate(vp, NULL, NULL);
+ if (timestamp == -1 && errno != 0)
+ errx(1, "%s: %s: invalid timestamp value", option, vp);
+ return timestamp;
+}
+
+/*
+ * The value of n for the inode times (atime, ctime, and mtime) is a range,
+ * i.e. n matches from (n - 1) to n 24 hour periods. This interacts with
+ * -n, such that "-mtime -1" would be less than 0 days, which isn't what the
+ * user wanted. Correct so that -1 is "less than 1".
+ */
+#define TIME_CORRECT(p, ttype) \
+ if ((p)->type == ttype && (p)->flags == F_LESSTHAN) \
+ ++((p)->t_data);
+
+/*
+ * -amin n functions --
+ *
+ * True if the difference between the file access time and the
+ * current time is n 1 minute periods.
+ */
+int
+f_amin(PLAN *plan, FTSENT *entry)
+{
+ COMPARE((now - entry->fts_statp->st_atime +
+ SECSPERMIN - 1) / SECSPERMIN, plan->t_data);
+}
+
+PLAN *
+c_amin(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_AMIN, f_amin);
+ new->t_data = find_parsenum(new, opt, arg, NULL);
+ TIME_CORRECT(new, N_AMIN);
+ return (new);
+}
+
+/*
+ * -anewer file functions --
+ *
+ * True if the current file has been accessed more recently
+ * than the access time of the file named by the pathname
+ * file.
+ */
+int
+f_anewer(PLAN *plan, FTSENT *entry)
+{
+
+ return timespeccmp(&entry->fts_statp->st_atim, &plan->ts_data, >);
+}
+
+PLAN *
+c_anewer(char ***argvp, int isok, char *opt)
+{
+ char *filename = **argvp;
+ PLAN *new;
+ struct stat sb;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ if (stat(filename, &sb))
+ err(1, "%s: %s", opt, filename);
+ new = palloc(N_ANEWER, f_anewer);
+ new->ts_data = sb.st_atim;
+ return (new);
+}
+
+/*
+ * -asince "timestamp" functions --
+ *
+ * True if the file access time is greater than the timestamp value
+ */
+int
+f_asince(PLAN *plan, FTSENT *entry)
+{
+ COMPARE(entry->fts_statp->st_atime, plan->t_data);
+}
+
+PLAN *
+c_asince(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_ASINCE, f_asince);
+ new->t_data = find_parsedate(new, opt, arg);
+ new->flags = F_GREATER;
+ return (new);
+}
+
+/*
+ * -atime n functions --
+ *
+ * True if the difference between the file access time and the
+ * current time is n 24 hour periods.
+ */
+int
+f_atime(PLAN *plan, FTSENT *entry)
+{
+ COMPARE((now - entry->fts_statp->st_atime +
+ SECSPERDAY - 1) / SECSPERDAY, plan->t_data);
+}
+
+PLAN *
+c_atime(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_ATIME, f_atime);
+ new->t_data = find_parsenum(new, opt, arg, NULL);
+ TIME_CORRECT(new, N_ATIME);
+ return (new);
+}
+
+/*
+ * -cmin n functions --
+ *
+ * True if the difference between the last change of file
+ * status information and the current time is n 24 hour periods.
+ */
+int
+f_cmin(PLAN *plan, FTSENT *entry)
+{
+ COMPARE((now - entry->fts_statp->st_ctime +
+ SECSPERMIN - 1) / SECSPERMIN, plan->t_data);
+}
+
+PLAN *
+c_cmin(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_CMIN, f_cmin);
+ new->t_data = find_parsenum(new, opt, arg, NULL);
+ TIME_CORRECT(new, N_CMIN);
+ return (new);
+}
+
+/*
+ * -cnewer file functions --
+ *
+ * True if the current file has been changed more recently
+ * than the changed time of the file named by the pathname
+ * file.
+ */
+int
+f_cnewer(PLAN *plan, FTSENT *entry)
+{
+
+ return timespeccmp(&entry->fts_statp->st_ctim, &plan->ts_data, >);
+}
+
+PLAN *
+c_cnewer(char ***argvp, int isok, char *opt)
+{
+ char *filename = **argvp;
+ PLAN *new;
+ struct stat sb;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ if (stat(filename, &sb))
+ err(1, "%s: %s ", opt, filename);
+ new = palloc(N_CNEWER, f_cnewer);
+ new->ts_data = sb.st_ctim;
+ return (new);
+}
+
+/*
+ * -csince "timestamp" functions --
+ *
+ * True if the file status change time is greater than the timestamp value
+ */
+int
+f_csince(PLAN *plan, FTSENT *entry)
+{
+ COMPARE(entry->fts_statp->st_ctime, plan->t_data);
+}
+
+PLAN *
+c_csince(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_CSINCE, f_csince);
+ new->t_data = find_parsedate(new, opt, arg);
+ new->flags = F_GREATER;
+ return (new);
+}
+
+/*
+ * -ctime n functions --
+ *
+ * True if the difference between the last change of file
+ * status information and the current time is n 24 hour periods.
+ */
+int
+f_ctime(PLAN *plan, FTSENT *entry)
+{
+ COMPARE((now - entry->fts_statp->st_ctime +
+ SECSPERDAY - 1) / SECSPERDAY, plan->t_data);
+}
+
+PLAN *
+c_ctime(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_CTIME, f_ctime);
+ new->t_data = find_parsenum(new, opt, arg, NULL);
+ TIME_CORRECT(new, N_CTIME);
+ return (new);
+}
+
+/*
+ * -delete functions --
+ *
+ * Always true. Makes its best shot and continues on regardless.
+ */
+int
+f_delete(PLAN *plan __unused, FTSENT *entry)
+{
+ /* ignore these from fts */
+ if (strcmp(entry->fts_accpath, ".") == 0 ||
+ strcmp(entry->fts_accpath, "..") == 0)
+ return 1;
+
+ /* sanity check */
+ if (isdepth == 0 || /* depth off */
+ (ftsoptions & FTS_NOSTAT) || /* not stat()ing */
+ !(ftsoptions & FTS_PHYSICAL) || /* physical off */
+ (ftsoptions & FTS_LOGICAL)) /* or finally, logical on */
+ errx(1, "-delete: insecure options got turned on");
+
+ /* Potentially unsafe - do not accept relative paths whatsoever */
+ if (entry->fts_level > 0 && strchr(entry->fts_accpath, '/') != NULL)
+ errx(1, "-delete: %s: relative path potentially not safe",
+ entry->fts_accpath);
+
+ /* Turn off user immutable bits if running as root */
+ if ((entry->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
+ !(entry->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
+ geteuid() == 0)
+ chflags(entry->fts_accpath,
+ entry->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
+
+ /* rmdir directories, unlink everything else */
+ if (S_ISDIR(entry->fts_statp->st_mode)) {
+ if (rmdir(entry->fts_accpath) < 0 && errno != ENOTEMPTY)
+ warn("-delete: rmdir(%s)", entry->fts_path);
+ } else {
+ if (unlink(entry->fts_accpath) < 0)
+ warn("-delete: unlink(%s)", entry->fts_path);
+ }
+
+ /* "succeed" */
+ return 1;
+}
+
+PLAN *
+c_delete(char ***argvp __unused, int isok, char *opt)
+{
+
+ ftsoptions &= ~FTS_NOSTAT; /* no optimize */
+ ftsoptions |= FTS_PHYSICAL; /* disable -follow */
+ ftsoptions &= ~FTS_LOGICAL; /* disable -follow */
+ isoutput = 1; /* possible output */
+ isdepth = 1; /* -depth implied */
+
+ return palloc(N_DELETE, f_delete);
+}
+
+/*
+ * -depth functions --
+ *
+ * Always true, causes descent of the directory hierarchy to be done
+ * so that all entries in a directory are acted on before the directory
+ * itself.
+ */
+int
+f_always_true(PLAN *plan, FTSENT *entry)
+{
+
+ return (1);
+}
+
+PLAN *
+c_depth(char ***argvp, int isok, char *opt)
+{
+ isdepth = 1;
+
+ return (palloc(N_DEPTH, f_always_true));
+}
+
+/*
+ * -empty functions --
+ *
+ * True if the file or directory is empty
+ */
+int
+f_empty(PLAN *plan, FTSENT *entry)
+{
+ if (S_ISREG(entry->fts_statp->st_mode) &&
+ entry->fts_statp->st_size == 0)
+ return (1);
+ if (S_ISDIR(entry->fts_statp->st_mode)) {
+ struct dirent *dp;
+ int empty;
+ DIR *dir;
+
+ empty = 1;
+ dir = opendir(entry->fts_accpath);
+ if (dir == NULL)
+ return (0);
+ for (dp = readdir(dir); dp; dp = readdir(dir))
+ if (dp->d_name[0] != '.' ||
+ (dp->d_name[1] != '\0' &&
+ (dp->d_name[1] != '.' || dp->d_name[2] != '\0'))) {
+ empty = 0;
+ break;
+ }
+ closedir(dir);
+ return (empty);
+ }
+ return (0);
+}
+
+PLAN *
+c_empty(char ***argvp, int isok, char *opt)
+{
+ ftsoptions &= ~FTS_NOSTAT;
+
+ return (palloc(N_EMPTY, f_empty));
+}
+
+/*
+ * [-exec | -ok] utility [arg ... ] ; functions --
+ * [-exec | -ok] utility [arg ... ] {} + functions --
+ *
+ * If the end of the primary expression is delimited by a
+ * semicolon: true if the executed utility returns a zero value
+ * as exit status. If "{}" occurs anywhere, it gets replaced by
+ * the current pathname.
+ *
+ * If the end of the primary expression is delimited by a plus
+ * sign: always true. Pathnames for which the primary is
+ * evaluated shall be aggregated into sets. The utility will be
+ * executed once per set, with "{}" replaced by the entire set of
+ * pathnames (as if xargs). "{}" must appear last.
+ *
+ * The current directory for the execution of utility is the same
+ * as the current directory when the find utility was started.
+ *
+ * The primary -ok is different in that it requests affirmation
+ * of the user before executing the utility.
+ */
+int
+f_exec(PLAN *plan, FTSENT *entry)
+{
+ size_t cnt;
+ int l;
+ pid_t pid;
+ int status;
+
+ if (plan->flags & F_PLUSSET) {
+ /*
+ * Confirm sufficient buffer space, then copy the path
+ * to the buffer.
+ */
+ l = strlen(entry->fts_path);
+ if (plan->ep_p + l < plan->ep_ebp) {
+ plan->ep_bxp[plan->ep_narg++] =
+ strcpy(plan->ep_p, entry->fts_path);
+ plan->ep_p += l + 1;
+
+ if (plan->ep_narg == plan->ep_maxargs)
+ run_f_exec(plan);
+ } else {
+ /*
+ * Without sufficient space to copy in the next
+ * argument, run the command to empty out the
+ * buffer before re-attepting the copy.
+ */
+ run_f_exec(plan);
+ if ((plan->ep_p + l < plan->ep_ebp)) {
+ plan->ep_bxp[plan->ep_narg++]
+ = strcpy(plan->ep_p, entry->fts_path);
+ plan->ep_p += l + 1;
+ } else
+ errx(1, "insufficient space for argument");
+ }
+ return (1);
+ } else {
+ for (cnt = 0; plan->e_argv[cnt]; ++cnt)
+ if (plan->e_len[cnt])
+ brace_subst(plan->e_orig[cnt],
+ &plan->e_argv[cnt],
+ entry->fts_path,
+ &plan->e_len[cnt]);
+ if (plan->flags & F_NEEDOK && !queryuser(plan->e_argv))
+ return (0);
+
+ /* Don't mix output of command with find output. */
+ fflush(stdout);
+ fflush(stderr);
+
+ switch (pid = vfork()) {
+ case -1:
+ err(1, "vfork");
+ /* NOTREACHED */
+ case 0:
+ if (fchdir(dotfd)) {
+ warn("chdir");
+ _exit(1);
+ }
+ execvp(plan->e_argv[0], plan->e_argv);
+ warn("%s", plan->e_argv[0]);
+ _exit(1);
+ }
+ pid = waitpid(pid, &status, 0);
+ return (pid != -1 && WIFEXITED(status)
+ && !WEXITSTATUS(status));
+ }
+}
+
+static void
+run_f_exec(PLAN *plan)
+{
+ pid_t pid;
+ int rval, status;
+
+ /* Ensure arg list is null terminated. */
+ plan->ep_bxp[plan->ep_narg] = NULL;
+
+ /* Don't mix output of command with find output. */
+ fflush(stdout);
+ fflush(stderr);
+
+ switch (pid = vfork()) {
+ case -1:
+ err(1, "vfork");
+ /* NOTREACHED */
+ case 0:
+ if (fchdir(dotfd)) {
+ warn("chdir");
+ _exit(1);
+ }
+ execvp(plan->e_argv[0], plan->e_argv);
+ warn("%s", plan->e_argv[0]);
+ _exit(1);
+ }
+
+ /* Clear out the argument list. */
+ plan->ep_narg = 0;
+ plan->ep_bxp[plan->ep_narg] = NULL;
+ /* As well as the argument buffer. */
+ plan->ep_p = plan->ep_bbp;
+ *plan->ep_p = '\0';
+
+ pid = waitpid(pid, &status, 0);
+ if (WIFEXITED(status))
+ rval = WEXITSTATUS(status);
+ else
+ rval = -1;
+
+ /*
+ * If we have a non-zero exit status, preserve it so find(1) can
+ * later exit with it.
+ */
+ if (rval)
+ plan->ep_rval = rval;
+}
+
+/*
+ * c_exec --
+ * build three parallel arrays, one with pointers to the strings passed
+ * on the command line, one with (possibly duplicated) pointers to the
+ * argv array, and one with integer values that are lengths of the
+ * strings, but also flags meaning that the string has to be massaged.
+ *
+ * If -exec ... {} +, use only the first array, but make it large
+ * enough to hold 5000 args (cf. src/usr.bin/xargs/xargs.c for a
+ * discussion), and then allocate ARG_MAX - 4K of space for args.
+ */
+PLAN *
+c_exec(char ***argvp, int isok, char *opt)
+{
+ PLAN *new; /* node returned */
+ size_t cnt;
+ int brace, lastbrace;
+ char **argv, **ap, *p;
+
+ isoutput = 1;
+
+ new = palloc(N_EXEC, f_exec);
+ if (isok)
+ new->flags |= F_NEEDOK;
+
+ /*
+ * Terminate if we encounter an arg exactly equal to ";", or an
+ * arg exactly equal to "+" following an arg exactly equal to
+ * "{}".
+ */
+ for (ap = argv = *argvp, brace = 0;; ++ap) {
+ if (!*ap)
+ errx(1, "%s: no terminating \";\" or \"+\"", opt);
+ lastbrace = brace;
+ brace = 0;
+ if (strcmp(*ap, "{}") == 0)
+ brace = 1;
+ if (strcmp(*ap, ";") == 0)
+ break;
+ if (strcmp(*ap, "+") == 0 && lastbrace) {
+ new->flags |= F_PLUSSET;
+ break;
+ }
+ }
+
+ /*
+ * POSIX says -ok ... {} + "need not be supported," and it does
+ * not make much sense anyway.
+ */
+ if (new->flags & F_NEEDOK && new->flags & F_PLUSSET)
+ errx(1, "%s: terminating \"+\" not permitted.", opt);
+
+ if (new->flags & F_PLUSSET) {
+ size_t c, bufsize;
+
+ cnt = ap - *argvp - 1; /* units are words */
+ new->ep_maxargs = ARG_MAX / (sizeof (char *) + 16);
+ if (new->ep_maxargs > 5000)
+ new->ep_maxargs = 5000;
+ new->e_argv = emalloc((cnt + new->ep_maxargs)
+ * sizeof(*new->e_argv));
+
+ /* We start stuffing arguments after the user's last one. */
+ new->ep_bxp = &new->e_argv[cnt];
+ new->ep_narg = 0;
+
+ /*
+ * Count up the space of the user's arguments, and
+ * subtract that from what we allocate.
+ */
+#define MAXARG (ARG_MAX - 4 * 1024)
+ for (argv = *argvp, c = 0, cnt = 0;
+ argv < ap;
+ ++argv, ++cnt) {
+ c += strlen(*argv) + 1;
+ if (c >= MAXARG)
+ errx(1, "Arguments too long");
+ new->e_argv[cnt] = *argv;
+ }
+ if (c + new->ep_maxargs * sizeof (char *) >= MAXARG)
+ errx(1, "Arguments too long");
+ bufsize = MAXARG - c - new->ep_maxargs * sizeof (char *);
+
+ /*
+ * Allocate, and then initialize current, base, and
+ * end pointers.
+ */
+ new->ep_p = new->ep_bbp = emalloc(bufsize + 1);
+ new->ep_ebp = new->ep_bbp + bufsize - 1;
+ new->ep_rval = 0;
+ } else { /* !F_PLUSSET */
+ cnt = ap - *argvp + 1;
+ new->e_argv = emalloc(cnt * sizeof(*new->e_argv));
+ new->e_orig = emalloc(cnt * sizeof(*new->e_orig));
+ new->e_len = emalloc(cnt * sizeof(*new->e_len));
+
+ for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
+ new->e_orig[cnt] = *argv;
+ for (p = *argv; *p; ++p)
+ if (p[0] == '{' && p[1] == '}') {
+ new->e_argv[cnt] =
+ emalloc(MAXPATHLEN);
+ new->e_len[cnt] = MAXPATHLEN;
+ break;
+ }
+ if (!*p) {
+ new->e_argv[cnt] = *argv;
+ new->e_len[cnt] = 0;
+ }
+ }
+ new->e_orig[cnt] = NULL;
+ }
+
+ new->e_argv[cnt] = NULL;
+ *argvp = argv + 1;
+ return (new);
+}
+
+/*
+ * -execdir utility [arg ... ] ; functions --
+ *
+ * True if the executed utility returns a zero value as exit status.
+ * The end of the primary expression is delimited by a semicolon. If
+ * "{}" occurs anywhere, it gets replaced by the unqualified pathname.
+ * The current directory for the execution of utility is the same as
+ * the directory where the file lives.
+ */
+int
+f_execdir(PLAN *plan, FTSENT *entry)
+{
+ size_t cnt;
+ pid_t pid;
+ int status;
+ char *file;
+
+ /* XXX - if file/dir ends in '/' this will not work -- can it? */
+ if ((file = strrchr(entry->fts_path, '/')))
+ file++;
+ else
+ file = entry->fts_path;
+
+ for (cnt = 0; plan->e_argv[cnt]; ++cnt)
+ if (plan->e_len[cnt])
+ brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt],
+ file, &plan->e_len[cnt]);
+
+ /* don't mix output of command with find output */
+ fflush(stdout);
+ fflush(stderr);
+
+ switch (pid = vfork()) {
+ case -1:
+ err(1, "fork");
+ /* NOTREACHED */
+ case 0:
+ execvp(plan->e_argv[0], plan->e_argv);
+ warn("%s", plan->e_argv[0]);
+ _exit(1);
+ }
+ pid = waitpid(pid, &status, 0);
+ return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
+}
+
+/*
+ * c_execdir --
+ * build three parallel arrays, one with pointers to the strings passed
+ * on the command line, one with (possibly duplicated) pointers to the
+ * argv array, and one with integer values that are lengths of the
+ * strings, but also flags meaning that the string has to be massaged.
+ */
+PLAN *
+c_execdir(char ***argvp, int isok, char *opt)
+{
+ PLAN *new; /* node returned */
+ size_t cnt;
+ char **argv, **ap, *p;
+
+ ftsoptions &= ~FTS_NOSTAT;
+ isoutput = 1;
+
+ new = palloc(N_EXECDIR, f_execdir);
+
+ for (ap = argv = *argvp;; ++ap) {
+ if (!*ap)
+ errx(1, "%s: no terminating \";\"", opt);
+ if (**ap == ';')
+ break;
+ }
+
+ cnt = ap - *argvp + 1;
+ new->e_argv = emalloc(cnt * sizeof(*new->e_argv));
+ new->e_orig = emalloc(cnt * sizeof(*new->e_orig));
+ new->e_len = emalloc(cnt * sizeof(*new->e_len));
+
+ for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
+ new->e_orig[cnt] = *argv;
+ for (p = *argv; *p; ++p)
+ if (p[0] == '{' && p[1] == '}') {
+ new->e_argv[cnt] = emalloc(MAXPATHLEN);
+ new->e_len[cnt] = MAXPATHLEN;
+ break;
+ }
+ if (!*p) {
+ new->e_argv[cnt] = *argv;
+ new->e_len[cnt] = 0;
+ }
+ }
+ new->e_argv[cnt] = new->e_orig[cnt] = NULL;
+
+ *argvp = argv + 1;
+ return (new);
+}
+
+PLAN *
+c_exit(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ /* not technically true, but otherwise '-print' is implied */
+ isoutput = 1;
+
+ new = palloc(N_EXIT, f_always_true);
+
+ if (arg) {
+ (*argvp)++;
+ new->exit_val = find_parsenum(new, opt, arg, NULL);
+ } else
+ new->exit_val = 0;
+
+ return (new);
+}
+
+
+/*
+ * -false function
+ */
+int
+f_false(PLAN *plan, FTSENT *entry)
+{
+
+ return (0);
+}
+
+PLAN *
+c_false(char ***argvp, int isok, char *opt)
+{
+ return (palloc(N_FALSE, f_false));
+}
+
+
+/*
+ * -flags [-]flags functions --
+ */
+int
+f_flags(PLAN *plan, FTSENT *entry)
+{
+ u_int32_t flags;
+
+ flags = entry->fts_statp->st_flags;
+ if (plan->flags == F_ATLEAST)
+ return ((plan->f_data | flags) == flags);
+ else
+ return (flags == plan->f_data);
+ /* NOTREACHED */
+}
+
+PLAN *
+c_flags(char ***argvp, int isok, char *opt)
+{
+ char *flags = **argvp;
+ PLAN *new;
+ u_long flagset;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_FLAGS, f_flags);
+
+ if (*flags == '-') {
+ new->flags = F_ATLEAST;
+ ++flags;
+ }
+
+ flagset = 0;
+ if ((strcmp(flags, "none") != 0) &&
+ (string_to_flags(&flags, &flagset, NULL) != 0))
+ errx(1, "%s: %s: illegal flags string", opt, flags);
+ new->f_data = flagset;
+ return (new);
+}
+
+/*
+ * -follow functions --
+ *
+ * Always true, causes symbolic links to be followed on a global
+ * basis.
+ */
+PLAN *
+c_follow(char ***argvp, int isok, char *opt)
+{
+ ftsoptions &= ~FTS_PHYSICAL;
+ ftsoptions |= FTS_LOGICAL;
+
+ return (palloc(N_FOLLOW, f_always_true));
+}
+
+/* -fprint functions --
+ *
+ * Causes the current pathame to be written to the defined output file.
+ */
+int
+f_fprint(PLAN *plan, FTSENT *entry)
+{
+
+ if (-1 == fprintf(plan->fprint_file, "%s\n", entry->fts_path))
+ warn("fprintf");
+
+ return(1);
+
+ /* no descriptors are closed; they will be closed by
+ operating system when this find command exits. */
+}
+
+PLAN *
+c_fprint(char ***argvp, int isok, char *opt)
+{
+ PLAN *new;
+
+ isoutput = 1; /* do not assume -print */
+
+ new = palloc(N_FPRINT, f_fprint);
+
+ if (NULL == (new->fprint_file = fopen(**argvp, "w")))
+ err(1, "%s: %s: cannot create file", opt, **argvp);
+
+ (*argvp)++;
+ return (new);
+}
+
+/*
+ * -fstype functions --
+ *
+ * True if the file is of a certain type.
+ */
+int
+f_fstype(PLAN *plan, FTSENT *entry)
+{
+ static dev_t curdev; /* need a guaranteed illegal dev value */
+ static int first = 1;
+ struct statvfs sb;
+ static short val;
+ static char fstype[sizeof(sb.f_fstypename)];
+ char *p, save[2];
+
+ memset(&save, 0, sizeof save); /* XXX gcc */
+
+ /* Only check when we cross mount point. */
+ if (first || curdev != entry->fts_statp->st_dev) {
+ curdev = entry->fts_statp->st_dev;
+
+ /*
+ * Statfs follows symlinks; find wants the link's file system,
+ * not where it points.
+ */
+ if (entry->fts_info == FTS_SL ||
+ entry->fts_info == FTS_SLNONE) {
+ if ((p = strrchr(entry->fts_accpath, '/')) != NULL)
+ ++p;
+ else
+ p = entry->fts_accpath;
+ save[0] = p[0];
+ p[0] = '.';
+ save[1] = p[1];
+ p[1] = '\0';
+
+ } else
+ p = NULL;
+
+ if (statvfs(entry->fts_accpath, &sb))
+ err(1, "%s", entry->fts_accpath);
+
+ if (p) {
+ p[0] = save[0];
+ p[1] = save[1];
+ }
+
+ first = 0;
+
+ /*
+ * Further tests may need both of these values, so
+ * always copy both of them.
+ */
+ val = sb.f_flag;
+ strlcpy(fstype, sb.f_fstypename, sizeof(fstype));
+ }
+ switch (plan->flags) {
+ case F_MTFLAG:
+ return (val & plan->mt_data);
+ case F_MTTYPE:
+ return (strncmp(fstype, plan->c_data, sizeof(fstype)) == 0);
+ default:
+ abort();
+ }
+}
+
+PLAN *
+c_fstype(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_FSTYPE, f_fstype);
+
+ switch (*arg) {
+ case 'l':
+ if (!strcmp(arg, "local")) {
+ new->flags = F_MTFLAG;
+ new->mt_data = MNT_LOCAL;
+ return (new);
+ }
+ break;
+ case 'r':
+ if (!strcmp(arg, "rdonly")) {
+ new->flags = F_MTFLAG;
+ new->mt_data = MNT_RDONLY;
+ return (new);
+ }
+ break;
+ }
+
+ new->flags = F_MTTYPE;
+ new->c_data = arg;
+ return (new);
+}
+
+/*
+ * -group gname functions --
+ *
+ * True if the file belongs to the group gname. If gname is numeric and
+ * an equivalent of the getgrnam() function does not return a valid group
+ * name, gname is taken as a group ID.
+ */
+int
+f_group(PLAN *plan, FTSENT *entry)
+{
+
+ COMPARE(entry->fts_statp->st_gid, plan->g_data);
+}
+
+PLAN *
+c_group(char ***argvp, int isok, char *opt)
+{
+ char *gname = **argvp;
+ PLAN *new;
+ struct group *g;
+ gid_t gid;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_GROUP, f_group);
+ g = getgrnam(gname);
+ if (g == NULL) {
+ if (atoi(gname) == 0 && gname[0] != '0' &&
+ strcmp(gname, "+0") && strcmp(gname, "-0"))
+ errx(1, "%s: %s: no such group", opt, gname);
+ gid = find_parsenum(new, "-group", gname, NULL);
+
+ } else {
+ new->flags = F_EQUAL;
+ gid = g->gr_gid;
+ }
+
+ new->g_data = gid;
+ return (new);
+}
+
+/*
+ * -inum n functions --
+ *
+ * True if the file has inode # n.
+ */
+int
+f_inum(PLAN *plan, FTSENT *entry)
+{
+
+ COMPARE(entry->fts_statp->st_ino, plan->i_data);
+}
+
+PLAN *
+c_inum(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_INUM, f_inum);
+ new->i_data = find_parsenum(new, opt, arg, NULL);
+ return (new);
+}
+
+/*
+ * -links n functions --
+ *
+ * True if the file has n links.
+ */
+int
+f_links(PLAN *plan, FTSENT *entry)
+{
+
+ COMPARE(entry->fts_statp->st_nlink, plan->l_data);
+}
+
+PLAN *
+c_links(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_LINKS, f_links);
+ new->l_data = (nlink_t)find_parsenum(new, opt, arg, NULL);
+ return (new);
+}
+
+/*
+ * -ls functions --
+ *
+ * Always true - prints the current entry to stdout in "ls" format.
+ */
+int
+f_ls(PLAN *plan, FTSENT *entry)
+{
+
+ printlong(entry->fts_path, entry->fts_accpath, entry->fts_statp);
+ return (1);
+}
+
+PLAN *
+c_ls(char ***argvp, int isok, char *opt)
+{
+
+ ftsoptions &= ~FTS_NOSTAT;
+ isoutput = 1;
+
+ return (palloc(N_LS, f_ls));
+}
+
+/*
+ * - maxdepth n functions --
+ *
+ * True if the current search depth is less than or equal to the
+ * maximum depth specified
+ */
+int
+f_maxdepth(PLAN *plan, FTSENT *entry)
+{
+ extern FTS *tree;
+
+ if (entry->fts_level >= plan->max_data)
+ fts_set(tree, entry, FTS_SKIP);
+ return (entry->fts_level <= plan->max_data);
+}
+
+PLAN *
+c_maxdepth(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ new = palloc(N_MAXDEPTH, f_maxdepth);
+ new->max_data = atoi(arg);
+ return (new);
+}
+
+/*
+ * - mindepth n functions --
+ *
+ * True if the current search depth is greater than or equal to the
+ * minimum depth specified
+ */
+int
+f_mindepth(PLAN *plan, FTSENT *entry)
+{
+ return (entry->fts_level >= plan->min_data);
+}
+
+PLAN *
+c_mindepth(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ new = palloc(N_MINDEPTH, f_mindepth);
+ new->min_data = atoi(arg);
+ return (new);
+}
+
+/*
+ * -mmin n functions --
+ *
+ * True if the difference between the file modification time and the
+ * current time is n 24 hour periods.
+ */
+int
+f_mmin(PLAN *plan, FTSENT *entry)
+{
+ COMPARE((now - entry->fts_statp->st_mtime + SECSPERMIN - 1) /
+ SECSPERMIN, plan->t_data);
+}
+
+PLAN *
+c_mmin(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_MMIN, f_mmin);
+ new->t_data = find_parsenum(new, opt, arg, NULL);
+ TIME_CORRECT(new, N_MMIN);
+ return (new);
+}
+
+/*
+ * -mtime n functions --
+ *
+ * True if the difference between the file modification time and the
+ * current time is n 24 hour periods.
+ */
+int
+f_mtime(PLAN *plan, FTSENT *entry)
+{
+ COMPARE((now - entry->fts_statp->st_mtime + SECSPERDAY - 1) /
+ SECSPERDAY, plan->t_data);
+}
+
+PLAN *
+c_mtime(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_MTIME, f_mtime);
+ new->t_data = find_parsenum(new, opt, arg, NULL);
+ TIME_CORRECT(new, N_MTIME);
+ return (new);
+}
+
+/*
+ * -name functions --
+ *
+ * True if the basename of the filename being examined
+ * matches pattern using Pattern Matching Notation S3.14
+ */
+int
+f_name(PLAN *plan, FTSENT *entry)
+{
+
+ return (!fnmatch(plan->c_data, entry->fts_name, 0));
+}
+
+PLAN *
+c_name(char ***argvp, int isok, char *opt)
+{
+ char *pattern = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ new = palloc(N_NAME, f_name);
+ new->c_data = pattern;
+ return (new);
+}
+
+/*
+ * -iname functions --
+ *
+ * Similar to -name, but does case insensitive matching
+ *
+ */
+int
+f_iname(PLAN *plan, FTSENT *entry)
+{
+ return (!fnmatch(plan->c_data, entry->fts_name, FNM_CASEFOLD));
+}
+
+PLAN *
+c_iname(char ***argvp, int isok, char *opt)
+{
+ char *pattern = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ new = palloc(N_INAME, f_iname);
+ new->c_data = pattern;
+ return (new);
+}
+
+/*
+ * -newer file functions --
+ *
+ * True if the current file has been modified more recently
+ * than the modification time of the file named by the pathname
+ * file.
+ */
+int
+f_newer(PLAN *plan, FTSENT *entry)
+{
+
+ return timespeccmp(&entry->fts_statp->st_mtim, &plan->ts_data, >);
+}
+
+PLAN *
+c_newer(char ***argvp, int isok, char *opt)
+{
+ char *filename = **argvp;
+ PLAN *new;
+ struct stat sb;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ if (stat(filename, &sb))
+ err(1, "%s: %s", opt, filename);
+ new = palloc(N_NEWER, f_newer);
+ new->ts_data = sb.st_mtim;
+ return (new);
+}
+
+/*
+ * -nogroup functions --
+ *
+ * True if file belongs to a user ID for which the equivalent
+ * of the getgrnam() 9.2.1 [POSIX.1] function returns NULL.
+ */
+int
+f_nogroup(PLAN *plan, FTSENT *entry)
+{
+
+ return (group_from_gid(entry->fts_statp->st_gid, 1) ? 0 : 1);
+}
+
+PLAN *
+c_nogroup(char ***argvp, int isok, char *opt)
+{
+ ftsoptions &= ~FTS_NOSTAT;
+
+ return (palloc(N_NOGROUP, f_nogroup));
+}
+
+/*
+ * -nouser functions --
+ *
+ * True if file belongs to a user ID for which the equivalent
+ * of the getpwuid() 9.2.2 [POSIX.1] function returns NULL.
+ */
+int
+f_nouser(PLAN *plan, FTSENT *entry)
+{
+
+ return (user_from_uid(entry->fts_statp->st_uid, 1) ? 0 : 1);
+}
+
+PLAN *
+c_nouser(char ***argvp, int isok, char *opt)
+{
+ ftsoptions &= ~FTS_NOSTAT;
+
+ return (palloc(N_NOUSER, f_nouser));
+}
+
+/*
+ * -path functions --
+ *
+ * True if the path of the filename being examined
+ * matches pattern using Pattern Matching Notation S3.14
+ */
+int
+f_path(PLAN *plan, FTSENT *entry)
+{
+
+ return (!fnmatch(plan->c_data, entry->fts_path, 0));
+}
+
+PLAN *
+c_path(char ***argvp, int isok, char *opt)
+{
+ char *pattern = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ new = palloc(N_NAME, f_path);
+ new->c_data = pattern;
+ return (new);
+}
+
+/*
+ * -perm functions --
+ *
+ * The mode argument is used to represent file mode bits. If it starts
+ * with a leading digit, it's treated as an octal mode, otherwise as a
+ * symbolic mode.
+ */
+int
+f_perm(PLAN *plan, FTSENT *entry)
+{
+ mode_t mode;
+
+ mode = entry->fts_statp->st_mode &
+ (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO);
+ if (plan->flags == F_ATLEAST)
+ return ((plan->m_data | mode) == mode);
+ else
+ return (mode == plan->m_data);
+ /* NOTREACHED */
+}
+
+PLAN *
+c_perm(char ***argvp, int isok, char *opt)
+{
+ char *perm = **argvp;
+ PLAN *new;
+ mode_t *set;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_PERM, f_perm);
+
+ if (*perm == '-') {
+ new->flags = F_ATLEAST;
+ ++perm;
+ }
+
+ if ((set = setmode(perm)) == NULL)
+ err(1, "%s: Cannot set file mode `%s'", opt, perm);
+
+ new->m_data = getmode(set, 0);
+ free(set);
+ return (new);
+}
+
+/*
+ * -print functions --
+ *
+ * Always true, causes the current pathame to be written to
+ * standard output.
+ */
+int
+f_print(PLAN *plan, FTSENT *entry)
+{
+
+ (void)printf("%s\n", entry->fts_path);
+ return (1);
+}
+
+int
+f_print0(PLAN *plan, FTSENT *entry)
+{
+
+ (void)fputs(entry->fts_path, stdout);
+ (void)fputc('\0', stdout);
+ return (1);
+}
+
+int
+f_printx(PLAN *plan, FTSENT *entry)
+{
+ char *cp;
+
+ for (cp = entry->fts_path; *cp; cp++) {
+ if (*cp == '\'' || *cp == '\"' || *cp == ' ' ||
+ *cp == '$' || *cp == '`' ||
+ *cp == '\t' || *cp == '\n' || *cp == '\\')
+ fputc('\\', stdout);
+
+ fputc(*cp, stdout);
+ }
+
+ fputc('\n', stdout);
+ return (1);
+}
+
+PLAN *
+c_print(char ***argvp, int isok, char *opt)
+{
+
+ isoutput = 1;
+
+ return (palloc(N_PRINT, f_print));
+}
+
+PLAN *
+c_print0(char ***argvp, int isok, char *opt)
+{
+
+ isoutput = 1;
+
+ return (palloc(N_PRINT0, f_print0));
+}
+
+PLAN *
+c_printx(char ***argvp, int isok, char *opt)
+{
+
+ isoutput = 1;
+
+ return (palloc(N_PRINTX, f_printx));
+}
+
+/*
+ * -prune functions --
+ *
+ * Prune a portion of the hierarchy.
+ */
+int
+f_prune(PLAN *plan, FTSENT *entry)
+{
+ if (fts_set(tree, entry, FTS_SKIP))
+ err(1, "%s", entry->fts_path);
+ return (1);
+}
+
+PLAN *
+c_prune(char ***argvp, int isok, char *opt)
+{
+
+ return (palloc(N_PRUNE, f_prune));
+}
+
+/*
+ * -regex regexp (and related) functions --
+ *
+ * True if the complete file path matches the regular expression regexp.
+ * For -regex, regexp is a case-sensitive (basic) regular expression.
+ * For -iregex, regexp is a case-insensitive (basic) regular expression.
+ */
+int
+f_regex(PLAN *plan, FTSENT *entry)
+{
+
+ return (regexec(&plan->regexp_data, entry->fts_path, 0, NULL, 0) == 0);
+}
+
+static PLAN *
+c_regex_common(char ***argvp, int isok, enum ntype type, bool icase)
+{
+ char errbuf[LINE_MAX];
+ regex_t reg;
+ char *regexp = **argvp;
+ char *lineregexp;
+ PLAN *new;
+ int rv;
+ size_t len;
+
+ (*argvp)++;
+
+ len = strlen(regexp) + 1 + 6;
+ lineregexp = malloc(len); /* max needed */
+ if (lineregexp == NULL)
+ err(1, NULL);
+ snprintf(lineregexp, len, "^%s(%s%s)$",
+ (regcomp_flags & REG_EXTENDED) ? "" : "\\", regexp,
+ (regcomp_flags & REG_EXTENDED) ? "" : "\\");
+ rv = regcomp(&reg, lineregexp, REG_NOSUB|regcomp_flags|
+ (icase ? REG_ICASE : 0));
+ free(lineregexp);
+ if (rv != 0) {
+ regerror(rv, &reg, errbuf, sizeof errbuf);
+ errx(1, "regexp %s: %s", regexp, errbuf);
+ }
+
+ new = palloc(type, f_regex);
+ new->regexp_data = reg;
+ return (new);
+}
+
+PLAN *
+c_regex(char ***argvp, int isok, char *opt)
+{
+
+ return (c_regex_common(argvp, isok, N_REGEX, false));
+}
+
+PLAN *
+c_iregex(char ***argvp, int isok, char *opt)
+{
+
+ return (c_regex_common(argvp, isok, N_IREGEX, true));
+}
+
+/*
+ * -since "timestamp" functions --
+ *
+ * True if the file modification time is greater than the timestamp value
+ */
+int
+f_since(PLAN *plan, FTSENT *entry)
+{
+ COMPARE(entry->fts_statp->st_mtime, plan->t_data);
+}
+
+PLAN *
+c_since(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_SINCE, f_since);
+ new->t_data = find_parsedate(new, opt, arg);
+ new->flags = F_GREATER;
+ return (new);
+}
+
+/*
+ * -size n[c] functions --
+ *
+ * True if the file size in bytes, divided by an implementation defined
+ * value and rounded up to the next integer, is n. If n is followed by
+ * a c, the size is in bytes.
+ */
+#define FIND_SIZE 512
+static int divsize = 1;
+
+int
+f_size(PLAN *plan, FTSENT *entry)
+{
+ off_t size;
+
+ size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) /
+ FIND_SIZE : entry->fts_statp->st_size;
+ COMPARE(size, plan->o_data);
+}
+
+PLAN *
+c_size(char ***argvp, int isok, char *opt)
+{
+ char *arg = **argvp;
+ PLAN *new;
+ char endch;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_SIZE, f_size);
+ endch = 'c';
+ new->o_data = find_parsenum(new, opt, arg, &endch);
+ if (endch == 'c')
+ divsize = 0;
+ return (new);
+}
+
+/*
+ * -type c functions --
+ *
+ * True if the type of the file is c, where c is b, c, d, p, f or w
+ * for block special file, character special file, directory, FIFO,
+ * regular file or whiteout respectively.
+ */
+int
+f_type(PLAN *plan, FTSENT *entry)
+{
+
+ return ((entry->fts_statp->st_mode & S_IFMT) == plan->m_data);
+}
+
+PLAN *
+c_type(char ***argvp, int isok, char *opt)
+{
+ char *typestring = **argvp;
+ PLAN *new;
+ mode_t mask = (mode_t)0;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ switch (typestring[0]) {
+ case 'b':
+ mask = S_IFBLK;
+ break;
+ case 'c':
+ mask = S_IFCHR;
+ break;
+ case 'd':
+ mask = S_IFDIR;
+ break;
+ case 'f':
+ mask = S_IFREG;
+ break;
+ case 'l':
+ mask = S_IFLNK;
+ break;
+ case 'p':
+ mask = S_IFIFO;
+ break;
+ case 's':
+ mask = S_IFSOCK;
+ break;
+#ifdef S_IFWHT
+ case 'W':
+ case 'w':
+ mask = S_IFWHT;
+#ifdef FTS_WHITEOUT
+ ftsoptions |= FTS_WHITEOUT;
+#endif
+ break;
+#endif /* S_IFWHT */
+ default:
+ errx(1, "%s: %s: unknown type", opt, typestring);
+ }
+
+ new = palloc(N_TYPE, f_type);
+ new->m_data = mask;
+ return (new);
+}
+
+/*
+ * -user uname functions --
+ *
+ * True if the file belongs to the user uname. If uname is numeric and
+ * an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not
+ * return a valid user name, uname is taken as a user ID.
+ */
+int
+f_user(PLAN *plan, FTSENT *entry)
+{
+
+ COMPARE(entry->fts_statp->st_uid, plan->u_data);
+}
+
+PLAN *
+c_user(char ***argvp, int isok, char *opt)
+{
+ char *username = **argvp;
+ PLAN *new;
+ struct passwd *p;
+ uid_t uid;
+
+ (*argvp)++;
+ ftsoptions &= ~FTS_NOSTAT;
+
+ new = palloc(N_USER, f_user);
+ p = getpwnam(username);
+ if (p == NULL) {
+ if (atoi(username) == 0 && username[0] != '0' &&
+ strcmp(username, "+0") && strcmp(username, "-0"))
+ errx(1, "%s: %s: no such user", opt, username);
+ uid = find_parsenum(new, opt, username, NULL);
+
+ } else {
+ new->flags = F_EQUAL;
+ uid = p->pw_uid;
+ }
+
+ new->u_data = uid;
+ return (new);
+}
+
+/*
+ * -xdev functions --
+ *
+ * Always true, causes find not to descend past directories that have a
+ * different device ID (st_dev, see stat() S5.6.2 [POSIX.1])
+ */
+PLAN *
+c_xdev(char ***argvp, int isok, char *opt)
+{
+ ftsoptions |= FTS_XDEV;
+
+ return (palloc(N_XDEV, f_always_true));
+}
+
+/*
+ * ( expression ) functions --
+ *
+ * True if expression is true.
+ */
+int
+f_expr(PLAN *plan, FTSENT *entry)
+{
+ PLAN *p;
+ int state;
+
+ state = 0;
+ for (p = plan->p_data[0];
+ p && (state = (p->eval)(p, entry)); p = p->next);
+ return (state);
+}
+
+/*
+ * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers. They are
+ * eliminated during phase 2 of find_formplan() --- the '(' node is converted
+ * to a N_EXPR node containing the expression and the ')' node is discarded.
+ */
+PLAN *
+c_openparen(char ***argvp, int isok, char *opt)
+{
+
+ return (palloc(N_OPENPAREN, (int (*)(PLAN *, FTSENT *))-1));
+}
+
+PLAN *
+c_closeparen(char ***argvp, int isok, char *opt)
+{
+
+ return (palloc(N_CLOSEPAREN, (int (*)(PLAN *, FTSENT *))-1));
+}
+
+/*
+ * ! expression functions --
+ *
+ * Negation of a primary; the unary NOT operator.
+ */
+int
+f_not(PLAN *plan, FTSENT *entry)
+{
+ PLAN *p;
+ int state;
+
+ state = 0;
+ for (p = plan->p_data[0];
+ p && (state = (p->eval)(p, entry)); p = p->next);
+ return (!state);
+}
+
+PLAN *
+c_not(char ***argvp, int isok, char *opt)
+{
+
+ return (palloc(N_NOT, f_not));
+}
+
+/*
+ * expression -o expression functions --
+ *
+ * Alternation of primaries; the OR operator. The second expression is
+ * not evaluated if the first expression is true.
+ */
+int
+f_or(PLAN *plan, FTSENT *entry)
+{
+ PLAN *p;
+ int state;
+
+ state = 0;
+ for (p = plan->p_data[0];
+ p && (state = (p->eval)(p, entry)); p = p->next);
+
+ if (state)
+ return (1);
+
+ for (p = plan->p_data[1];
+ p && (state = (p->eval)(p, entry)); p = p->next);
+ return (state);
+}
+
+PLAN *
+c_or(char ***argvp, int isok, char *opt)
+{
+
+ return (palloc(N_OR, f_or));
+}
+
+PLAN *
+c_null(char ***argvp, int isok, char *opt)
+{
+
+ return (NULL);
+}
+
+
+/*
+ * plan_cleanup --
+ * Check and see if the specified plan has any residual state,
+ * and if so, clean it up as appropriate.
+ *
+ * At the moment, only N_EXEC has state. Two kinds: 1)
+ * lists of files to feed to subprocesses 2) State on exit
+ * statusses of past subprocesses.
+ */
+/* ARGSUSED1 */
+int
+plan_cleanup(PLAN *plan, void *arg)
+{
+ if (plan->type==N_EXEC && plan->ep_narg)
+ run_f_exec(plan);
+
+ return plan->ep_rval; /* Passed save exit-status up chain */
+}
+
+static PLAN *
+palloc(enum ntype t, int (*f)(PLAN *, FTSENT *))
+{
+ PLAN *new;
+
+ if ((new = malloc(sizeof(PLAN))) == NULL)
+ err(1, NULL);
+ memset(new, 0, sizeof(PLAN));
+ new->type = t;
+ new->eval = f;
+ return (new);
+}
diff --git a/usr.bin/find/ls.c b/usr.bin/find/ls.c
new file mode 100644
index 0000000..23b7cfe
--- /dev/null
+++ b/usr.bin/find/ls.c
@@ -0,0 +1,124 @@
+/* $NetBSD: ls.c,v 1.21 2011/08/31 16:24:57 plunky Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)ls.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: ls.c,v 1.21 2011/08/31 16:24:57 plunky Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <tzfile.h>
+#include <unistd.h>
+
+#include "find.h"
+
+/* Derived from the print routines in the ls(1) source code. */
+
+static void printlink(char *);
+static void printtime(time_t);
+
+void
+printlong(char *name, /* filename to print */
+ char *accpath, /* current valid path to filename */
+ struct stat *sb) /* stat buffer */
+{
+ char modep[15];
+
+ (void)printf("%7lu %6lld ", (u_long)sb->st_ino,
+ (long long)sb->st_blocks);
+ (void)strmode(sb->st_mode, modep);
+ (void)printf("%s %3lu %-*s %-*s ", modep, (unsigned long)sb->st_nlink,
+ LOGIN_NAME_MAX, user_from_uid(sb->st_uid, 0), LOGIN_NAME_MAX,
+ group_from_gid(sb->st_gid, 0));
+
+ if (S_ISCHR(sb->st_mode) || S_ISBLK(sb->st_mode))
+ (void)printf("%3llu,%5llu ",
+ (unsigned long long)major(sb->st_rdev),
+ (unsigned long long)minor(sb->st_rdev));
+ else
+ (void)printf("%9lld ", (long long)sb->st_size);
+ printtime(sb->st_mtime);
+ (void)printf("%s", name);
+ if (S_ISLNK(sb->st_mode))
+ printlink(accpath);
+ (void)putchar('\n');
+}
+
+static void
+printtime(time_t ftime)
+{
+ int i;
+ char *longstring;
+
+ longstring = ctime(&ftime);
+ for (i = 4; i < 11; ++i)
+ (void)putchar(longstring[i]);
+
+#define SIXMONTHS ((DAYSPERNYEAR / 2) * SECSPERDAY)
+ if (ftime + SIXMONTHS > time(NULL))
+ for (i = 11; i < 16; ++i)
+ (void)putchar(longstring[i]);
+ else {
+ (void)putchar(' ');
+ for (i = 20; i < 24; ++i)
+ (void)putchar(longstring[i]);
+ }
+ (void)putchar(' ');
+}
+
+static void
+printlink(char *name)
+{
+ int lnklen;
+ char path[MAXPATHLEN + 1];
+
+ if ((lnklen = readlink(name, path, sizeof(path) - 1)) == -1) {
+ warn("%s", name);
+ return;
+ }
+ path[lnklen] = '\0';
+ (void)printf(" -> %s", path);
+}
diff --git a/usr.bin/find/main.c b/usr.bin/find/main.c
new file mode 100644
index 0000000..5aab177
--- /dev/null
+++ b/usr.bin/find/main.c
@@ -0,0 +1,164 @@
+/* $NetBSD: main.c,v 1.31 2013/01/24 17:50:08 christos Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Cimarron D. Taylor of the University of California, Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)main.c 8.4 (Berkeley) 5/4/95";
+#else
+__COPYRIGHT("@(#) Copyright (c) 1990, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+__RCSID("$NetBSD: main.c,v 1.31 2013/01/24 17:50:08 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "find.h"
+
+time_t now; /* time find was run */
+int dotfd; /* starting directory */
+int ftsoptions; /* options for the ftsopen(3) call */
+int isdeprecated; /* using deprecated syntax */
+int isdepth; /* do directories on post-order visit */
+int isoutput; /* user specified output operator */
+int issort; /* sort directory entries */
+int isxargs; /* don't permit xargs delimiting chars */
+int regcomp_flags = REG_BASIC; /* regex compilation flags */
+
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ char **p, **start;
+ int ch;
+
+ (void)time(&now); /* initialize the time-of-day */
+ (void)setlocale(LC_ALL, "");
+
+ /* array to hold dir list. at most (argc - 1) elements. */
+ p = start = malloc(argc * sizeof (char *));
+ if (p == NULL)
+ err(1, NULL);
+
+ ftsoptions = FTS_NOSTAT | FTS_PHYSICAL;
+ while ((ch = getopt(argc, argv, "HLPdEf:hsXx")) != -1)
+ switch (ch) {
+ case 'H':
+ ftsoptions &= ~FTS_LOGICAL;
+ ftsoptions |= FTS_PHYSICAL|FTS_COMFOLLOW;
+ break;
+ case 'L':
+ ftsoptions &= ~(FTS_COMFOLLOW|FTS_PHYSICAL);
+ ftsoptions |= FTS_LOGICAL;
+ break;
+ case 'P':
+ ftsoptions &= ~(FTS_COMFOLLOW|FTS_LOGICAL);
+ ftsoptions |= FTS_PHYSICAL;
+ break;
+ case 'd':
+ isdepth = 1;
+ break;
+ case 'E':
+ regcomp_flags = REG_EXTENDED;
+ break;
+ case 'f':
+ *p++ = optarg;
+ break;
+ case 'h':
+ ftsoptions &= ~FTS_PHYSICAL;
+ ftsoptions |= FTS_LOGICAL;
+ break;
+ case 's':
+ issort = 1;
+ break;
+ case 'X':
+ isxargs = 1;
+ break;
+ case 'x':
+ ftsoptions |= FTS_XDEV;
+ break;
+ case '?':
+ default:
+ break;
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /*
+ * Find first option to delimit the file list. The first argument
+ * that starts with a -, or is a ! or a ( must be interpreted as a
+ * part of the find expression, according to POSIX .2.
+ */
+ for (; *argv != NULL; *p++ = *argv++) {
+ if (argv[0][0] == '-')
+ break;
+ if ((argv[0][0] == '!' || argv[0][0] == '(') &&
+ argv[0][1] == '\0')
+ break;
+ }
+
+ if (p == start)
+ usage();
+
+ *p = NULL;
+
+ if ((dotfd = open(".", O_RDONLY | O_CLOEXEC, 0)) == -1)
+ err(1, ".");
+
+ exit(find_execute(find_formplan(argv), start));
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "Usage: %s [-H | -L | -P] [-dEhsXx] [-f file] "
+ "file [file ...] [expression]\n", getprogname());
+ exit(1);
+}
diff --git a/usr.bin/find/misc.c b/usr.bin/find/misc.c
new file mode 100644
index 0000000..8ba84c0
--- /dev/null
+++ b/usr.bin/find/misc.c
@@ -0,0 +1,151 @@
+/* $NetBSD: misc.c,v 1.14 2006/10/11 19:51:10 apb Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Cimarron D. Taylor of the University of California, Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)misc.c 8.2 (Berkeley) 4/1/94";
+#else
+__RCSID("$NetBSD: misc.c,v 1.14 2006/10/11 19:51:10 apb Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "find.h"
+
+/*
+ * brace_subst --
+ * Replace occurrences of {} in orig with path, and place it in a malloced
+ * area of memory set in store.
+ */
+void
+brace_subst(char *orig, char **store, char *path, int *len)
+{
+ int nlen, plen, rest;
+ char ch, *p, *ostore;
+
+ plen = strlen(path);
+ for (p = *store; (ch = *orig) != '\0'; ++orig)
+ if (ch == '{' && orig[1] == '}') {
+ /* Length of string after the {}. */
+ rest = strlen(&orig[2]);
+
+ nlen = *len;
+ while ((p - *store) + plen + rest + 1 > nlen)
+ nlen *= 2;
+
+ if (nlen > *len) {
+ ostore = *store;
+ if ((*store = realloc(ostore, nlen)) == NULL)
+ err(1, "realloc");
+ *len = nlen;
+ p += *store - ostore; /* Relocate. */
+ }
+ memmove(p, path, plen);
+ p += plen;
+ ++orig;
+ } else
+ *p++ = ch;
+ *p = '\0';
+}
+
+/*
+ * queryuser --
+ * print a message to standard error and then read input from standard
+ * input. If the input is 'y' then 1 is returned.
+ */
+int
+queryuser(char **argv)
+{
+ int ch, first, nl;
+
+ (void)fprintf(stderr, "\"%s", *argv);
+ while (*++argv)
+ (void)fprintf(stderr, " %s", *argv);
+ (void)fprintf(stderr, "\"? ");
+ (void)fflush(stderr);
+
+ first = ch = getchar();
+ for (nl = 0;;) {
+ if (ch == '\n') {
+ nl = 1;
+ break;
+ }
+ if (ch == EOF)
+ break;
+ ch = getchar();
+ }
+
+ if (!nl) {
+ (void)fprintf(stderr, "\n");
+ (void)fflush(stderr);
+ }
+ return (first == 'y');
+}
+
+/*
+ * show_path --
+ * called on SIGINFO
+ */
+/* ARGSUSED */
+void
+show_path(int sig)
+{
+ extern FTSENT *g_entry;
+ int errno_bak;
+
+ if (g_entry == NULL) {
+ /*
+ * not initialized yet.
+ * assumption: pointer assignment is atomic.
+ */
+ return;
+ }
+
+ errno_bak = errno;
+ write(STDERR_FILENO, "find path: ", 11);
+ write(STDERR_FILENO, g_entry->fts_path, g_entry->fts_pathlen);
+ write(STDERR_FILENO, "\n", 1);
+ errno = errno_bak;
+}
diff --git a/usr.bin/find/operator.c b/usr.bin/find/operator.c
new file mode 100644
index 0000000..63a6aa4
--- /dev/null
+++ b/usr.bin/find/operator.c
@@ -0,0 +1,272 @@
+/* $NetBSD: operator.c,v 1.10 2014/10/18 08:33:30 snj Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Cimarron D. Taylor of the University of California, Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)operator.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: operator.c,v 1.10 2014/10/18 08:33:30 snj Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <fts.h>
+#include <stdio.h>
+
+#include "find.h"
+
+static PLAN *yanknode(PLAN **);
+static PLAN *yankexpr(PLAN **);
+
+/*
+ * yanknode --
+ * destructively removes the top from the plan
+ */
+static PLAN *
+yanknode(PLAN **planp) /* pointer to top of plan (modified) */
+{
+ PLAN *node; /* top node removed from the plan */
+
+ if ((node = (*planp)) == NULL)
+ return (NULL);
+ (*planp) = (*planp)->next;
+ node->next = NULL;
+ return (node);
+}
+
+/*
+ * yankexpr --
+ * Removes one expression from the plan. This is used mainly by
+ * paren_squish. In comments below, an expression is either a
+ * simple node or a N_EXPR node containing a list of simple nodes.
+ */
+static PLAN *
+yankexpr(PLAN **planp) /* pointer to top of plan (modified) */
+{
+ PLAN *next; /* temp node holding subexpression results */
+ PLAN *node; /* pointer to returned node or expression */
+ PLAN *tail; /* pointer to tail of subplan */
+ PLAN *subplan; /* pointer to head of ( ) expression */
+
+ /* first pull the top node from the plan */
+ if ((node = yanknode(planp)) == NULL)
+ return (NULL);
+
+ /*
+ * If the node is an '(' then we recursively slurp up expressions
+ * until we find its associated ')'. If it's a closing paren we
+ * just return it and unwind our recursion; all other nodes are
+ * complete expressions, so just return them.
+ */
+ if (node->type == N_OPENPAREN)
+ for (tail = subplan = NULL;;) {
+ if ((next = yankexpr(planp)) == NULL)
+ err(1, "(: missing closing ')'");
+ /*
+ * If we find a closing ')' we store the collected
+ * subplan in our '(' node and convert the node to
+ * a N_EXPR. The ')' we found is ignored. Otherwise,
+ * we just continue to add whatever we get to our
+ * subplan.
+ */
+ if (next->type == N_CLOSEPAREN) {
+ if (subplan == NULL)
+ errx(1, "(): empty inner expression");
+ node->p_data[0] = subplan;
+ node->type = N_EXPR;
+ node->eval = f_expr;
+ break;
+ } else {
+ if (subplan == NULL)
+ tail = subplan = next;
+ else {
+ tail->next = next;
+ tail = next;
+ }
+ tail->next = NULL;
+ }
+ }
+ return (node);
+}
+
+/*
+ * paren_squish --
+ * replaces "parentheisized" plans in our search plan with "expr" nodes.
+ */
+PLAN *
+paren_squish(PLAN *plan) /* plan with ( ) nodes */
+{
+ PLAN *expr; /* pointer to next expression */
+ PLAN *tail; /* pointer to tail of result plan */
+ PLAN *result; /* pointer to head of result plan */
+
+ result = tail = NULL;
+
+ /*
+ * the basic idea is to have yankexpr do all our work and just
+ * collect its results together.
+ */
+ while ((expr = yankexpr(&plan)) != NULL) {
+ /*
+ * if we find an unclaimed ')' it means there is a missing
+ * '(' someplace.
+ */
+ if (expr->type == N_CLOSEPAREN)
+ errx(1, "): no beginning '('");
+
+ /* add the expression to our result plan */
+ if (result == NULL)
+ tail = result = expr;
+ else {
+ tail->next = expr;
+ tail = expr;
+ }
+ tail->next = NULL;
+ }
+ return (result);
+}
+
+/*
+ * not_squish --
+ * compresses "!" expressions in our search plan.
+ */
+PLAN *
+not_squish(PLAN *plan) /* plan to process */
+{
+ PLAN *next; /* next node being processed */
+ PLAN *node; /* temporary node used in N_NOT processing */
+ PLAN *tail; /* pointer to tail of result plan */
+ PLAN *result; /* pointer to head of result plan */
+
+ tail = result = next = NULL;
+
+ while ((next = yanknode(&plan)) != NULL) {
+ /*
+ * if we encounter a ( expression ) then look for nots in
+ * the expr subplan.
+ */
+ if (next->type == N_EXPR)
+ next->p_data[0] = not_squish(next->p_data[0]);
+
+ /*
+ * if we encounter a not, then snag the next node and place
+ * it in the not's subplan. As an optimization we compress
+ * several not's to zero or one not.
+ */
+ if (next->type == N_NOT) {
+ int notlevel = 1;
+
+ node = yanknode(&plan);
+ while (node != NULL && node->type == N_NOT) {
+ ++notlevel;
+ node = yanknode(&plan);
+ }
+ if (node == NULL)
+ errx(1, "!: no following expression");
+ if (node->type == N_OR)
+ errx(1, "!: nothing between ! and -o");
+ if (node->type == N_EXPR)
+ node = not_squish(node);
+ if (notlevel % 2 != 1)
+ next = node;
+ else
+ next->p_data[0] = node;
+ }
+
+ /* add the node to our result plan */
+ if (result == NULL)
+ tail = result = next;
+ else {
+ tail->next = next;
+ tail = next;
+ }
+ tail->next = NULL;
+ }
+ return (result);
+}
+
+/*
+ * or_squish --
+ * compresses -o expressions in our search plan.
+ */
+PLAN *
+or_squish(PLAN *plan) /* plan with ors to be squished */
+{
+ PLAN *next; /* next node being processed */
+ PLAN *tail; /* pointer to tail of result plan */
+ PLAN *result; /* pointer to head of result plan */
+
+ tail = result = next = NULL;
+
+ while ((next = yanknode(&plan)) != NULL) {
+ /*
+ * if we encounter a ( expression ) then look for or's in
+ * the expr subplan.
+ */
+ if (next->type == N_EXPR)
+ next->p_data[0] = or_squish(next->p_data[0]);
+
+ /* if we encounter a not then look for not's in the subplan */
+ if (next->type == N_NOT)
+ next->p_data[0] = or_squish(next->p_data[0]);
+
+ /*
+ * if we encounter an or, then place our collected plan in the
+ * or's first subplan and then recursively collect the
+ * remaining stuff into the second subplan and return the or.
+ */
+ if (next->type == N_OR) {
+ if (result == NULL)
+ errx(1, "-o: no expression before -o");
+ next->p_data[0] = result;
+ next->p_data[1] = or_squish(plan);
+ if (next->p_data[1] == NULL)
+ errx(1, "-o: no expression after -o");
+ return (next);
+ }
+
+ /* add the node to our result plan */
+ if (result == NULL)
+ tail = result = next;
+ else {
+ tail->next = next;
+ tail = next;
+ }
+ tail->next = NULL;
+ }
+ return (result);
+}
diff --git a/usr.bin/find/option.c b/usr.bin/find/option.c
new file mode 100644
index 0000000..0af4447
--- /dev/null
+++ b/usr.bin/find/option.c
@@ -0,0 +1,194 @@
+/* $NetBSD: option.c,v 1.27 2016/06/13 00:04:40 pgoyette Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Cimarron D. Taylor of the University of California, Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)option.c 8.2 (Berkeley) 4/16/94";
+#else
+__RCSID("$NetBSD: option.c,v 1.27 2016/06/13 00:04:40 pgoyette Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <fts.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "find.h"
+
+int typecompare(const void *, const void *);
+static OPTION *option(char *);
+
+/* NB: the following table must be sorted lexically. */
+static OPTION const options[] = {
+ { "!", N_NOT, c_not, 0 },
+ { "(", N_OPENPAREN, c_openparen, 0 },
+ { ")", N_CLOSEPAREN, c_closeparen, 0 },
+ { "-a", N_AND, c_null, 0 },
+ { "-amin", N_AMIN, c_amin, 1 },
+ { "-and", N_AND, c_null, 0 },
+ { "-anewer", N_ANEWER, c_anewer, 1 },
+ { "-asince", N_ASINCE, c_asince, 1 },
+ { "-atime", N_ATIME, c_atime, 1 },
+ { "-cmin", N_CMIN, c_cmin, 1 },
+ { "-cnewer", N_CNEWER, c_cnewer, 1 },
+ { "-csince", N_CSINCE, c_csince, 1 },
+ { "-ctime", N_CTIME, c_ctime, 1 },
+ { "-delete", N_DELETE, c_delete, 0 },
+ { "-depth", N_DEPTH, c_depth, 0 },
+ { "-empty", N_EMPTY, c_empty, 0 },
+ { "-exec", N_EXEC, c_exec, 1 },
+ { "-execdir", N_EXECDIR, c_execdir, 1 },
+ { "-exit", N_EXIT, c_exit, 0 },
+ { "-false", N_FALSE, c_false, 0 },
+ { "-flags", N_FLAGS, c_flags, 1 },
+ { "-follow", N_FOLLOW, c_follow, 0 },
+ { "-fprint", N_FPRINT, c_fprint, 1 },
+ { "-fstype", N_FSTYPE, c_fstype, 1 },
+ { "-group", N_GROUP, c_group, 1 },
+ { "-iname", N_INAME, c_iname, 1 },
+ { "-inum", N_INUM, c_inum, 1 },
+ { "-iregex", N_IREGEX, c_iregex, 1 },
+ { "-links", N_LINKS, c_links, 1 },
+ { "-ls", N_LS, c_ls, 0 },
+ { "-maxdepth", N_MAXDEPTH, c_maxdepth, 1 },
+ { "-mindepth", N_MINDEPTH, c_mindepth, 1 },
+ { "-mmin", N_MMIN, c_mmin, 1 },
+ { "-mtime", N_MTIME, c_mtime, 1 },
+ { "-name", N_NAME, c_name, 1 },
+ { "-newer", N_NEWER, c_newer, 1 },
+
+/* Aliases for compatability with Gnu findutils */
+ { "-neweraa", N_ANEWER, c_anewer, 1 },
+ { "-newerat", N_ASINCE, c_asince, 1 },
+ { "-newercc", N_CNEWER, c_cnewer, 1 },
+ { "-newerct", N_CSINCE, c_csince, 1 },
+ { "-newermm", N_NEWER, c_newer, 1 },
+ { "-newermt", N_SINCE, c_since, 1 },
+
+/*
+ * Unimplemented Gnu findutils options
+ *
+ * If you implement any of these, be sure to re-sort the table
+ * in ascii(7) order!
+ *
+ { "-newerBB", N_UNIMPL, c_unimpl, 1 },
+ { "-newerBa", N_UNIMPL, c_unimpl, 1 },
+ { "-newerBc", N_UNIMPL, c_unimpl, 1 },
+ { "-newerBm", N_UNIMPL, c_unimpl, 1 },
+ { "-newerBt", N_UNIMPL, c_unimpl, 1 },
+ { "-neweraB", N_UNIMPL, c_unimpl, 1 },
+ { "-newerac", N_UNIMPL, c_unimpl, 1 },
+ { "-neweram", N_UNIMPL, c_unimpl, 1 },
+ { "-newerca", N_UNIMPL, c_unimpl, 1 },
+ { "-newercm", N_UNIMPL, c_unimpl, 1 },
+ { "-newercB", N_UNIMPL, c_unimpl, 1 },
+ { "-newermB", N_UNIMPL, c_unimpl, 1 },
+ { "-newerma", N_UNIMPL, c_unimpl, 1 },
+ { "-newermc", N_UNIMPL, c_unimpl, 1 },
+ *
+ */
+
+ { "-nogroup", N_NOGROUP, c_nogroup, 0 },
+ { "-nouser", N_NOUSER, c_nouser, 0 },
+ { "-o", N_OR, c_or, 0 },
+ { "-ok", N_OK, c_exec, 1 },
+ { "-or", N_OR, c_or, 0 },
+ { "-path", N_PATH, c_path, 1 },
+ { "-perm", N_PERM, c_perm, 1 },
+ { "-print", N_PRINT, c_print, 0 },
+ { "-print0", N_PRINT0, c_print0, 0 },
+ { "-printx", N_PRINTX, c_printx, 0 },
+ { "-prune", N_PRUNE, c_prune, 0 },
+ { "-regex", N_REGEX, c_regex, 1 },
+ { "-rm", N_DELETE, c_delete, 0 },
+ { "-since", N_SINCE, c_since, 1 },
+ { "-size", N_SIZE, c_size, 1 },
+ { "-type", N_TYPE, c_type, 1 },
+ { "-user", N_USER, c_user, 1 },
+ { "-xdev", N_XDEV, c_xdev, 0 }
+};
+
+/*
+ * find_create --
+ * create a node corresponding to a command line argument.
+ *
+ * TODO:
+ * add create/process function pointers to node, so we can skip
+ * this switch stuff.
+ */
+PLAN *
+find_create(char ***argvp)
+{
+ OPTION *p;
+ PLAN *new;
+ char **argv;
+ char *opt;
+
+ argv = *argvp;
+ opt = *argv;
+
+ if ((p = option(opt)) == NULL)
+ errx(1, "%s: unknown option", opt);
+ ++argv;
+ if (p->arg && !*argv)
+ errx(1, "%s: requires additional arguments", opt);
+
+ new = (p->create)(&argv, p->token == N_OK, opt);
+
+ *argvp = argv;
+ return (new);
+}
+
+static OPTION *
+option(char *name)
+{
+ OPTION tmp;
+
+ tmp.name = name;
+ return ((OPTION *)bsearch(&tmp, options,
+ sizeof(options)/sizeof(OPTION), sizeof(OPTION), typecompare));
+}
+
+int
+typecompare(const void *a, const void *b)
+{
+
+ return (strcmp(((const OPTION *)a)->name, ((const OPTION *)b)->name));
+}
diff --git a/usr.bin/fold/fold.1 b/usr.bin/fold/fold.1
new file mode 100644
index 0000000..8f3f6a1
--- /dev/null
+++ b/usr.bin/fold/fold.1
@@ -0,0 +1,93 @@
+.\" $NetBSD: fold.1,v 1.17 2012/05/12 15:17:15 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)fold.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd May 12, 2012
+.Dt FOLD 1
+.Os
+.Sh NAME
+.Nm fold
+.Nd "fold long lines for finite width output device"
+.Sh SYNOPSIS
+.Nm
+.Op Fl bs
+.Op Fl w Ar width
+.Op Ar
+.Sh DESCRIPTION
+.Nm
+is a filter which folds the contents of the specified files,
+or the standard input if no files are specified,
+breaking the lines to have a maximum of 80 characters.
+.Pp
+The options are as follows:
+.Bl -tag -width XwXwidthXX
+.It Fl b
+Count
+.Ar width
+in bytes rather than column positions.
+.It Fl s
+Fold line after the last blank character within the first
+.Ar width
+column positions (or bytes).
+If a blank character does not exist within the width, then
+a longer line will still be split at the width.
+.It Fl w Ar width
+Specifies
+.Ar width
+to use as a line width, instead of the default 80 characters.
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width indent
+.It Ev LC_CTYPE
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr expand 1 ,
+.Xr fmt 1
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.1-2008 .
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 1 .
+.Sh BUGS
+If underlining is present it may be messed up by folding.
+.Pp
+.Ar Width
+should be a multiple of 8 if tabs are present, or the tabs should
+be expanded using
+.Xr expand 1
+before using
+.Nm .
diff --git a/usr.bin/fold/fold.c b/usr.bin/fold/fold.c
new file mode 100644
index 0000000..52ecc3d
--- /dev/null
+++ b/usr.bin/fold/fold.c
@@ -0,0 +1,248 @@
+/* $NetBSD: fold.c,v 1.17 2011/09/04 20:24:59 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kevin Ruddy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1990, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)fold.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: fold.c,v 1.17 2011/09/04 20:24:59 joerg Exp $");
+#endif /* not lint */
+
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <err.h>
+
+#define DEFLINEWIDTH 80
+
+static void fold(int);
+static int new_column_position(int, wint_t);
+__dead static void usage(void);
+
+static int count_bytes = 0;
+static int split_words = 0;
+
+int
+main(int argc, char **argv)
+{
+ int ch;
+ int width;
+ char *p;
+
+ setlocale(LC_CTYPE, "");
+ setprogname(argv[0]);
+
+ width = -1;
+ while ((ch = getopt(argc, argv, "0123456789bsw:")) != -1)
+ switch (ch) {
+ case 'b':
+ count_bytes = 1;
+ break;
+ case 's':
+ split_words = 1;
+ break;
+ case 'w':
+ if ((width = atoi(optarg)) <= 0)
+ errx(1, "illegal width value");
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if (width == -1) {
+ p = argv[optind - 1];
+ if (p[0] == '-' && p[1] == ch && !p[2])
+ width = atoi(++p);
+ else
+ width = atoi(argv[optind] + 1);
+ }
+ break;
+ default:
+ usage();
+ }
+ argv += optind;
+ argc -= optind;
+
+ if (width == -1)
+ width = DEFLINEWIDTH;
+
+ if (!*argv)
+ fold(width);
+ else for (; *argv; ++argv)
+ if (!freopen(*argv, "r", stdin)) {
+ err (1, "%s", *argv);
+ /* NOTREACHED */
+ } else
+ fold(width);
+ exit(0);
+}
+
+/*
+ * Fold the contents of standard input to fit within WIDTH columns
+ * (or bytes) and write to standard output.
+ *
+ * If split_words is set, split the line at the last space character
+ * on the line. This flag necessitates storing the line in a buffer
+ * until the current column > width, or a newline or EOF is read.
+ *
+ * The buffer can grow larger than WIDTH due to backspaces and carriage
+ * returns embedded in the input stream.
+ */
+static void
+fold(int width)
+{
+ static wchar_t *buf = NULL;
+ wchar_t *nbuf;
+ static int buf_max = 0;
+ wint_t ch;
+ int col, indx, i;
+
+ col = indx = 0;
+ while ((ch = getwchar()) != WEOF) {
+ if (ch == L'\n') {
+ if (indx != 0) {
+ for (i = 0; i < indx; i++)
+ putwchar(buf[i]);
+ }
+ putwchar(L'\n');
+ col = indx = 0;
+ continue;
+ }
+
+ col = new_column_position (col, ch);
+ if (col > width) {
+ int last_space;
+
+#ifdef __GNUC__
+ last_space = 0; /* XXX gcc */
+#endif
+ if (split_words) {
+ for (i = 0, last_space = -1; i < indx; i++)
+ if (buf[i] == L' ')
+ last_space = i;
+ }
+
+ if (split_words && last_space != -1) {
+ for (i = 0; i < last_space; i++)
+ putwchar(buf[i]);
+
+ /* increase last_space here, so we skip trailing whitespace */
+ last_space++;
+ wmemmove (buf, buf+last_space, indx-last_space);
+
+ indx -= last_space;
+ col = 0;
+ for (i = 0; i < indx; i++) {
+ col = new_column_position (col, buf[i]);
+ }
+ } else {
+ for (i = 0; i < indx; i++)
+ putwchar(buf[i]);
+ col = indx = 0;
+ }
+ putwchar('\n');
+
+ /* calculate the column position for the next line. */
+ col = new_column_position (col, ch);
+ }
+
+ if (indx + 1 > buf_max) {
+ /* Allocate buffer in LINE_MAX increments */
+ if ((nbuf = realloc (buf, buf_max + 2048)) == NULL) {
+ err (1, "realloc");
+ /* NOTREACHED */
+ }
+ buf = nbuf;
+ buf_max += 2048;
+ }
+ buf[indx++] = ch;
+ }
+
+ if (indx != 0) {
+ for (i = 0; i < indx; i++)
+ putwchar(buf[i]);
+ }
+}
+
+/*
+ * calculate the column position
+ */
+static int
+new_column_position (int col, wint_t ch)
+{
+ int w;
+
+ if (!count_bytes) {
+ switch (ch) {
+ case L'\b':
+ if (col > 0)
+ --col;
+ break;
+ case L'\r':
+ col = 0;
+ break;
+ case L'\t':
+ col = (col + 8) & ~7;
+ break;
+ default:
+ w = wcwidth(ch);
+ if (w > 0)
+ col += w;
+ break;
+ }
+ } else {
+ char dummy[MB_LEN_MAX];
+
+ /* XXX: we assume stateless encoding */
+ col += wcrtomb(dummy, ch, NULL);
+ }
+
+ return col;
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: %s [-bs] [-w width] [file ...]\n", getprogname());
+ exit(1);
+}
+
diff --git a/usr.bin/gencat/gencat.1 b/usr.bin/gencat/gencat.1
new file mode 100644
index 0000000..6fcc171
--- /dev/null
+++ b/usr.bin/gencat/gencat.1
@@ -0,0 +1,233 @@
+.\" $NetBSD: gencat.1,v 1.14 2018/07/28 08:03:41 wiz Exp $
+.\"
+.\" Copyright (c) 2007 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Kee Hinckley and Brian Ginsbach.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" Written by Kee Hinckley <nazgul@somewhere.com>
+.\"
+.Dd December 29, 2011
+.Dt GENCAT 1
+.Os
+.Sh NAME
+.Nm gencat
+.Nd generates a Native Language Support (NLS) message catalog file
+.Sh SYNOPSIS
+.Nm
+.Ar catfile
+.Op Ar msgfile|- ...
+.Sh DESCRIPTION
+The
+.Nm
+utility generates a formatted message catalog
+.Ar catfile
+from stdin or one or more message source text files
+.Ar msgfile .
+The file
+.Ar catfile
+is created if it does not already exist.
+If
+.Ar catfile
+does exist, its messages are included in the new
+.Ar catfile .
+The new message text defined in
+.Ar msgfile
+replaces the old message text currently in
+.Ar catfile
+when the set and message numbers match.
+.Pp
+The generated message catalog contains message
+strings that will be retrieved using the
+.Xr catgets 3
+library call.
+These messages are dynamically loaded by the
+Native Language Support (NLS) library at run time.
+Error messages are grouped into sets, and a program can load a
+particular set depending on which type, or language, of messages
+is desired.
+.Ss Message Text Source File Format
+The message text source files are text files in the format described below.
+Note that the fields of a message text source line are separated by
+space or tab characters.
+.\" XXX Required by POSIX; the code must be fixed first. Above line should be
+.\" a single space or tab character;
+.\" any other space or tab characters are considered to be part of the
+.\" field contents.
+.Bl -tag -width 3n
+.It Li $set Ar n comment
+Determines the set identifier to be used for all subsequent messages
+until the next
+.Li $set
+or end-of-file.
+The
+.Ar n
+is the set identifier which is defined as a number in the range
+.Bo 1 ,
+.Dv NL_SETMAX Bc .
+Set identifiers within a single source file need not be contiguous.
+Any string following the set identifier is treated as a comment.
+If no
+.Li $set
+directive is specified in a message text source file,
+all messages will be located in the default message set
+.Dv NL_SETD .
+.It Li $delset Ar n comment
+Removes message set
+.Ar n
+from the catalog.
+The
+.Ar n
+is a set identifier in the range
+.Bo 1 ,
+.Dv NL_SETMAX Bc .
+If a message set was created earlier in the
+current file, or in a file previously read by the
+.Nm
+command, this directive will remove it.
+Any string following the set identifier is treated as a comment.
+.It Li $ Ar comment
+A line beginning with
+.Li $
+followed by a space or tab character is treated as a comment.
+.It Ar m message-text
+A message line consists of a message identifier
+.Ar m
+in the range
+.Bo 1 ,
+.Dv NL_MSGMAX Bc
+and the
+.Ar message-text .
+The
+.Ar message-text
+is read until the end of the line or a quote character
+(if one is specified).
+The
+.Ar message-text
+is stored in the message catalog with
+the set identifier specified by the last
+.Li $set
+directive, and the message identifier
+.Ar m .
+If the
+.Ar message-text
+is empty and there is a space or tab character
+following the message identifier,
+an empty string is stored in the message catalog.
+If no
+.Ar message-text
+is provided,
+and if there is no space or tab character following the message
+identifier,
+the message with the message identifier
+.Ar m
+in the current set is removed from the catalog.
+Message identifiers need not be contiguous within a single set.
+The length of
+.Ar message-text
+must be in the range
+.Bo 0 ,
+.Dv NL_TEXTMAX Bc .
+.It Li $quote Ar c
+Sets an optional quote character to be used around the
+.Ar message-text .
+The quote character
+.Ar c
+may be any character other than white space.
+If this is specified, then messages must begin and end with the
+quote character.
+.\" XXX Remove next sentence when code is fixed for POSIX conformance.
+This is useful when messages must contain leading white space.
+.\" XXX Replacement when above is removed.
+.\" This is useful to make leading and trailing spaces or empty
+.\" messages visible.
+By default no quote character is used.
+If an empty
+.Li $quote
+directive is specified, then the current quote character is unset.
+.El
+.Pp
+Empty lines
+.\" XXX Remove next line when the code is fixed for POSIX conformance.
+and leading blanks
+in a message text source file are ignored.
+Any line beginning with any character other than those
+described above is ignored as a syntax error.
+.Pp
+Text message strings may contain any characters and
+the following special characters and escape sequences.
+.Bl -column -offset indent ".Sy carriage return" ".Sy Symbol" ".Sy Sequence"
+.It Sy Description Ta Sy Symbol Ta Sy Sequence
+.It newline Ta NL(LF) Ta Li \en
+.It horizontal tab Ta HT Ta Li \et
+.It vertical tab Ta VT Ta Li \ev
+.It backspace Ta BS Ta Li \eb
+.It carriage return Ta CR Ta Li \er
+.It form feed Ta FF Ta Li \ef
+.It backslash Ta \e Ta Li \e\e
+.It bit pattern Ta ddd Ta Li \eddd
+.El
+.Pp
+A bit pattern,
+.Li \eddd ,
+consists of a backslash followed by
+one, two, or three octal digits representing the value of the character.
+The current quote character, if defined, may be escaped with a backslash
+to generate the quote character.
+Any character following the backslash ('\e') other than those specified
+is ignored.
+.Pp
+A backslash at the end of the line continues the message onto the next line.
+The following two lines are an example of such a message:
+.Pp
+.Dl 1 This message continues \e
+.D1 on the next line
+.Pp
+Producing the following message:
+.Pp
+.Dl 1 This message continues on the next line
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr catclose 3 ,
+.Xr catgets 3 ,
+.Xr catopen 3 ,
+.Xr nls 7
+.\" XXX Close but not quite; add when code is fixed.
+.\".Sh STANDARDS
+.\"The
+.\".Nm
+.\"utility is compliant with the
+.\".St -p1003.1-2004
+.\"standard.
+.Sh AUTHORS
+.An -nosplit
+The Native Language Support (NLS) message catalog facility was
+contributed by
+.An J.T. Conklin
+.Aq Mt jtc@NetBSD.org .
+This page was originally written by
+.An Kee Hinckley
+.Aq Mt nazgul@somewhere.com .
diff --git a/usr.bin/gencat/gencat.c b/usr.bin/gencat/gencat.c
new file mode 100644
index 0000000..444ce2d
--- /dev/null
+++ b/usr.bin/gencat/gencat.c
@@ -0,0 +1,879 @@
+/* $NetBSD: gencat.c,v 1.36 2013/11/27 17:38:11 apb Exp $ */
+
+/*
+ * Copyright (c) 1996 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by J.T. Conklin.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if defined(__RCSID) && !defined(lint)
+__RCSID("$NetBSD: gencat.c,v 1.36 2013/11/27 17:38:11 apb Exp $");
+#endif
+
+/***********************************************************
+Copyright 1990, by Alfalfa Software Incorporated, Cambridge, Massachusetts.
+
+ All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that Alfalfa's name not be used in
+advertising or publicity pertaining to distribution of the software
+without specific, written prior permission.
+
+ALPHALPHA DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
+ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
+ALPHALPHA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
+ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+SOFTWARE.
+
+If you make any modifications, bugfixes or other changes to this software
+we'd appreciate it if you could send a copy to us so we can keep things
+up-to-date. Many thanks.
+ Kee Hinckley
+ Alfalfa Software, Inc.
+ 267 Allston St., #3
+ Cambridge, MA 02139 USA
+ nazgul@alfalfa.com
+
+******************************************************************/
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#define _NLS_PRIVATE
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <netinet/in.h> /* Needed by arpa/inet.h on NetBSD */
+#include <arpa/inet.h> /* Needed for htonl() on POSIX systems */
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <nl_types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifndef NL_SETMAX
+#define NL_SETMAX 255
+#endif
+#ifndef NL_MSGMAX
+#define NL_MSGMAX 2048
+#endif
+
+struct _msgT {
+ long msgId;
+ char *str;
+ LIST_ENTRY(_msgT) entries;
+};
+
+struct _setT {
+ long setId;
+ LIST_HEAD(msghead, _msgT) msghead;
+ LIST_ENTRY(_setT) entries;
+};
+
+static LIST_HEAD(sethead, _setT) sethead = LIST_HEAD_INITIALIZER(sethead);
+static struct _setT *curSet;
+
+static const char *curfile;
+static char *curline = NULL;
+static long lineno = 0;
+
+static char *cskip(char *);
+__dead static void error(const char *);
+static char *get_line(int);
+static char *getmsg(int, char *, char);
+static void warning(const char *, const char *);
+static char *wskip(char *);
+static char *xstrdup(const char *);
+static void *xmalloc(size_t);
+static void *xrealloc(void *, size_t);
+
+static void MCParse(int fd);
+static void MCReadCat(int fd);
+static void MCWriteCat(int fd);
+static void MCDelMsg(int msgId);
+static void MCAddMsg(int msgId, const char *msg);
+static void MCAddSet(int setId);
+static void MCDelSet(int setId);
+__dead static void usage(void);
+
+#define CORRUPT "corrupt message catalog"
+#define NOMEMORY "out of memory"
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s catfile [msgfile|- ...]\n", getprogname());
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ofd, ifd;
+ char *catfile = NULL;
+ int c;
+ int updatecat = 0;
+
+ while ((c = getopt(argc, argv, "")) != -1) {
+ switch (c) {
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ usage();
+ /* NOTREACHED */
+ }
+ catfile = *argv++;
+
+ if ((catfile[0] == '-') && (catfile[1] == '\0')) {
+ ofd = STDOUT_FILENO;
+ } else {
+ ofd = open(catfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
+ if (ofd < 0) {
+ if (errno == EEXIST) {
+ if ((ofd = open(catfile, O_RDWR)) < 0) {
+ err(1, "Unable to open %s", catfile);
+ /* NOTREACHED */
+ }
+ } else {
+ err(1, "Unable to create new %s", catfile);
+ /* NOTREACHED */
+ }
+ curfile = catfile;
+ updatecat = 1;
+ MCReadCat(ofd);
+ if (lseek(ofd, (off_t)0, SEEK_SET) == (off_t)-1) {
+ err(1, "Unable to seek on %s", catfile);
+ /* NOTREACHED */
+ }
+ }
+ }
+
+ if (argc < 2 || (((*argv)[0] == '-') && ((*argv)[1] == '\0'))) {
+ if (argc > 2)
+ usage();
+ /* NOTREACHED */
+ MCParse(STDIN_FILENO);
+ } else {
+ for (; *argv; argv++) {
+ if ((ifd = open(*argv, O_RDONLY)) < 0)
+ err(1, "Unable to read %s", *argv);
+ curfile = *argv;
+ lineno = 0;
+ MCParse(ifd);
+ close(ifd);
+ }
+ }
+
+ if (updatecat) {
+ if (ftruncate(ofd, 0) != 0) {
+ err(1, "Unable to truncate %s", catfile);
+ /* NOTREACHED */
+ }
+ }
+
+ MCWriteCat(ofd);
+ exit(0);
+}
+
+static void
+warning(const char *cptr, const char *msg)
+{
+ if (lineno) {
+ fprintf(stderr, "%s: %s on line %ld, %s\n",
+ getprogname(), msg, lineno, curfile);
+ fprintf(stderr, "%s\n", curline);
+ if (cptr) {
+ char *tptr;
+ for (tptr = curline; tptr < cptr; ++tptr)
+ putc(' ', stderr);
+ fprintf(stderr, "^\n");
+ }
+ } else {
+ fprintf(stderr, "%s: %s, %s\n", getprogname(), msg, curfile);
+ }
+}
+
+static void
+error(const char *msg)
+{
+ warning(NULL, msg);
+ exit(1);
+}
+
+static void *
+xmalloc(size_t len)
+{
+ void *p;
+
+ if ((p = malloc(len)) == NULL)
+ errx(1, NOMEMORY);
+ return (p);
+}
+
+static void *
+xrealloc(void *ptr, size_t size)
+{
+ if ((ptr = realloc(ptr, size)) == NULL)
+ errx(1, NOMEMORY);
+ return (ptr);
+}
+
+static char *
+xstrdup(const char *str)
+{
+ char *nstr;
+
+ if ((nstr = strdup(str)) == NULL)
+ errx(1, NOMEMORY);
+ return (nstr);
+}
+
+static char *
+get_line(int fd)
+{
+ static long curlen = BUFSIZ;
+ static char buf[BUFSIZ], *bptr = buf, *bend = buf;
+ char *cptr, *cend;
+ long buflen;
+
+ if (!curline) {
+ curline = xmalloc(curlen);
+ }
+ ++lineno;
+
+ cptr = curline;
+ cend = curline + curlen;
+ for (;;) {
+ for (; bptr < bend && cptr < cend; ++cptr, ++bptr) {
+ if (*bptr == '\n') {
+ *cptr = '\0';
+ ++bptr;
+ return (curline);
+ } else
+ *cptr = *bptr;
+ }
+ if (cptr == cend) {
+ cptr = curline = xrealloc(curline, curlen *= 2);
+ cend = curline + curlen;
+ }
+ if (bptr == bend) {
+ buflen = read(fd, buf, BUFSIZ);
+ if (buflen <= 0) {
+ if (cptr > curline) {
+ *cptr = '\0';
+ return (curline);
+ }
+ return (NULL);
+ }
+ bend = buf + buflen;
+ bptr = buf;
+ }
+ }
+}
+
+static char *
+wskip(char *cptr)
+{
+ if (!*cptr || !isspace((unsigned char) *cptr)) {
+ warning(cptr, "expected a space");
+ return (cptr);
+ }
+ while (*cptr && isspace((unsigned char) *cptr))
+ ++cptr;
+ return (cptr);
+}
+
+static char *
+cskip(char *cptr)
+{
+ if (!*cptr || isspace((unsigned char) *cptr)) {
+ warning(cptr, "wasn't expecting a space");
+ return (cptr);
+ }
+ while (*cptr && !isspace((unsigned char) *cptr))
+ ++cptr;
+ return (cptr);
+}
+
+static char *
+getmsg(int fd, char *cptr, char quote)
+{
+ static char *msg = NULL;
+ static size_t msglen = 0;
+ size_t clen, i;
+ int in_quote = 0;
+ char *tptr;
+
+ if (quote && *cptr == quote) {
+ ++cptr;
+ in_quote = 1;
+ }
+
+ clen = strlen(cptr) + 1;
+ if (clen > msglen) {
+ if (msglen)
+ msg = xrealloc(msg, clen);
+ else
+ msg = xmalloc(clen);
+ msglen = clen;
+ }
+ tptr = msg;
+
+ while (*cptr) {
+ if (quote && *cptr == quote) {
+ char *tmp;
+ tmp = cptr + 1;
+ if (!in_quote) {
+ /* XXX hard error? */
+ warning(cptr, "unexpected quote character, ignoring");
+ *tptr++ = *cptr++;
+ } else {
+ cptr++;
+ /* don't use wskip() */
+ while (*cptr && isspace((unsigned char) *cptr))
+#ifndef _BACKWARDS_COMPAT
+ cptr++;
+#else
+ *tptr++ = *cptr++;
+#endif
+ /* XXX hard error? */
+ if (*cptr)
+ warning(tmp, "unexpected extra characters, ignoring");
+ in_quote = 0;
+#ifndef _BACKWARDS_COMPAT
+ break;
+#endif
+ }
+ } else {
+ if (*cptr == '\\') {
+ ++cptr;
+ switch (*cptr) {
+ case '\0':
+ cptr = get_line(fd);
+ if (!cptr)
+ error("premature end of file");
+ msglen += strlen(cptr);
+ i = tptr - msg;
+ msg = xrealloc(msg, msglen);
+ tptr = msg + i;
+ break;
+ case 'n':
+ *tptr++ = '\n';
+ ++cptr;
+ break;
+ case 't':
+ *tptr++ = '\t';
+ ++cptr;
+ break;
+ case 'v':
+ *tptr++ = '\v';
+ ++cptr;
+ break;
+ case 'b':
+ *tptr++ = '\b';
+ ++cptr;
+ break;
+ case 'r':
+ *tptr++ = '\r';
+ ++cptr;
+ break;
+ case 'f':
+ *tptr++ = '\f';
+ ++cptr;
+ break;
+ case '\\':
+ *tptr++ = '\\';
+ ++cptr;
+ break;
+ default:
+ if (quote && *cptr == quote) {
+ *tptr++ = *cptr++;
+ } else if (isdigit((unsigned char) *cptr)) {
+ *tptr = 0;
+ for (i = 0; i < 3; ++i) {
+ if (!isdigit((unsigned char) *cptr))
+ break;
+ if (*cptr > '7')
+ warning(cptr, "octal number greater than 7?!");
+ *tptr *= 8;
+ *tptr += (*cptr - '0');
+ ++cptr;
+ }
+ } else {
+ warning(cptr, "unrecognized escape sequence");
+ }
+ break;
+ }
+ } else {
+ *tptr++ = *cptr++;
+ }
+ }
+ }
+
+ if (in_quote)
+ warning(cptr, "unterminated quoted message, ignoring");
+
+ *tptr = '\0';
+ return (msg);
+}
+
+static void
+MCParse(int fd)
+{
+ char *cptr, *str;
+ int msgid = 0;
+ int setid = 0;
+ char quote = 0;
+
+ while ((cptr = get_line(fd))) {
+ if (*cptr == '$') {
+ ++cptr;
+ if (strncmp(cptr, "set", 3) == 0) {
+ cptr += 3;
+ cptr = wskip(cptr);
+ setid = atoi(cptr);
+ MCAddSet(setid);
+ msgid = 0;
+ } else if (strncmp(cptr, "delset", 6) == 0) {
+ cptr += 6;
+ cptr = wskip(cptr);
+ setid = atoi(cptr);
+ MCDelSet(setid);
+ } else if (strncmp(cptr, "quote", 5) == 0) {
+ cptr += 5;
+ if (!*cptr)
+ quote = 0;
+ else {
+ cptr = wskip(cptr);
+ if (!*cptr)
+ quote = 0;
+ else
+ quote = *cptr;
+ }
+ } else if (isspace((unsigned char) *cptr)) {
+ ;
+ } else {
+ if (*cptr) {
+ cptr = wskip(cptr);
+ if (*cptr)
+ warning(cptr, "unrecognized line");
+ }
+ }
+ } else {
+ /*
+ * First check for (and eat) empty lines....
+ */
+ if (!*cptr)
+ continue;
+ /*
+ * We have a digit? Start of a message. Else,
+ * syntax error.
+ */
+ if (isdigit((unsigned char) *cptr)) {
+ msgid = atoi(cptr);
+ cptr = cskip(cptr);
+ if (*cptr) {
+ cptr = wskip(cptr);
+ if (!*cptr) {
+ MCAddMsg(msgid, "");
+ continue;
+ }
+ }
+ } else {
+ warning(cptr, "neither blank line nor start of a message id");
+ continue;
+ }
+ /*
+ * If no set directive specified, all messages
+ * shall be in default message set NL_SETD.
+ */
+ if (setid == 0) {
+ setid = NL_SETD;
+ MCAddSet(setid);
+ }
+ /*
+ * If we have a message ID, but no message,
+ * then this means "delete this message id
+ * from the catalog".
+ */
+ if (!*cptr) {
+ MCDelMsg(msgid);
+ } else {
+ str = getmsg(fd, cptr, quote);
+ MCAddMsg(msgid, str);
+ }
+ }
+ }
+}
+
+static void
+MCReadCat(int fd)
+{
+ void *msgcat; /* message catalog data */
+ struct _nls_cat_hdr cat_hdr;
+ struct _nls_set_hdr *set_hdr;
+ struct _nls_msg_hdr *msg_hdr;
+ char *strings;
+ ssize_t n;
+ int m, s;
+ int msgno, setno;
+
+ n = read(fd, &cat_hdr, sizeof(cat_hdr));
+ if (n < (ssize_t)sizeof(cat_hdr)) {
+ if (n == 0)
+ return; /* empty file */
+ else if (n == -1)
+ err(1, "header read");
+ else
+ errx(1, CORRUPT);
+ }
+ if (ntohl((uint32_t)cat_hdr.__magic) != _NLS_MAGIC)
+ errx(1, "%s: bad magic number (%#x)", CORRUPT, cat_hdr.__magic);
+
+ cat_hdr.__mem = ntohl(cat_hdr.__mem);
+
+ cat_hdr.__nsets = ntohl(cat_hdr.__nsets);
+ cat_hdr.__msg_hdr_offset = ntohl(cat_hdr.__msg_hdr_offset);
+ cat_hdr.__msg_txt_offset = ntohl(cat_hdr.__msg_txt_offset);
+ if ((cat_hdr.__mem < 0) ||
+ (cat_hdr.__msg_hdr_offset < 0) ||
+ (cat_hdr.__msg_txt_offset < 0) ||
+ (cat_hdr.__mem < (int32_t)(cat_hdr.__nsets * sizeof(struct _nls_set_hdr))) ||
+ (cat_hdr.__mem < cat_hdr.__msg_hdr_offset) ||
+ (cat_hdr.__mem < cat_hdr.__msg_txt_offset))
+ errx(1, "%s: catalog header", CORRUPT);
+
+ msgcat = xmalloc(cat_hdr.__mem);
+
+ n = read(fd, msgcat, cat_hdr.__mem);
+ if (n < cat_hdr.__mem) {
+ if (n == -1)
+ err(1, "data read");
+ else
+ errx(1, CORRUPT);
+ }
+
+ set_hdr = (struct _nls_set_hdr *)msgcat;
+ msg_hdr = (struct _nls_msg_hdr *)((char *)msgcat +
+ cat_hdr.__msg_hdr_offset);
+ strings = (char *)msgcat + cat_hdr.__msg_txt_offset;
+
+ setno = 0;
+ for (s = 0; s < cat_hdr.__nsets; s++, set_hdr++) {
+ set_hdr->__setno = ntohl(set_hdr->__setno);
+ if (set_hdr->__setno < setno)
+ errx(1, "%s: bad set number (%d)",
+ CORRUPT, set_hdr->__setno);
+ setno = set_hdr->__setno;
+
+ MCAddSet(setno);
+
+ set_hdr->__nmsgs = ntohl(set_hdr->__nmsgs);
+ set_hdr->__index = ntohl(set_hdr->__index);
+ if (set_hdr->__nmsgs < 0 || set_hdr->__index < 0)
+ errx(1, "%s: set header", CORRUPT);
+
+ /* Get the data */
+ msgno = 0;
+ for (m = 0; m < set_hdr->__nmsgs; m++, msg_hdr++) {
+ msg_hdr->__msgno = ntohl(msg_hdr->__msgno);
+ msg_hdr->__offset = ntohl(msg_hdr->__offset);
+ if (msg_hdr->__msgno < msgno)
+ errx(1, "%s: bad message number (%d)",
+ CORRUPT, msg_hdr->__msgno);
+ if ((msg_hdr->__offset < 0) ||
+ ((strings + msg_hdr->__offset) >
+ ((char *)msgcat + cat_hdr.__mem)))
+ errx(1, "%s: message header", CORRUPT);
+
+ msgno = msg_hdr->__msgno;
+ MCAddMsg(msgno, strings + msg_hdr->__offset);
+ }
+ }
+ free(msgcat);
+}
+
+/*
+ * Write message catalog.
+ *
+ * The message catalog is first converted from its internal to its
+ * external representation in a chunk of memory allocated for this
+ * purpose. Then the completed catalog is written. This approach
+ * avoids additional housekeeping variables and/or a lot of seeks
+ * that would otherwise be required.
+ */
+static void
+MCWriteCat(int fd)
+{
+ int nsets; /* number of sets */
+ int nmsgs; /* number of msgs */
+ int string_size; /* total size of string pool */
+ int msgcat_size; /* total size of message catalog */
+ void *msgcat; /* message catalog data */
+ struct _nls_cat_hdr *cat_hdr;
+ struct _nls_set_hdr *set_hdr;
+ struct _nls_msg_hdr *msg_hdr;
+ char *strings;
+ struct _setT *set;
+ struct _msgT *msg;
+ int msg_index;
+ int msg_offset;
+
+ /* determine number of sets, number of messages, and size of the
+ * string pool */
+ nsets = 0;
+ nmsgs = 0;
+ string_size = 0;
+
+ LIST_FOREACH(set, &sethead, entries) {
+ nsets++;
+
+ LIST_FOREACH(msg, &set->msghead, entries) {
+ nmsgs++;
+ string_size += strlen(msg->str) + 1;
+ }
+ }
+
+#ifdef DEBUG
+ printf("number of sets: %d\n", nsets);
+ printf("number of msgs: %d\n", nmsgs);
+ printf("string pool size: %d\n", string_size);
+#endif
+
+ /* determine size and then allocate buffer for constructing external
+ * message catalog representation */
+ msgcat_size = sizeof(struct _nls_cat_hdr)
+ + (nsets * sizeof(struct _nls_set_hdr))
+ + (nmsgs * sizeof(struct _nls_msg_hdr))
+ + string_size;
+
+ msgcat = xmalloc(msgcat_size);
+ memset(msgcat, '\0', msgcat_size);
+
+ /* fill in msg catalog header */
+ cat_hdr = (struct _nls_cat_hdr *) msgcat;
+ cat_hdr->__magic = htonl(_NLS_MAGIC);
+ cat_hdr->__nsets = htonl(nsets);
+ cat_hdr->__mem = htonl(msgcat_size - sizeof(struct _nls_cat_hdr));
+ cat_hdr->__msg_hdr_offset =
+ htonl(nsets * sizeof(struct _nls_set_hdr));
+ cat_hdr->__msg_txt_offset =
+ htonl(nsets * sizeof(struct _nls_set_hdr) +
+ nmsgs * sizeof(struct _nls_msg_hdr));
+
+ /* compute offsets for set & msg header tables and string pool */
+ set_hdr = (struct _nls_set_hdr *) ((char *) msgcat +
+ sizeof(struct _nls_cat_hdr));
+ msg_hdr = (struct _nls_msg_hdr *) ((char *) msgcat +
+ sizeof(struct _nls_cat_hdr) +
+ nsets * sizeof(struct _nls_set_hdr));
+ strings = (char *) msgcat +
+ sizeof(struct _nls_cat_hdr) +
+ nsets * sizeof(struct _nls_set_hdr) +
+ nmsgs * sizeof(struct _nls_msg_hdr);
+
+ msg_index = 0;
+ msg_offset = 0;
+ LIST_FOREACH(set, &sethead, entries) {
+
+ nmsgs = 0;
+ LIST_FOREACH(msg, &set->msghead, entries) {
+ int32_t msg_len = strlen(msg->str) + 1;
+
+ msg_hdr->__msgno = htonl(msg->msgId);
+ msg_hdr->__msglen = htonl(msg_len);
+ msg_hdr->__offset = htonl(msg_offset);
+
+ memcpy(strings, msg->str, msg_len);
+ strings += msg_len;
+ msg_offset += msg_len;
+
+ nmsgs++;
+ msg_hdr++;
+ }
+
+ set_hdr->__setno = htonl(set->setId);
+ set_hdr->__nmsgs = htonl(nmsgs);
+ set_hdr->__index = htonl(msg_index);
+ msg_index += nmsgs;
+ set_hdr++;
+ }
+
+ /* write out catalog. XXX: should this be done in small chunks? */
+ write(fd, msgcat, msgcat_size);
+}
+
+static void
+MCAddSet(int setId)
+{
+ struct _setT *p, *q;
+
+ if (setId <= 0) {
+ error("setId's must be greater than zero");
+ /* NOTREACHED */
+ }
+ if (setId > NL_SETMAX) {
+ error("setId exceeds limit");
+ /* NOTREACHED */
+ }
+
+ p = LIST_FIRST(&sethead);
+ q = NULL;
+ for (; p != NULL && p->setId < setId; q = p, p = LIST_NEXT(p, entries))
+ continue;
+
+ if (p && p->setId == setId) {
+ ;
+ } else {
+ p = xmalloc(sizeof(struct _setT));
+ memset(p, '\0', sizeof(struct _setT));
+ LIST_INIT(&p->msghead);
+
+ p->setId = setId;
+
+ if (q == NULL) {
+ LIST_INSERT_HEAD(&sethead, p, entries);
+ } else {
+ LIST_INSERT_AFTER(q, p, entries);
+ }
+ }
+
+ curSet = p;
+}
+
+static void
+MCAddMsg(int msgId, const char *str)
+{
+ struct _msgT *p, *q;
+
+ if (!curSet)
+ error("can't specify a message when no set exists");
+
+ if (msgId <= 0) {
+ error("msgId's must be greater than zero");
+ /* NOTREACHED */
+ }
+ if (msgId > NL_MSGMAX) {
+ error("msgID exceeds limit");
+ /* NOTREACHED */
+ }
+
+ p = LIST_FIRST(&curSet->msghead);
+ q = NULL;
+ for (; p != NULL && p->msgId < msgId; q = p, p = LIST_NEXT(p, entries))
+ continue;
+
+ if (p && p->msgId == msgId) {
+ free(p->str);
+ } else {
+ p = xmalloc(sizeof(struct _msgT));
+ memset(p, '\0', sizeof(struct _msgT));
+
+ if (q == NULL) {
+ LIST_INSERT_HEAD(&curSet->msghead, p, entries);
+ } else {
+ LIST_INSERT_AFTER(q, p, entries);
+ }
+ }
+
+ p->msgId = msgId;
+ p->str = xstrdup(str);
+}
+
+static void
+MCDelSet(int setId)
+{
+ struct _setT *set;
+ struct _msgT *msg;
+
+ if (setId <= 0) {
+ error("setId's must be greater than zero");
+ /* NOTREACHED */
+ }
+ if (setId > NL_SETMAX) {
+ error("setId exceeds limit");
+ /* NOTREACHED */
+ }
+
+ set = LIST_FIRST(&sethead);
+ for (; set != NULL && set->setId < setId; set = LIST_NEXT(set, entries))
+ continue;
+
+ if (set && set->setId == setId) {
+ LIST_REMOVE(set, entries);
+ while ((msg = LIST_FIRST(&set->msghead)) != NULL) {
+ LIST_REMOVE(msg, entries);
+ free(msg->str);
+ free(msg);
+ }
+ free(set);
+ return;
+ }
+ warning(NULL, "specified set doesn't exist");
+}
+
+static void
+MCDelMsg(int msgId)
+{
+ struct _msgT *msg;
+
+ if (!curSet)
+ error("you can't delete a message before defining the set");
+
+ msg = LIST_FIRST(&curSet->msghead);
+ for (; msg != NULL && msg->msgId < msgId; msg = LIST_NEXT(msg, entries))
+ continue;
+
+ if (msg && msg->msgId == msgId) {
+ LIST_REMOVE(msg, entries);
+ free(msg->str);
+ free(msg);
+ return;
+ }
+ warning(NULL, "specified msg doesn't exist");
+}
diff --git a/usr.bin/getconf/getconf.1 b/usr.bin/getconf/getconf.1
new file mode 100644
index 0000000..520a688
--- /dev/null
+++ b/usr.bin/getconf/getconf.1
@@ -0,0 +1,94 @@
+.\" $NetBSD: getconf.1,v 1.13 2014/04/13 01:45:34 snj Exp $
+.\"
+.\" Copyright (c) 1996 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by J.T. Conklin.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd August 9, 2011
+.Dt GETCONF 1
+.Os
+.Sh NAME
+.Nm getconf
+.Nd get configuration values
+.Sh SYNOPSIS
+.Nm
+.Ar system_var
+.Nm
+.Fl a
+.Nm
+.Ar path_var
+.Ar pathname
+.Nm
+.Fl a
+.Ar pathname
+.Sh DESCRIPTION
+The
+.Nm
+utility writes the current value of a configurable system limit or
+option variable to the standard output.
+.Pp
+The
+.Ar system_var
+argument specifies the system variable to be queried.
+The names of the system variables are from
+.Xr sysconf 3
+with the leading
+.Dq Li _SC_
+removed.
+.Pp
+The
+.Ar path_var
+argument specifies the pathname variable to be queried for the specified
+.Ar pathname
+argument.
+The names of the pathname variables are from
+.Xr pathconf 2
+with the leading
+.Dq Li _PC_
+removed.
+.Pp
+When invoked with the option
+.Fl a ,
+.Nm
+writes a list of all applicable variables and their values to the
+standard output, in the format
+.Do
+.Va name
+=
+.Va value
+.Dc .
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr pathconf 2 ,
+.Xr confstr 3 ,
+.Xr limits 3 ,
+.Xr sysconf 3
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/getconf/getconf.c b/usr.bin/getconf/getconf.c
new file mode 100644
index 0000000..8de472b
--- /dev/null
+++ b/usr.bin/getconf/getconf.c
@@ -0,0 +1,342 @@
+/* $NetBSD: getconf.c,v 1.35 2013/12/19 19:11:50 rmind Exp $ */
+
+/*-
+ * Copyright (c) 1996, 1998 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by J.T. Conklin.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: getconf.c,v 1.35 2013/12/19 19:11:50 rmind Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+struct conf_variable
+{
+ const char *name;
+ enum { SYSCONF, CONFSTR, PATHCONF, CONSTANT } type;
+ long value;
+};
+
+static void print_longvar(const char *, long);
+static void print_strvar(const char *, const char *);
+static void printvar(const struct conf_variable *, const char *);
+static void usage(void) __dead;
+
+static const struct conf_variable conf_table[] =
+{
+ { "PATH", CONFSTR, _CS_PATH },
+
+ /* Utility Limit Minimum Values */
+ { "POSIX2_BC_BASE_MAX", CONSTANT, _POSIX2_BC_BASE_MAX },
+ { "POSIX2_BC_DIM_MAX", CONSTANT, _POSIX2_BC_DIM_MAX },
+ { "POSIX2_BC_SCALE_MAX", CONSTANT, _POSIX2_BC_SCALE_MAX },
+ { "POSIX2_BC_STRING_MAX", CONSTANT, _POSIX2_BC_STRING_MAX },
+ { "POSIX2_COLL_WEIGHTS_MAX", CONSTANT, _POSIX2_COLL_WEIGHTS_MAX },
+ { "POSIX2_EXPR_NEST_MAX", CONSTANT, _POSIX2_EXPR_NEST_MAX },
+ { "POSIX2_LINE_MAX", CONSTANT, _POSIX2_LINE_MAX },
+ { "POSIX2_RE_DUP_MAX", CONSTANT, _POSIX2_RE_DUP_MAX },
+ { "POSIX2_VERSION", CONSTANT, _POSIX2_VERSION },
+
+ /* POSIX.1 Minimum Values */
+ { "_POSIX_AIO_LISTIO_MAX", CONSTANT, _POSIX_AIO_LISTIO_MAX },
+ { "_POSIX_AIO_MAX", CONSTANT, _POSIX_AIO_MAX },
+ { "_POSIX_ARG_MAX", CONSTANT, _POSIX_ARG_MAX },
+ { "_POSIX_CHILD_MAX", CONSTANT, _POSIX_CHILD_MAX },
+ { "_POSIX_LINK_MAX", CONSTANT, _POSIX_LINK_MAX },
+ { "_POSIX_MAX_CANON", CONSTANT, _POSIX_MAX_CANON },
+ { "_POSIX_MAX_INPUT", CONSTANT, _POSIX_MAX_INPUT },
+ { "_POSIX_MQ_OPEN_MAX", CONSTANT, _POSIX_MQ_OPEN_MAX },
+ { "_POSIX_MQ_PRIO_MAX", CONSTANT, _POSIX_MQ_PRIO_MAX },
+ { "_POSIX_NAME_MAX", CONSTANT, _POSIX_NAME_MAX },
+ { "_POSIX_NGROUPS_MAX", CONSTANT, _POSIX_NGROUPS_MAX },
+ { "_POSIX_OPEN_MAX", CONSTANT, _POSIX_OPEN_MAX },
+ { "_POSIX_PATH_MAX", CONSTANT, _POSIX_PATH_MAX },
+ { "_POSIX_PIPE_BUF", CONSTANT, _POSIX_PIPE_BUF },
+ { "_POSIX_SSIZE_MAX", CONSTANT, _POSIX_SSIZE_MAX },
+ { "_POSIX_STREAM_MAX", CONSTANT, _POSIX_STREAM_MAX },
+ { "_POSIX_TZNAME_MAX", CONSTANT, _POSIX_TZNAME_MAX },
+
+ /* Symbolic Utility Limits */
+ { "BC_BASE_MAX", SYSCONF, _SC_BC_BASE_MAX },
+ { "BC_DIM_MAX", SYSCONF, _SC_BC_DIM_MAX },
+ { "BC_SCALE_MAX", SYSCONF, _SC_BC_SCALE_MAX },
+ { "BC_STRING_MAX", SYSCONF, _SC_BC_STRING_MAX },
+ { "COLL_WEIGHTS_MAX", SYSCONF, _SC_COLL_WEIGHTS_MAX },
+ { "EXPR_NEST_MAX", SYSCONF, _SC_EXPR_NEST_MAX },
+ { "LINE_MAX", SYSCONF, _SC_LINE_MAX },
+ { "RE_DUP_MAX", SYSCONF, _SC_RE_DUP_MAX },
+
+ /* Optional Facility Configuration Values */
+ { "_POSIX2_C_BIND", SYSCONF, _SC_2_C_BIND },
+ { "POSIX2_C_DEV", SYSCONF, _SC_2_C_DEV },
+ { "POSIX2_CHAR_TERM", SYSCONF, _SC_2_CHAR_TERM },
+ { "POSIX2_FORT_DEV", SYSCONF, _SC_2_FORT_DEV },
+ { "POSIX2_FORT_RUN", SYSCONF, _SC_2_FORT_RUN },
+ { "POSIX2_LOCALEDEF", SYSCONF, _SC_2_LOCALEDEF },
+ { "POSIX2_SW_DEV", SYSCONF, _SC_2_SW_DEV },
+ { "POSIX2_UPE", SYSCONF, _SC_2_UPE },
+
+ /* POSIX.1 Configurable System Variables */
+ { "AIO_LISTIO_MAX", SYSCONF, _SC_AIO_LISTIO_MAX },
+ { "AIO_MAX", SYSCONF, _SC_AIO_MAX },
+ { "ARG_MAX", SYSCONF, _SC_ARG_MAX },
+ { "CHILD_MAX", SYSCONF, _SC_CHILD_MAX },
+ { "CLK_TCK", SYSCONF, _SC_CLK_TCK },
+ { "MQ_OPEN_MAX", SYSCONF, _SC_MQ_OPEN_MAX },
+ { "MQ_PRIO_MAX", SYSCONF, _SC_MQ_PRIO_MAX },
+ { "NGROUPS_MAX", SYSCONF, _SC_NGROUPS_MAX },
+ { "OPEN_MAX", SYSCONF, _SC_OPEN_MAX },
+ { "STREAM_MAX", SYSCONF, _SC_STREAM_MAX },
+ { "TZNAME_MAX", SYSCONF, _SC_TZNAME_MAX },
+ { "_POSIX_JOB_CONTROL", SYSCONF, _SC_JOB_CONTROL },
+ { "_POSIX_SAVED_IDS", SYSCONF, _SC_SAVED_IDS },
+ { "_POSIX_VERSION", SYSCONF, _SC_VERSION },
+
+ { "LINK_MAX", PATHCONF, _PC_LINK_MAX },
+ { "MAX_CANON", PATHCONF, _PC_MAX_CANON },
+ { "MAX_INPUT", PATHCONF, _PC_MAX_INPUT },
+ { "NAME_MAX", PATHCONF, _PC_NAME_MAX },
+ { "PATH_MAX", PATHCONF, _PC_PATH_MAX },
+ { "PIPE_BUF", PATHCONF, _PC_PIPE_BUF },
+ { "_POSIX_CHOWN_RESTRICTED", PATHCONF, _PC_CHOWN_RESTRICTED },
+ { "_POSIX_NO_TRUNC", PATHCONF, _PC_NO_TRUNC },
+ { "_POSIX_VDISABLE", PATHCONF, _PC_VDISABLE },
+
+ /* POSIX.1b Configurable System Variables */
+ { "PAGESIZE", SYSCONF, _SC_PAGESIZE },
+ { "_POSIX_ASYNCHRONOUS_IO", SYSCONF, _SC_ASYNCHRONOUS_IO },
+ { "_POSIX_FSYNC", SYSCONF, _SC_FSYNC },
+ { "_POSIX_MAPPED_FILES", SYSCONF, _SC_MAPPED_FILES },
+ { "_POSIX_MEMLOCK", SYSCONF, _SC_MEMLOCK },
+ { "_POSIX_MEMLOCK_RANGE", SYSCONF, _SC_MEMLOCK_RANGE },
+ { "_POSIX_MEMORY_PROTECTION", SYSCONF, _SC_MEMORY_PROTECTION },
+ { "_POSIX_MESSAGE_PASSING", SYSCONF, _SC_MESSAGE_PASSING },
+ { "_POSIX_MONOTONIC_CLOCK", SYSCONF, _SC_MONOTONIC_CLOCK },
+ { "_POSIX_PRIORITY_SCHEDULING", SYSCONF, _SC_PRIORITY_SCHEDULING },
+ { "_POSIX_SEMAPHORES", SYSCONF, _SC_SEMAPHORES },
+ { "_POSIX_SHARED_MEMORY_OBJECTS", SYSCONF, _SC_SHARED_MEMORY_OBJECTS },
+ { "_POSIX_SYNCHRONIZED_IO", SYSCONF, _SC_SYNCHRONIZED_IO },
+ { "_POSIX_TIMERS", SYSCONF, _SC_TIMERS },
+
+ { "_POSIX_SYNC_IO", PATHCONF, _PC_SYNC_IO },
+
+ /* POSIX.1c Configurable System Variables */
+ { "LOGIN_NAME_MAX", SYSCONF, _SC_LOGIN_NAME_MAX },
+ { "_POSIX_THREADS", SYSCONF, _SC_THREADS },
+
+ /* POSIX.1j Configurable System Variables */
+ { "_POSIX_BARRIERS", SYSCONF, _SC_BARRIERS },
+ { "_POSIX_READER_WRITER_LOCKS", SYSCONF, _SC_READER_WRITER_LOCKS },
+ { "_POSIX_SPIN_LOCKS", SYSCONF, _SC_SPIN_LOCKS },
+
+ /* XPG4.2 Configurable System Variables */
+ { "IOV_MAX", SYSCONF, _SC_IOV_MAX },
+ { "PAGE_SIZE", SYSCONF, _SC_PAGE_SIZE },
+ { "_XOPEN_SHM", SYSCONF, _SC_XOPEN_SHM },
+
+ /* X/Open CAE Spec. Issue 5 Version 2 Configurable System Variables */
+ { "FILESIZEBITS", PATHCONF, _PC_FILESIZEBITS },
+
+ /* POSIX.1-2001 XSI Option Group Configurable System Variables */
+ { "ATEXIT_MAX", SYSCONF, _SC_ATEXIT_MAX },
+
+ /* POSIX.1-2001 TSF Configurable System Variables */
+ { "GETGR_R_SIZE_MAX", SYSCONF, _SC_GETGR_R_SIZE_MAX },
+ { "GETPW_R_SIZE_MAX", SYSCONF, _SC_GETPW_R_SIZE_MAX },
+
+#ifdef _NETBSD_SOURCE
+ /* Commonly provided extensions */
+ { "NPROCESSORS_CONF", SYSCONF, _SC_NPROCESSORS_CONF },
+ { "NPROCESSORS_ONLN", SYSCONF, _SC_NPROCESSORS_ONLN },
+#endif /* _NETBSD_SOURCE */
+
+ { NULL, CONSTANT, 0L }
+};
+
+static int a_flag = 0; /* list all variables */
+
+int
+main(int argc, char **argv)
+{
+ int ch;
+ const struct conf_variable *cp;
+ const char *varname, *pathname;
+ int found;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "a")) != -1) {
+ switch (ch) {
+ case 'a':
+ a_flag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!a_flag) {
+ if (argc == 0)
+ usage();
+ varname = argv[0];
+ argc--;
+ argv++;
+ } else
+ varname = NULL;
+
+ if (argc > 1)
+ usage();
+ pathname = argv[0]; /* may be NULL */
+
+ found = 0;
+ for (cp = conf_table; cp->name != NULL; cp++) {
+ if (a_flag || strcmp(varname, cp->name) == 0) {
+ /*LINTED weird expression*/
+ if ((cp->type == PATHCONF) == (pathname != NULL)) {
+ printvar(cp, pathname);
+ found = 1;
+ } else if (!a_flag)
+ errx(EXIT_FAILURE,
+ "%s: invalid variable type", cp->name);
+ }
+ }
+
+ if (!a_flag && !found)
+ errx(EXIT_FAILURE, "%s: unknown variable", varname);
+
+ (void)fflush(stdout);
+ return ferror(stdout) ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+static void
+print_longvar(const char *name, long value)
+{
+ if (a_flag)
+ (void)printf("%s = %ld\n", name, value);
+ else
+ (void)printf("%ld\n", value);
+}
+
+static void
+print_strvar(const char *name, const char *sval)
+{
+ if (a_flag)
+ (void)printf("%s = %s\n", name, sval);
+ else
+ (void)printf("%s\n", sval);
+}
+
+static void
+printvar(const struct conf_variable *cp, const char *pathname)
+{
+ size_t slen;
+ char *sval;
+ long val;
+
+ switch (cp->type) {
+ case CONSTANT:
+ print_longvar(cp->name, cp->value);
+ break;
+
+ case CONFSTR:
+ errno = 0;
+ slen = confstr((int)cp->value, NULL, 0);
+ if (slen == 0) {
+ if (errno != 0)
+
+out: err(EXIT_FAILURE, "confstr(%ld)", cp->value);
+ else
+ print_strvar(cp->name, "undefined");
+ }
+
+ if ((sval = malloc(slen)) == NULL)
+ err(EXIT_FAILURE, "Can't allocate %zu bytes", slen);
+
+ errno = 0;
+ if (confstr((int)cp->value, sval, slen) == 0) {
+ if (errno != 0)
+ goto out;
+ else
+ print_strvar(cp->name, "undefined");
+ } else
+ print_strvar(cp->name, sval);
+
+ free(sval);
+ break;
+
+ case SYSCONF:
+ errno = 0;
+ if ((val = sysconf((int)cp->value)) == -1) {
+ if (errno != 0)
+ err(EXIT_FAILURE, "sysconf(%ld)", cp->value);
+ print_strvar(cp->name, "undefined");
+ } else
+ print_longvar(cp->name, val);
+ break;
+
+ case PATHCONF:
+ errno = 0;
+ if ((val = pathconf(pathname, (int)cp->value)) == -1) {
+ if (errno != 0) {
+ if (a_flag && errno == EINVAL) {
+ /* Just skip invalid variables */
+ return;
+ }
+ err(EXIT_FAILURE, "pathconf(%s, %ld)",
+ pathname, cp->value);
+ /* NOTREACHED */
+ }
+
+ print_strvar(cp->name, "undefined");
+ } else
+ print_longvar(cp->name, val);
+ break;
+ }
+}
+
+
+static void
+usage(void)
+{
+ const char *p = getprogname();
+ (void)fprintf(stderr, "Usage: %s system_var\n\t%s -a\n"
+ "\t%s path_var pathname\n\t%s -a pathname\n", p, p, p, p);
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/grep/fastgrep.c b/usr.bin/grep/fastgrep.c
new file mode 100644
index 0000000..2fcd864
--- /dev/null
+++ b/usr.bin/grep/fastgrep.c
@@ -0,0 +1,336 @@
+/* $OpenBSD: util.c,v 1.36 2007/10/02 17:59:18 otto Exp $ */
+/* $FreeBSD: head/usr.bin/grep/fastgrep.c 211496 2010-08-19 09:28:59Z des $ */
+
+/*-
+ * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav
+ * Copyright (C) 2008 Gabor Kovesdan <gabor@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * XXX: This file is a speed up for grep to cover the defects of the
+ * regex library. These optimizations should practically be implemented
+ * there keeping this code clean. This is a future TODO, but for the
+ * meantime, we need to use this workaround.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: fastgrep.c,v 1.5 2011/04/18 03:27:40 joerg Exp $");
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include "grep.h"
+
+static inline int grep_cmp(const unsigned char *, const unsigned char *, size_t);
+static inline void grep_revstr(unsigned char *, int);
+
+void
+fgrepcomp(fastgrep_t *fg, const char *pat)
+{
+ unsigned int i;
+
+ /* Initialize. */
+ fg->len = strlen(pat);
+ fg->bol = false;
+ fg->eol = false;
+ fg->reversed = false;
+
+ fg->pattern = (unsigned char *)grep_strdup(pat);
+
+ /* Preprocess pattern. */
+ for (i = 0; i <= UCHAR_MAX; i++)
+ fg->qsBc[i] = fg->len;
+ for (i = 1; i < fg->len; i++)
+ fg->qsBc[fg->pattern[i]] = fg->len - i;
+}
+
+/*
+ * Returns: -1 on failure, 0 on success
+ */
+int
+fastcomp(fastgrep_t *fg, const char *pat)
+{
+ unsigned int i;
+ int firstHalfDot = -1;
+ int firstLastHalfDot = -1;
+ int hasDot = 0;
+ int lastHalfDot = 0;
+ int shiftPatternLen;
+
+ /* Initialize. */
+ fg->len = strlen(pat);
+ fg->bol = false;
+ fg->eol = false;
+ fg->reversed = false;
+ fg->word = wflag;
+
+ /* Remove end-of-line character ('$'). */
+ if (fg->len > 0 && pat[fg->len - 1] == '$') {
+ fg->eol = true;
+ fg->len--;
+ }
+
+ /* Remove beginning-of-line character ('^'). */
+ if (pat[0] == '^') {
+ fg->bol = true;
+ fg->len--;
+ pat++;
+ }
+
+ if (fg->len >= 14 &&
+ memcmp(pat, "[[:<:]]", 7) == 0 &&
+ memcmp(pat + fg->len - 7, "[[:>:]]", 7) == 0) {
+ fg->len -= 14;
+ pat += 7;
+ /* Word boundary is handled separately in util.c */
+ fg->word = true;
+ }
+
+ /*
+ * pat has been adjusted earlier to not include '^', '$' or
+ * the word match character classes at the beginning and ending
+ * of the string respectively.
+ */
+ fg->pattern = grep_malloc(fg->len + 1);
+ memcpy(fg->pattern, pat, fg->len);
+ fg->pattern[fg->len] = '\0';
+
+ /* Look for ways to cheat...er...avoid the full regex engine. */
+ for (i = 0; i < fg->len; i++) {
+ /* Can still cheat? */
+ if (fg->pattern[i] == '.') {
+ hasDot = i;
+ if (i < fg->len / 2) {
+ if (firstHalfDot < 0)
+ /* Closest dot to the beginning */
+ firstHalfDot = i;
+ } else {
+ /* Closest dot to the end of the pattern. */
+ lastHalfDot = i;
+ if (firstLastHalfDot < 0)
+ firstLastHalfDot = i;
+ }
+ } else {
+ /* Free memory and let others know this is empty. */
+ free(fg->pattern);
+ fg->pattern = NULL;
+ return (-1);
+ }
+ }
+
+ /*
+ * Determine if a reverse search would be faster based on the placement
+ * of the dots.
+ */
+ if ((!(lflag || cflag)) && ((!(fg->bol || fg->eol)) &&
+ ((lastHalfDot) && ((firstHalfDot < 0) ||
+ ((fg->len - (lastHalfDot + 1)) < (size_t)firstHalfDot)))) &&
+ !oflag && !color) {
+ fg->reversed = true;
+ hasDot = fg->len - (firstHalfDot < 0 ?
+ firstLastHalfDot : firstHalfDot) - 1;
+ grep_revstr(fg->pattern, fg->len);
+ }
+
+ /*
+ * Normal Quick Search would require a shift based on the position the
+ * next character after the comparison is within the pattern. With
+ * wildcards, the position of the last dot effects the maximum shift
+ * distance.
+ * The closer to the end the wild card is the slower the search. A
+ * reverse version of this algorithm would be useful for wildcards near
+ * the end of the string.
+ *
+ * Examples:
+ * Pattern Max shift
+ * ------- ---------
+ * this 5
+ * .his 4
+ * t.is 3
+ * th.s 2
+ * thi. 1
+ */
+
+ /* Adjust the shift based on location of the last dot ('.'). */
+ shiftPatternLen = fg->len - hasDot;
+
+ /* Preprocess pattern. */
+ for (i = 0; i <= (signed)UCHAR_MAX; i++)
+ fg->qsBc[i] = shiftPatternLen;
+ for (i = hasDot + 1; i < fg->len; i++) {
+ fg->qsBc[fg->pattern[i]] = fg->len - i;
+ }
+
+ /*
+ * Put pattern back to normal after pre-processing to allow for easy
+ * comparisons later.
+ */
+ if (fg->reversed)
+ grep_revstr(fg->pattern, fg->len);
+
+ return (0);
+}
+
+int
+grep_search(fastgrep_t *fg, const unsigned char *data, size_t len, regmatch_t *pmatch)
+{
+ unsigned int j;
+ int ret = REG_NOMATCH;
+
+ if (pmatch->rm_so == (ssize_t)len)
+ return (ret);
+
+ if (fg->bol && pmatch->rm_so != 0) {
+ pmatch->rm_so = len;
+ pmatch->rm_eo = len;
+ return (ret);
+ }
+
+ /* No point in going farther if we do not have enough data. */
+ if (len < fg->len)
+ return (ret);
+
+ /* Only try once at the beginning or ending of the line. */
+ if (fg->bol || fg->eol) {
+ /* Simple text comparison. */
+ /* Verify data is >= pattern length before searching on it. */
+ if (len >= fg->len) {
+ /* Determine where in data to start search at. */
+ j = fg->eol ? len - fg->len : 0;
+ if (!((fg->bol && fg->eol) && (len != fg->len)))
+ if (grep_cmp(fg->pattern, data + j,
+ fg->len) == -1) {
+ pmatch->rm_so = j;
+ pmatch->rm_eo = j + fg->len;
+ ret = 0;
+ }
+ }
+ } else if (fg->reversed) {
+ /* Quick Search algorithm. */
+ j = len;
+ do {
+ if (grep_cmp(fg->pattern, data + j - fg->len,
+ fg->len) == -1) {
+ pmatch->rm_so = j - fg->len;
+ pmatch->rm_eo = j;
+ ret = 0;
+ break;
+ }
+ /* Shift if within bounds, otherwise, we are done. */
+ if (j == fg->len)
+ break;
+ j -= fg->qsBc[data[j - fg->len - 1]];
+ } while (j >= fg->len);
+ } else {
+ /* Quick Search algorithm. */
+ j = pmatch->rm_so;
+ do {
+ if (grep_cmp(fg->pattern, data + j, fg->len) == -1) {
+ pmatch->rm_so = j;
+ pmatch->rm_eo = j + fg->len;
+ ret = 0;
+ break;
+ }
+
+ /* Shift if within bounds, otherwise, we are done. */
+ if (j + fg->len == len)
+ break;
+ else
+ j += fg->qsBc[data[j + fg->len]];
+ } while (j <= (len - fg->len));
+ }
+
+ return (ret);
+}
+
+/*
+ * Returns: i >= 0 on failure (position that it failed)
+ * -1 on success
+ */
+static inline int
+grep_cmp(const unsigned char *pat, const unsigned char *data, size_t len)
+{
+ size_t size;
+ wchar_t *wdata, *wpat;
+ unsigned int i;
+
+ if (iflag) {
+ if ((size = mbstowcs(NULL, (const char *)data, 0)) ==
+ ((size_t) - 1))
+ return (-1);
+
+ wdata = grep_malloc(size * sizeof(wint_t));
+
+ if (mbstowcs(wdata, (const char *)data, size) ==
+ ((size_t) - 1))
+ return (-1);
+
+ if ((size = mbstowcs(NULL, (const char *)pat, 0)) ==
+ ((size_t) - 1))
+ return (-1);
+
+ wpat = grep_malloc(size * sizeof(wint_t));
+
+ if (mbstowcs(wpat, (const char *)pat, size) == ((size_t) - 1))
+ return (-1);
+ for (i = 0; i < len; i++) {
+ if ((towlower(wpat[i]) == towlower(wdata[i])) ||
+ ((grepbehave != GREP_FIXED) && wpat[i] == L'.'))
+ continue;
+ free(wpat);
+ free(wdata);
+ return (i);
+ }
+ } else {
+ for (i = 0; i < len; i++) {
+ if ((pat[i] == data[i]) || ((grepbehave != GREP_FIXED) &&
+ pat[i] == '.'))
+ continue;
+ return (i);
+ }
+ }
+ return (-1);
+}
+
+static inline void
+grep_revstr(unsigned char *str, int len)
+{
+ int i;
+ char c;
+
+ for (i = 0; i < len / 2; i++) {
+ c = str[i];
+ str[i] = str[len - i - 1];
+ str[len - i - 1] = c;
+ }
+}
diff --git a/usr.bin/grep/file.c b/usr.bin/grep/file.c
new file mode 100644
index 0000000..ef057ba
--- /dev/null
+++ b/usr.bin/grep/file.c
@@ -0,0 +1,276 @@
+/* $NetBSD: file.c,v 1.10 2018/08/12 09:03:21 christos Exp $ */
+/* $FreeBSD: head/usr.bin/grep/file.c 211496 2010-08-19 09:28:59Z des $ */
+/* $OpenBSD: file.c,v 1.11 2010/07/02 20:48:48 nicm Exp $ */
+
+/*-
+ * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav
+ * Copyright (C) 2008-2010 Gabor Kovesdan <gabor@FreeBSD.org>
+ * Copyright (C) 2010 Dimitry Andric <dimitry@andric.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: file.c,v 1.10 2018/08/12 09:03:21 christos Exp $");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include "grep.h"
+
+#define MAXBUFSIZ (32 * 1024)
+#define LNBUFBUMP 80
+
+#ifndef WITHOUT_GZIP
+static gzFile gzbufdesc;
+#endif
+#ifndef WITHOUT_BZ2
+static BZFILE* bzbufdesc;
+#endif
+
+static unsigned char buffer[MAXBUFSIZ];
+static unsigned char *bufpos;
+static size_t bufrem;
+
+static unsigned char *lnbuf;
+static size_t lnbuflen;
+
+static inline int
+grep_refill(struct file *f)
+{
+ ssize_t nr = -1;
+ int bzerr;
+
+ bufpos = buffer;
+ bufrem = 0;
+
+#ifndef WITHOUT_GZIP
+ if (filebehave == FILE_GZIP) {
+ nr = gzread(gzbufdesc, buffer, MAXBUFSIZ);
+ if (nr == -1)
+ return -1;
+ }
+#endif
+#ifndef WITHOUT_BZ2
+ if (filebehave == FILE_BZIP && bzbufdesc != NULL) {
+ nr = BZ2_bzRead(&bzerr, bzbufdesc, buffer, MAXBUFSIZ);
+ switch (bzerr) {
+ case BZ_OK:
+ case BZ_STREAM_END:
+ /* No problem, nr will be okay */
+ break;
+ case BZ_DATA_ERROR_MAGIC:
+ /*
+ * As opposed to gzread(), which simply returns the
+ * plain file data, if it is not in the correct
+ * compressed format, BZ2_bzRead() instead aborts.
+ *
+ * So, just restart at the beginning of the file again,
+ * and use plain reads from now on.
+ */
+ BZ2_bzReadClose(&bzerr, bzbufdesc);
+ bzbufdesc = NULL;
+ if (lseek(f->fd, 0, SEEK_SET) == -1)
+ return (-1);
+ nr = read(f->fd, buffer, MAXBUFSIZ);
+ break;
+ default:
+ /* Make sure we exit with an error */
+ nr = -1;
+ }
+ if (nr == -1)
+ return -1;
+ }
+#endif
+ if (nr == -1) {
+ nr = read(f->fd, buffer, MAXBUFSIZ);
+ }
+
+ if (nr < 0)
+ return (-1);
+
+ bufrem = nr;
+ return (0);
+}
+
+static inline int
+grep_lnbufgrow(size_t newlen)
+{
+
+ if (lnbuflen < newlen) {
+ lnbuf = grep_realloc(lnbuf, newlen);
+ lnbuflen = newlen;
+ }
+
+ return (0);
+}
+
+char *
+grep_fgetln(struct file *f, size_t *lenp)
+{
+ unsigned char *p;
+ char *ret;
+ size_t len;
+ size_t off;
+ ptrdiff_t diff;
+
+ /* Fill the buffer, if necessary */
+ if (bufrem == 0 && grep_refill(f) != 0)
+ goto error;
+
+ if (bufrem == 0) {
+ /* Return zero length to indicate EOF */
+ *lenp = 0;
+ return ((char *)bufpos);
+ }
+
+ /* Look for a newline in the remaining part of the buffer */
+ if ((p = memchr(bufpos, line_sep, bufrem)) != NULL) {
+ ++p; /* advance over newline */
+ ret = (char *)bufpos;
+ len = p - bufpos;
+ bufrem -= len;
+ bufpos = p;
+ *lenp = len;
+ return (ret);
+ }
+
+ /* We have to copy the current buffered data to the line buffer */
+ for (len = bufrem, off = 0; ; len += bufrem) {
+ /* Make sure there is room for more data */
+ if (grep_lnbufgrow(len + LNBUFBUMP))
+ goto error;
+ memcpy(lnbuf + off, bufpos, len - off);
+ off = len;
+ if (grep_refill(f) != 0)
+ goto error;
+ if (bufrem == 0)
+ /* EOF: return partial line */
+ break;
+ if ((p = memchr(bufpos, line_sep, bufrem)) == NULL)
+ continue;
+ /* got it: finish up the line (like code above) */
+ ++p;
+ diff = p - bufpos;
+ len += diff;
+ if (grep_lnbufgrow(len))
+ goto error;
+ memcpy(lnbuf + off, bufpos, diff);
+ bufrem -= diff;
+ bufpos = p;
+ break;
+ }
+ *lenp = len;
+ return ((char *)lnbuf);
+
+error:
+ *lenp = 0;
+ return (NULL);
+}
+
+static inline struct file *
+grep_file_init(struct file *f)
+{
+
+#ifndef WITHOUT_GZIP
+ if (filebehave == FILE_GZIP &&
+ (gzbufdesc = gzdopen(f->fd, "r")) == NULL)
+ goto error;
+#endif
+
+#ifndef WITHOUT_BZ2
+ if (filebehave == FILE_BZIP &&
+ (bzbufdesc = BZ2_bzdopen(f->fd, "r")) == NULL)
+ goto error;
+#endif
+
+ /* Fill read buffer, also catches errors early */
+ if (grep_refill(f) != 0)
+ goto error;
+
+ /* Check for binary stuff, if necessary */
+ if (!nulldataflag && binbehave != BINFILE_TEXT &&
+ memchr(bufpos, '\0', bufrem) != NULL)
+ f->binary = true;
+
+ return (f);
+error:
+ close(f->fd);
+ free(f);
+ return (NULL);
+}
+
+/*
+ * Opens a file for processing.
+ */
+struct file *
+grep_open(const char *path)
+{
+ struct file *f;
+
+ f = grep_malloc(sizeof *f);
+ memset(f, 0, sizeof *f);
+ if (path == NULL) {
+ /* Processing stdin implies --line-buffered. */
+ lbflag = true;
+ f->fd = STDIN_FILENO;
+ } else if ((f->fd = open(path, O_RDONLY)) == -1) {
+ free(f);
+ return (NULL);
+ }
+
+ return (grep_file_init(f));
+}
+
+/*
+ * Closes a file.
+ */
+void
+grep_close(struct file *f)
+{
+
+ close(f->fd);
+
+ /* Reset read buffer and line buffer */
+ bufpos = buffer;
+ bufrem = 0;
+
+ free(lnbuf);
+ lnbuf = NULL;
+ lnbuflen = 0;
+}
diff --git a/usr.bin/grep/grep.1 b/usr.bin/grep/grep.1
new file mode 100644
index 0000000..7446cad
--- /dev/null
+++ b/usr.bin/grep/grep.1
@@ -0,0 +1,486 @@
+.\" $NetBSD: grep.1,v 1.4 2012/04/08 22:00:38 wiz Exp $
+.\" $FreeBSD: head/usr.bin/grep/grep.1 210652 2010-07-30 14:05:20Z joel $
+.\" $OpenBSD: grep.1,v 1.38 2010/04/05 06:30:59 jmc Exp $
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)grep.1 8.3 (Berkeley) 4/18/94
+.\"
+.Dd April 19, 2011
+.Dt GREP 1
+.Os
+.Sh NAME
+.Nm grep , egrep , fgrep ,
+.Nm zgrep , zegrep , zfgrep
+.Nd file pattern searcher
+.Sh SYNOPSIS
+.Nm grep
+.Op Fl abcdDEFGHhIiJLlmnOopqRSsUVvwxZz
+.Op Fl A Ar num
+.Op Fl B Ar num
+.Op Fl C Ns Op Ar num
+.Op Fl e Ar pattern
+.Op Fl f Ar file
+.Op Fl Fl binary-files Ns = Ns Ar value
+.Op Fl Fl color Ns Op = Ns Ar when
+.Op Fl Fl colour Ns Op = Ns Ar when
+.Op Fl Fl context Ns Op = Ns Ar num
+.Op Fl Fl decompress
+.Op Fl Fl label
+.Op Fl Fl line-buffered
+.Op Ar pattern
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm grep
+utility searches any given input files,
+selecting lines that match one or more patterns.
+By default, a pattern matches an input line if the regular expression
+(RE) in the pattern matches the input line
+without its trailing newline.
+An empty expression matches every line.
+Each input line that matches at least one of the patterns is written
+to the standard output.
+.Pp
+.Nm grep
+is used for simple patterns and
+basic regular expressions
+.Pq BREs ;
+.Nm egrep
+can handle extended regular expressions
+.Pq EREs .
+See
+.Xr re_format 7
+for more information on regular expressions.
+.Nm fgrep
+is quicker than both
+.Nm grep
+and
+.Nm egrep ,
+but can only handle fixed patterns
+(i.e. it does not interpret regular expressions).
+Patterns may consist of one or more lines,
+allowing any of the pattern lines to match a portion of the input.
+.Pp
+.Nm zgrep ,
+.Nm zegrep ,
+and
+.Nm zfgrep
+act like
+.Nm grep ,
+.Nm egrep ,
+and
+.Nm fgrep ,
+respectively, but accept input files compressed with the
+.Xr compress 1
+or
+.Xr gzip 1
+compression utilities.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl A Ar num , Fl Fl after-context Ns = Ns Ar num
+Print
+.Ar num
+lines of trailing context after each match.
+See also the
+.Fl B
+and
+.Fl C
+options.
+.It Fl a , Fl Fl text
+Treat all files as ASCII text.
+Normally
+.Nm
+will simply print
+.Dq Binary file ... matches
+if files contain binary characters.
+Use of this option forces
+.Nm
+to output lines matching the specified pattern.
+.It Fl B Ar num , Fl Fl before-context Ns = Ns Ar num
+Print
+.Ar num
+lines of leading context before each match.
+See also the
+.Fl A
+and
+.Fl C
+options.
+.It Fl b , Fl Fl byte-offset
+The offset in bytes of a matched pattern is
+displayed in front of the respective matched line.
+.It Fl C Ns Op Ar num , Fl Fl context Ns = Ns Ar num
+Print
+.Ar num
+lines of leading and trailing context surrounding each match.
+The default is 2 and is equivalent to
+.Fl A
+.Ar 2
+.Fl B
+.Ar 2 .
+Note:
+no whitespace may be given between the option and its argument.
+.It Fl c , Fl Fl count
+Only a count of selected lines is written to standard output.
+.It Fl Fl colour Ns = Ns Op Ar when , Fl Fl color Ns = Ns Op Ar when
+Mark up the matching text with the expression stored in
+.Ev GREP_COLOR
+environment variable.
+The possible values of when can be `never', `always' or `auto'.
+.It Fl D Ar action , Fl Fl devices Ns = Ns Ar action
+Specify the demanded action for devices, FIFOs and sockets.
+The default action is `read', which means, that they are read
+as if they were normal files.
+If the action is set to `skip', devices will be silently skipped.
+.It Fl d Ar action , Fl Fl directories Ns = Ns Ar action
+Specify the demanded action for directories.
+It is `read' by default, which means that the directories
+are read in the same manner as normal files.
+Other possible values are `skip' to silently ignore the
+directories, and `recurse' to read them recursively, which
+has the same effect as the
+.Fl R
+and
+.Fl r
+option.
+.It Fl E , Fl Fl extended-regexp
+Interpret
+.Ar pattern
+as an extended regular expression
+(i.e. force
+.Nm grep
+to behave as
+.Nm egrep ) .
+.It Fl e Ar pattern , Fl Fl regexp Ns = Ns Ar pattern
+Specify a pattern used during the search of the input:
+an input line is selected if it matches any of the specified patterns.
+This option is most useful when multiple
+.Fl e
+options are used to specify multiple patterns,
+or when a pattern begins with a dash
+.Pq Sq - .
+.It Fl Fl exclude
+If specified, it excludes files matching the given
+filename pattern from the search.
+Note that
+.Fl Fl exclude
+patterns take priority over
+.Fl Fl include
+patterns, and if no
+.Fl Fl include
+pattern is specified, all files are searched that are
+not excluded.
+Patterns are matched to the full path specified,
+not only to the filename component.
+.It Fl Fl exclude-dir
+If
+.Fl R
+is specified, it excludes directories matching the
+given filename pattern from the search.
+Note that
+.Fl Fl exclude-dir
+patterns take priority over
+.Fl Fl include-dir
+patterns, and if no
+.Fl Fl include-dir
+pattern is specified, all directories are searched that are
+not excluded.
+.It Fl F , Fl Fl fixed-strings
+Interpret
+.Ar pattern
+as a set of fixed strings
+(i.e. force
+.Nm grep
+to behave as
+.Nm fgrep ) .
+.It Fl f Ar file , Fl Fl file Ns = Ns Ar file
+Read one or more newline separated patterns from
+.Ar file .
+Empty pattern lines match every input line.
+Newlines are not considered part of a pattern.
+If
+.Ar file
+is empty, nothing is matched.
+.It Fl G , Fl Fl basic-regexp
+Interpret
+.Ar pattern
+as a basic regular expression
+(i.e. force
+.Nm grep
+to behave as traditional
+.Nm grep ) .
+.It Fl H
+Always print filename headers with output lines.
+.It Fl h , Fl Fl no-filename
+Never print filename headers
+.Pq i.e. filenames
+with output lines.
+.It Fl Fl help
+Print a brief help message.
+.It Fl I
+Ignore binary files.
+This option is equivalent to
+.Fl Fl binary-file Ns = Ns Ar without-match
+option.
+.It Fl i , Fl Fl ignore-case
+Perform case insensitive matching.
+By default,
+.Nm grep
+is case sensitive.
+.It Fl Fl include
+If specified, only files matching the
+given filename pattern are searched.
+Note that
+.Fl Fl exclude
+patterns take priority over
+.Fl Fl include
+patterns.
+Patterns are matched to the full path specified,
+not only to the filename component.
+.It Fl Fl include-dir
+If
+.Fl R
+is specified, only directories matching the
+given filename pattern are searched.
+Note that
+.Fl Fl exclude-dir
+patterns take priority over
+.Fl Fl include-dir
+patterns.
+.It Fl J, Fl Fl bz2decompress
+Decompress the
+.Xr bzip2 1
+compressed file before looking for the text.
+.It Fl L , Fl Fl files-without-match
+Only the names of files not containing selected lines are written to
+standard output.
+Pathnames are listed once per file searched.
+If the standard input is searched, the string
+.Dq (standard input)
+is written.
+.It Fl l , Fl Fl files-with-matches
+Only the names of files containing selected lines are written to
+standard output.
+.Nm grep
+will only search a file until a match has been found,
+making searches potentially less expensive.
+Pathnames are listed once per file searched.
+If the standard input is searched, the string
+.Dq (standard input)
+is written.
+.It Fl Fl mmap
+Use
+.Xr mmap 2
+instead of
+.Xr read 2
+to read input, which can result in better performance under some
+circumstances but can cause undefined behaviour.
+.It Fl m Ar num, Fl Fl max-count Ns = Ns Ar num
+Stop reading the file after
+.Ar num
+matches.
+.It Fl n , Fl Fl line-number
+Each output line is preceded by its relative line number in the file,
+starting at line 1.
+The line number counter is reset for each file processed.
+This option is ignored if
+.Fl c ,
+.Fl L ,
+.Fl l ,
+or
+.Fl q
+is
+specified.
+.It Fl O
+If
+.Fl R
+is specified, follow symbolic links only if they were explicitly listed
+on the command line.
+The default is not to follow symbolic links.
+.It Fl o, Fl Fl only-matching
+Prints only the matching part of the lines.
+.It Fl p
+If
+.Fl R
+is specified, no symbolic links are followed.
+This is the default.
+.It Fl q , Fl Fl quiet , Fl Fl silent
+Quiet mode:
+suppress normal output.
+.Nm grep
+will only search a file until a match has been found,
+making searches potentially less expensive.
+.It Fl R , Fl r , Fl Fl recursive
+Recursively search subdirectories listed.
+.It Fl S
+If
+.Fl R
+is specified, all symbolic links are followed.
+The default is not to follow symbolic links.
+.It Fl s , Fl Fl no-messages
+Silent mode.
+Nonexistent and unreadable files are ignored
+(i.e. their error messages are suppressed).
+.It Fl U , Fl Fl binary
+Search binary files, but do not attempt to print them.
+.It Fl V , Fl Fl version
+Display version information and exit.
+.It Fl v , Fl Fl invert-match
+Selected lines are those
+.Em not
+matching any of the specified patterns.
+.It Fl w , Fl Fl word-regexp
+The expression is searched for as a word (as if surrounded by
+.Sq [[:<:]]
+and
+.Sq [[:>:]] ;
+see
+.Xr re_format 7 ) .
+.It Fl x , Fl Fl line-regexp
+Only input lines selected against an entire fixed string or regular
+expression are considered to be matching lines.
+.It Fl y
+Equivalent to
+.Fl i .
+Obsoleted.
+.It Fl Z , Fl Fl null
+Prints a zero-byte after the file name.
+.It Fl z , Fl Fl null-data
+Use the zero byte (ASCII NUL) as line separator.
+.It Fl Fl binary-files Ns = Ns Ar value
+Controls searching and printing of binary files.
+Options are
+.Ar binary ,
+the default: search binary files but do not print them;
+.Ar without-match :
+do not search binary files;
+and
+.Ar text :
+treat all files as text.
+.It Fl Fl decompress
+Detect input files compressed with
+.Xr bzip2 1
+or
+.Xr gzip 1
+and decompress them dynamically.
+This makes
+.Nm grep
+behave like
+.Nm zgrep .
+.It Fl Fl line-buffered
+Force output to be line buffered.
+By default, output is line buffered when standard output is a terminal
+and block buffered otherwise.
+.Pp
+.El
+If no file arguments are specified, the standard input is used.
+.Sh EXIT STATUS
+The
+.Nm grep
+utility exits with one of the following values:
+.Pp
+.Bl -tag -width flag -compact
+.It Li 0
+One or more lines were selected.
+.It Li 1
+No lines were selected.
+.It Li \*(Gt1
+An error occurred.
+.El
+.Sh EXAMPLES
+To find all occurrences of the word
+.Sq patricia
+in a file:
+.Pp
+.Dl $ grep 'patricia' myfile
+.Pp
+To find all occurrences of the pattern
+.Ql .Pp
+at the beginning of a line:
+.Pp
+.Dl $ grep '^\e.Pp' myfile
+.Pp
+The apostrophes ensure the entire expression is evaluated by
+.Nm grep
+instead of by the user's shell.
+The caret
+.Ql ^
+matches the null string at the beginning of a line,
+and the
+.Ql \e
+escapes the
+.Ql \&. ,
+which would otherwise match any character.
+.Pp
+To find all lines in a file which do not contain the words
+.Sq foo
+or
+.Sq bar :
+.Pp
+.Dl $ grep -v -e 'foo' -e 'bar' myfile
+.Pp
+A simple example of an extended regular expression:
+.Pp
+.Dl $ egrep '19|20|25' calendar
+.Pp
+Peruses the file
+.Sq calendar
+looking for either 19, 20, or 25.
+.Sh SEE ALSO
+.Xr ed 1 ,
+.Xr ex 1 ,
+.Xr gzip 1 ,
+.Xr sed 1 ,
+.Xr re_format 7
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
+.Pp
+The flags
+.Op Fl AaBbCDdGHhIJLmoPRSUVwZ
+are extensions to that specification, and the behaviour of the
+.Fl f
+flag when used with an empty pattern file is left undefined.
+.Pp
+All long options are provided for compatibility with
+GNU versions of this utility.
+.Pp
+Historic versions of the
+.Nm grep
+utility also supported the flags
+.Op Fl ruy .
+This implementation supports those options;
+however, their use is strongly discouraged.
+.Sh HISTORY
+The
+.Nm grep
+command first appeared in
+.At v6 .
diff --git a/usr.bin/grep/grep.c b/usr.bin/grep/grep.c
new file mode 100644
index 0000000..bad2a73
--- /dev/null
+++ b/usr.bin/grep/grep.c
@@ -0,0 +1,722 @@
+/* $NetBSD: grep.c,v 1.15 2018/08/12 09:03:21 christos Exp $ */
+/* $FreeBSD: head/usr.bin/grep/grep.c 211519 2010-08-19 22:55:17Z delphij $ */
+/* $OpenBSD: grep.c,v 1.42 2010/07/02 22:18:03 tedu Exp $ */
+
+/*-
+ * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav
+ * Copyright (C) 2008-2009 Gabor Kovesdan <gabor@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: grep.c,v 1.15 2018/08/12 09:03:21 christos Exp $");
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <libgen.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "grep.h"
+
+#ifndef WITHOUT_NLS
+#include <nl_types.h>
+nl_catd catalog;
+#endif
+
+/*
+ * Default messags to use when NLS is disabled or no catalogue
+ * is found.
+ */
+const char *errstr[] = {
+ "",
+/* 1*/ "(standard input)",
+/* 2*/ "cannot read bzip2 compressed file",
+/* 3*/ "unknown %s option",
+/* 4*/ "usage: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A num] [-B num] [-C[num]]\n",
+/* 5*/ "\t[-e pattern] [-f file] [--binary-files=value] [--color=when]\n",
+/* 6*/ "\t[--context[=num]] [--directories=action] [--label] [--line-buffered]\n",
+/* 7*/ "\t[pattern] [file ...]\n",
+/* 8*/ "Binary file %s matches\n",
+/* 9*/ "%s (BSD grep) %s\n",
+};
+
+/* Flags passed to regcomp() and regexec() */
+int cflags = 0;
+int eflags = REG_STARTEND;
+
+/* Searching patterns */
+unsigned int patterns, pattern_sz;
+char **pattern;
+regex_t *r_pattern;
+fastgrep_t *fg_pattern;
+
+/* Filename exclusion/inclusion patterns */
+unsigned int fpatterns, fpattern_sz;
+unsigned int dpatterns, dpattern_sz;
+struct epat *dpattern, *fpattern;
+
+/* For regex errors */
+char re_error[RE_ERROR_BUF + 1];
+
+/* Command-line flags */
+unsigned long long Aflag; /* -A x: print x lines trailing each match */
+unsigned long long Bflag; /* -B x: print x lines leading each match */
+bool Hflag; /* -H: always print file name */
+bool Lflag; /* -L: only show names of files with no matches */
+bool bflag; /* -b: show block numbers for each match */
+bool cflag; /* -c: only show a count of matching lines */
+bool hflag; /* -h: don't print filename headers */
+bool iflag; /* -i: ignore case */
+bool lflag; /* -l: only show names of files with matches */
+bool mflag; /* -m x: stop reading the files after x matches */
+unsigned long long mcount; /* count for -m */
+bool nflag; /* -n: show line numbers in front of matching lines */
+bool oflag; /* -o: print only matching part */
+bool qflag; /* -q: quiet mode (don't output anything) */
+bool sflag; /* -s: silent mode (ignore errors) */
+bool vflag; /* -v: only show non-matching lines */
+bool wflag; /* -w: pattern must start and end on word boundaries */
+bool xflag; /* -x: pattern must match entire line */
+bool lbflag; /* --line-buffered */
+bool nullflag; /* --null */
+bool nulldataflag; /* --null-data */
+unsigned char line_sep = '\n'; /* 0 for --null-data */
+char *label; /* --label */
+const char *color; /* --color */
+int grepbehave = GREP_BASIC; /* -EFGP: type of the regex */
+int binbehave = BINFILE_BIN; /* -aIU: handling of binary files */
+int filebehave = FILE_STDIO; /* -JZ: normal, gzip or bzip2 file */
+int devbehave = DEV_READ; /* -D: handling of devices */
+int dirbehave = DIR_READ; /* -dRr: handling of directories */
+int linkbehave = LINK_READ; /* -OpS: handling of symlinks */
+
+bool dexclude, dinclude; /* --exclude-dir and --include-dir */
+bool fexclude, finclude; /* --exclude and --include */
+
+enum {
+ BIN_OPT = CHAR_MAX + 1,
+ COLOR_OPT,
+ DECOMPRESS_OPT,
+ HELP_OPT,
+ MMAP_OPT,
+ LINEBUF_OPT,
+ LABEL_OPT,
+ R_EXCLUDE_OPT,
+ R_INCLUDE_OPT,
+ R_DEXCLUDE_OPT,
+ R_DINCLUDE_OPT
+};
+
+static inline const char *init_color(const char *);
+
+/* Housekeeping */
+int tail; /* lines left to print */
+bool notfound; /* file not found */
+
+extern char *__progname;
+
+/*
+ * Prints usage information and returns 2.
+ */
+__dead static void
+usage(void)
+{
+ fprintf(stderr, getstr(4), __progname);
+ fprintf(stderr, "%s", getstr(5));
+ fprintf(stderr, "%s", getstr(6));
+ fprintf(stderr, "%s", getstr(7));
+ exit(2);
+}
+
+static const char optstr[] =
+ "0123456789A:B:C:D:EFGHIJLOPSRUVZabcd:e:f:hilm:nopqrsuvwxyz";
+
+struct option long_options[] =
+{
+ {"binary-files", required_argument, NULL, BIN_OPT},
+#ifndef WITHOUT_GZIP
+ {"decompress", no_argument, NULL, DECOMPRESS_OPT},
+#endif
+ {"help", no_argument, NULL, HELP_OPT},
+ {"mmap", no_argument, NULL, MMAP_OPT},
+ {"line-buffered", no_argument, NULL, LINEBUF_OPT},
+ {"label", required_argument, NULL, LABEL_OPT},
+ {"color", optional_argument, NULL, COLOR_OPT},
+ {"colour", optional_argument, NULL, COLOR_OPT},
+ {"exclude", required_argument, NULL, R_EXCLUDE_OPT},
+ {"include", required_argument, NULL, R_INCLUDE_OPT},
+ {"exclude-dir", required_argument, NULL, R_DEXCLUDE_OPT},
+ {"include-dir", required_argument, NULL, R_DINCLUDE_OPT},
+ {"after-context", required_argument, NULL, 'A'},
+ {"text", no_argument, NULL, 'a'},
+ {"before-context", required_argument, NULL, 'B'},
+ {"byte-offset", no_argument, NULL, 'b'},
+ {"context", optional_argument, NULL, 'C'},
+ {"count", no_argument, NULL, 'c'},
+ {"devices", required_argument, NULL, 'D'},
+ {"directories", required_argument, NULL, 'd'},
+ {"extended-regexp", no_argument, NULL, 'E'},
+ {"regexp", required_argument, NULL, 'e'},
+ {"fixed-strings", no_argument, NULL, 'F'},
+ {"file", required_argument, NULL, 'f'},
+ {"basic-regexp", no_argument, NULL, 'G'},
+ {"no-filename", no_argument, NULL, 'h'},
+ {"with-filename", no_argument, NULL, 'H'},
+ {"ignore-case", no_argument, NULL, 'i'},
+#ifndef WITHOUT_BZ2
+ {"bz2decompress", no_argument, NULL, 'J'},
+#endif
+ {"files-with-matches", no_argument, NULL, 'l'},
+ {"files-without-match", no_argument, NULL, 'L'},
+ {"max-count", required_argument, NULL, 'm'},
+ {"line-number", no_argument, NULL, 'n'},
+ {"only-matching", no_argument, NULL, 'o'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"silent", no_argument, NULL, 'q'},
+ {"recursive", no_argument, NULL, 'r'},
+ {"no-messages", no_argument, NULL, 's'},
+ {"binary", no_argument, NULL, 'U'},
+ {"unix-byte-offsets", no_argument, NULL, 'u'},
+ {"invert-match", no_argument, NULL, 'v'},
+ {"version", no_argument, NULL, 'V'},
+ {"word-regexp", no_argument, NULL, 'w'},
+ {"line-regexp", no_argument, NULL, 'x'},
+ {"null", no_argument, NULL, 'Z'},
+ {"null-data", no_argument, NULL, 'z'},
+ {NULL, no_argument, NULL, 0}
+};
+
+/*
+ * Adds a searching pattern to the internal array.
+ */
+static void
+add_pattern(char *pat, size_t len)
+{
+
+ /* TODO: Check for empty patterns and shortcut */
+
+ /* Increase size if necessary */
+ if (patterns == pattern_sz) {
+ pattern_sz *= 2;
+ pattern = grep_realloc(pattern, ++pattern_sz *
+ sizeof(*pattern));
+ }
+ if (len > 0 && pat[len - 1] == '\n')
+ --len;
+ /* pat may not be NUL-terminated */
+ pattern[patterns] = grep_malloc(len + 1);
+ memcpy(pattern[patterns], pat, len);
+ pattern[patterns][len] = '\0';
+ ++patterns;
+}
+
+/*
+ * Adds a file include/exclude pattern to the internal array.
+ */
+static void
+add_fpattern(const char *pat, int mode)
+{
+
+ /* Increase size if necessary */
+ if (fpatterns == fpattern_sz) {
+ fpattern_sz *= 2;
+ fpattern = grep_realloc(fpattern, ++fpattern_sz *
+ sizeof(struct epat));
+ }
+ fpattern[fpatterns].pat = grep_strdup(pat);
+ fpattern[fpatterns].mode = mode;
+ ++fpatterns;
+}
+
+/*
+ * Adds a directory include/exclude pattern to the internal array.
+ */
+static void
+add_dpattern(const char *pat, int mode)
+{
+
+ /* Increase size if necessary */
+ if (dpatterns == dpattern_sz) {
+ dpattern_sz *= 2;
+ dpattern = grep_realloc(dpattern, ++dpattern_sz *
+ sizeof(struct epat));
+ }
+ dpattern[dpatterns].pat = grep_strdup(pat);
+ dpattern[dpatterns].mode = mode;
+ ++dpatterns;
+}
+
+/*
+ * Reads searching patterns from a file and adds them with add_pattern().
+ */
+static void
+read_patterns(const char *fn)
+{
+ FILE *f;
+ char *line;
+ size_t len;
+ ssize_t rlen;
+
+ if ((f = fopen(fn, "r")) == NULL)
+ err(2, "%s", fn);
+ line = NULL;
+ len = 0;
+ while ((rlen = getline(&line, &len, f)) != -1)
+ add_pattern(line, *line == '\n' ? 0 : (size_t)rlen);
+ free(line);
+ if (ferror(f))
+ err(2, "%s", fn);
+ fclose(f);
+}
+
+static inline const char *
+init_color(const char *d)
+{
+ char *c;
+
+ c = getenv("GREP_COLOR");
+ return (c != NULL ? c : d);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char **aargv, **eargv, *eopts;
+ char *ep;
+ unsigned long long l;
+ unsigned int aargc, eargc, i, j;
+ int c, lastc, needpattern, newarg, prevoptind;
+
+ setlocale(LC_ALL, "");
+
+#ifndef WITHOUT_NLS
+ catalog = catopen("grep", NL_CAT_LOCALE);
+#endif
+
+ /* Check what is the program name of the binary. In this
+ way we can have all the funcionalities in one binary
+ without the need of scripting and using ugly hacks. */
+ switch (__progname[0]) {
+ case 'e':
+ grepbehave = GREP_EXTENDED;
+ break;
+ case 'f':
+ grepbehave = GREP_FIXED;
+ break;
+ case 'g':
+ grepbehave = GREP_BASIC;
+ break;
+#ifndef WITHOUT_GZIP
+ case 'z':
+ filebehave = FILE_GZIP;
+ switch(__progname[1]) {
+ case 'e':
+ grepbehave = GREP_EXTENDED;
+ break;
+ case 'f':
+ grepbehave = GREP_FIXED;
+ break;
+ case 'g':
+ grepbehave = GREP_BASIC;
+ break;
+ }
+ break;
+#endif
+ }
+
+ lastc = '\0';
+ newarg = 1;
+ prevoptind = 1;
+ needpattern = 1;
+
+ eopts = getenv("GREP_OPTIONS");
+
+ /* support for extra arguments in GREP_OPTIONS */
+ eargc = 0;
+ if (eopts != NULL) {
+ char *str;
+
+ /* make an estimation of how many extra arguments we have */
+ for (j = 0; j < strlen(eopts); j++)
+ if (eopts[j] == ' ')
+ eargc++;
+
+ eargv = (char **)grep_malloc(sizeof(char *) * (eargc + 1));
+
+ eargc = 0;
+ /* parse extra arguments */
+ while ((str = strsep(&eopts, " ")) != NULL)
+ eargv[eargc++] = grep_strdup(str);
+
+ aargv = (char **)grep_calloc(eargc + argc + 1,
+ sizeof(char *));
+
+ aargv[0] = argv[0];
+ for (i = 0; i < eargc; i++)
+ aargv[i + 1] = eargv[i];
+ for (j = 1; j < (unsigned int)argc; j++, i++)
+ aargv[i + 1] = argv[j];
+
+ aargc = eargc + argc;
+ } else {
+ aargv = argv;
+ aargc = argc;
+ }
+
+ while (((c = getopt_long(aargc, aargv, optstr, long_options, NULL)) !=
+ -1)) {
+ switch (c) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if (newarg || !isdigit(lastc))
+ Aflag = 0;
+ else if (Aflag > LLONG_MAX / 10) {
+ errno = ERANGE;
+ err(2, NULL);
+ }
+ Aflag = Bflag = (Aflag * 10) + (c - '0');
+ break;
+ case 'C':
+ if (optarg == NULL) {
+ Aflag = Bflag = 2;
+ break;
+ }
+ /* FALLTHROUGH */
+ case 'A':
+ /* FALLTHROUGH */
+ case 'B':
+ errno = 0;
+ l = strtoull(optarg, &ep, 10);
+ if (((errno == ERANGE) && (l == ULLONG_MAX)) ||
+ ((errno == EINVAL) && (l == 0)))
+ err(2, NULL);
+ else if (ep[0] != '\0') {
+ errno = EINVAL;
+ err(2, NULL);
+ }
+ if (c == 'A')
+ Aflag = l;
+ else if (c == 'B')
+ Bflag = l;
+ else
+ Aflag = Bflag = l;
+ break;
+ case 'a':
+ binbehave = BINFILE_TEXT;
+ break;
+ case 'b':
+ bflag = true;
+ break;
+ case 'c':
+ cflag = true;
+ break;
+ case 'D':
+ if (strcasecmp(optarg, "skip") == 0)
+ devbehave = DEV_SKIP;
+ else if (strcasecmp(optarg, "read") == 0)
+ devbehave = DEV_READ;
+ else
+ errx(2, getstr(3), "--devices");
+ break;
+ case 'd':
+ if (strcasecmp("recurse", optarg) == 0) {
+ Hflag = true;
+ dirbehave = DIR_RECURSE;
+ } else if (strcasecmp("skip", optarg) == 0)
+ dirbehave = DIR_SKIP;
+ else if (strcasecmp("read", optarg) == 0)
+ dirbehave = DIR_READ;
+ else
+ errx(2, getstr(3), "--directories");
+ break;
+ case 'E':
+ grepbehave = GREP_EXTENDED;
+ break;
+ case 'e':
+ add_pattern(optarg, strlen(optarg));
+ needpattern = 0;
+ break;
+ case 'F':
+ grepbehave = GREP_FIXED;
+ break;
+ case 'f':
+ read_patterns(optarg);
+ needpattern = 0;
+ break;
+ case 'G':
+ grepbehave = GREP_BASIC;
+ break;
+ case 'H':
+ Hflag = true;
+ break;
+ case 'h':
+ Hflag = false;
+ hflag = true;
+ break;
+ case 'I':
+ binbehave = BINFILE_SKIP;
+ break;
+ case 'i':
+ case 'y':
+ iflag = true;
+ cflags |= REG_ICASE;
+ break;
+#ifndef WITHOUT_BZ2
+ case 'J':
+ filebehave = FILE_BZIP;
+ break;
+#endif
+ case 'L':
+ lflag = false;
+ Lflag = true;
+ break;
+ case 'l':
+ Lflag = false;
+ lflag = true;
+ break;
+ case 'm':
+ mflag = true;
+ errno = 0;
+ mcount = strtoull(optarg, &ep, 10);
+ if (((errno == ERANGE) && (mcount == ULLONG_MAX)) ||
+ ((errno == EINVAL) && (mcount == 0)))
+ err(2, NULL);
+ else if (ep[0] != '\0') {
+ errno = EINVAL;
+ err(2, NULL);
+ }
+ break;
+ case 'n':
+ nflag = true;
+ break;
+ case 'O':
+ linkbehave = LINK_EXPLICIT;
+ break;
+ case 'o':
+ oflag = true;
+ break;
+ case 'p':
+ linkbehave = LINK_SKIP;
+ break;
+ case 'q':
+ qflag = true;
+ break;
+ case 'S':
+ linkbehave = LINK_READ;
+ break;
+ case 'R':
+ case 'r':
+ dirbehave = DIR_RECURSE;
+ Hflag = true;
+ break;
+ case 's':
+ sflag = true;
+ break;
+ case 'U':
+ binbehave = BINFILE_BIN;
+ break;
+ case 'u':
+ case MMAP_OPT:
+ /* noop, compatibility */
+ break;
+ case 'V':
+ printf(getstr(9), __progname, VERSION);
+ exit(0);
+ case 'v':
+ vflag = true;
+ break;
+ case 'w':
+ wflag = true;
+ break;
+ case 'x':
+ xflag = true;
+ break;
+ case 'Z':
+ nullflag = true;
+ break;
+ case 'z':
+ nulldataflag = true;
+ line_sep = '\0';
+ break;
+ case BIN_OPT:
+ if (strcasecmp("binary", optarg) == 0)
+ binbehave = BINFILE_BIN;
+ else if (strcasecmp("without-match", optarg) == 0)
+ binbehave = BINFILE_SKIP;
+ else if (strcasecmp("text", optarg) == 0)
+ binbehave = BINFILE_TEXT;
+ else
+ errx(2, getstr(3), "--binary-files");
+ break;
+ case COLOR_OPT:
+ color = NULL;
+ if (optarg == NULL || strcasecmp("auto", optarg) == 0 ||
+ strcasecmp("tty", optarg) == 0 ||
+ strcasecmp("if-tty", optarg) == 0) {
+ char *term;
+
+ term = getenv("TERM");
+ if (isatty(STDOUT_FILENO) && term != NULL &&
+ strcasecmp(term, "dumb") != 0)
+ color = init_color("01;31");
+ } else if (strcasecmp("always", optarg) == 0 ||
+ strcasecmp("yes", optarg) == 0 ||
+ strcasecmp("force", optarg) == 0) {
+ color = init_color("01;31");
+ } else if (strcasecmp("never", optarg) != 0 &&
+ strcasecmp("none", optarg) != 0 &&
+ strcasecmp("no", optarg) != 0)
+ errx(2, getstr(3), "--color");
+ break;
+#ifndef WITHOUT_GZIP
+ case DECOMPRESS_OPT:
+ filebehave = FILE_GZIP;
+ break;
+#endif
+ case LABEL_OPT:
+ label = optarg;
+ break;
+ case LINEBUF_OPT:
+ lbflag = true;
+ break;
+ case R_INCLUDE_OPT:
+ finclude = true;
+ add_fpattern(optarg, INCL_PAT);
+ break;
+ case R_EXCLUDE_OPT:
+ fexclude = true;
+ add_fpattern(optarg, EXCL_PAT);
+ break;
+ case R_DINCLUDE_OPT:
+ dinclude = true;
+ add_dpattern(optarg, INCL_PAT);
+ break;
+ case R_DEXCLUDE_OPT:
+ dexclude = true;
+ add_dpattern(optarg, EXCL_PAT);
+ break;
+ case HELP_OPT:
+ default:
+ usage();
+ }
+ lastc = c;
+ newarg = optind != prevoptind;
+ prevoptind = optind;
+ }
+ aargc -= optind;
+ aargv += optind;
+
+ /* Fail if we don't have any pattern */
+ if (aargc == 0 && needpattern)
+ usage();
+
+ /* Process patterns from command line */
+ if (aargc != 0 && needpattern) {
+ add_pattern(*aargv, strlen(*aargv));
+ --aargc;
+ ++aargv;
+ }
+
+ switch (grepbehave) {
+ case GREP_FIXED:
+ case GREP_BASIC:
+ break;
+ case GREP_EXTENDED:
+ cflags |= REG_EXTENDED;
+ break;
+ default:
+ /* NOTREACHED */
+ usage();
+ }
+
+ fg_pattern = grep_calloc(patterns, sizeof(*fg_pattern));
+ r_pattern = grep_calloc(patterns, sizeof(*r_pattern));
+/*
+ * XXX: fgrepcomp() and fastcomp() are workarounds for regexec() performance.
+ * Optimizations should be done there.
+ */
+ /* Check if cheating is allowed (always is for fgrep). */
+ if (grepbehave == GREP_FIXED) {
+ for (i = 0; i < patterns; ++i)
+ fgrepcomp(&fg_pattern[i], pattern[i]);
+ } else {
+ for (i = 0; i < patterns; ++i) {
+ if (fastcomp(&fg_pattern[i], pattern[i])) {
+ /* Fall back to full regex library */
+ c = regcomp(&r_pattern[i], pattern[i], cflags);
+ if (c != 0) {
+ regerror(c, &r_pattern[i], re_error,
+ RE_ERROR_BUF);
+ errx(2, "%s", re_error);
+ }
+ }
+ }
+ }
+
+ if (lbflag) {
+#ifdef _IOLBF
+ setvbuf(stdout, NULL, _IOLBF, 0);
+#else
+ setlinebuf(stdout);
+#endif
+ }
+
+ if ((aargc == 0 || aargc == 1) && !Hflag)
+ hflag = true;
+
+ if (aargc == 0)
+ exit(!procfile("-"));
+
+ if (dirbehave == DIR_RECURSE)
+ c = grep_tree(aargv);
+ else
+ for (c = 0; aargc--; ++aargv) {
+ if ((finclude || fexclude) && !file_matching(*aargv))
+ continue;
+ c+= procfile(*aargv);
+ }
+
+#ifndef WITHOUT_NLS
+ catclose(catalog);
+#endif
+
+ /* Find out the correct return value according to the
+ results and the command line option. */
+ exit(c ? (notfound ? (qflag ? 0 : 2) : 0) : (notfound ? 2 : 1));
+}
diff --git a/usr.bin/grep/grep.h b/usr.bin/grep/grep.h
new file mode 100644
index 0000000..b7ef7fa
--- /dev/null
+++ b/usr.bin/grep/grep.h
@@ -0,0 +1,162 @@
+/* $NetBSD: grep.h,v 1.10 2018/08/12 09:03:21 christos Exp $ */
+/* $OpenBSD: grep.h,v 1.15 2010/04/05 03:03:55 tedu Exp $ */
+/* $FreeBSD: head/usr.bin/grep/grep.h 211496 2010-08-19 09:28:59Z des $ */
+
+/*-
+ * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav
+ * Copyright (c) 2008-2009 Gabor Kovesdan <gabor@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef WITHOUT_BZ2
+#include <bzlib.h>
+#endif
+#include <limits.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdio.h>
+#ifndef WITHOUT_GZIP
+#include <zlib.h>
+#endif
+
+#ifdef WITHOUT_NLS
+#define getstr(n) errstr[n]
+#else
+#include <nl_types.h>
+
+extern nl_catd catalog;
+#define getstr(n) catgets(catalog, 1, n, errstr[n])
+#endif
+
+extern const char *errstr[];
+
+#define VERSION "2.5.1-FreeBSD"
+
+#define GREP_FIXED 0
+#define GREP_BASIC 1
+#define GREP_EXTENDED 2
+
+#define BINFILE_BIN 0
+#define BINFILE_SKIP 1
+#define BINFILE_TEXT 2
+
+#define FILE_STDIO 0
+#define FILE_GZIP 1
+#define FILE_BZIP 2
+
+#define DIR_READ 0
+#define DIR_SKIP 1
+#define DIR_RECURSE 2
+
+#define DEV_READ 0
+#define DEV_SKIP 1
+
+#define LINK_READ 0
+#define LINK_EXPLICIT 1
+#define LINK_SKIP 2
+
+#define EXCL_PAT 0
+#define INCL_PAT 1
+
+#define MAX_LINE_MATCHES 32
+
+struct file {
+ int fd;
+ bool binary;
+};
+
+struct str {
+ off_t off;
+ size_t len;
+ char *dat;
+ char *file;
+ int line_no;
+};
+
+struct epat {
+ char *pat;
+ int mode;
+};
+
+typedef struct {
+ size_t len;
+ unsigned char *pattern;
+ int qsBc[UCHAR_MAX + 1];
+ /* flags */
+ bool bol;
+ bool eol;
+ bool reversed;
+ bool word;
+} fastgrep_t;
+
+/* Flags passed to regcomp() and regexec() */
+extern int cflags, eflags;
+
+/* Command line flags */
+extern bool Eflag, Fflag, Gflag, Hflag, Lflag,
+ bflag, cflag, hflag, iflag, lflag, mflag, nflag, oflag,
+ qflag, sflag, vflag, wflag, xflag;
+extern bool dexclude, dinclude, fexclude, finclude, lbflag, nullflag, nulldataflag;
+extern unsigned char line_sep;
+extern unsigned long long Aflag, Bflag, mcount;
+extern char *label;
+extern const char *color;
+extern int binbehave, devbehave, dirbehave, filebehave, grepbehave, linkbehave;
+
+extern bool notfound;
+extern int tail;
+extern unsigned int dpatterns, fpatterns, patterns;
+extern char **pattern;
+extern struct epat *dpattern, *fpattern;
+extern regex_t *er_pattern, *r_pattern;
+extern fastgrep_t *fg_pattern;
+
+/* For regex errors */
+#define RE_ERROR_BUF 512
+extern char re_error[RE_ERROR_BUF + 1]; /* Seems big enough */
+
+/* util.c */
+bool file_matching(const char *fname);
+int procfile(const char *fn);
+int grep_tree(char **argv);
+void *grep_malloc(size_t size);
+void *grep_calloc(size_t nmemb, size_t size);
+void *grep_realloc(void *ptr, size_t size);
+char *grep_strdup(const char *str);
+void printline(struct str *line, int sep, regmatch_t *matches, int m);
+
+/* queue.c */
+void enqueue(struct str *x);
+void printqueue(void);
+void clearqueue(void);
+
+/* file.c */
+void grep_close(struct file *f);
+struct file *grep_open(const char *path);
+char *grep_fgetln(struct file *f, size_t *len);
+
+/* fastgrep.c */
+int fastcomp(fastgrep_t *, const char *);
+void fgrepcomp(fastgrep_t *, const char *);
+int grep_search(fastgrep_t *, const unsigned char *, size_t, regmatch_t *);
diff --git a/usr.bin/grep/nls/C.msg b/usr.bin/grep/nls/C.msg
new file mode 100644
index 0000000..d07aea6
--- /dev/null
+++ b/usr.bin/grep/nls/C.msg
@@ -0,0 +1,13 @@
+$ $FreeBSD: head/usr.bin/grep/nls/C.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(standard input)"
+2 "cannot read bzip2 compressed file"
+3 "unknown %s option"
+4 "usage: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A num] [-B num] [-C[num]]\n"
+5 "\t[-e pattern] [-f file] [--binary-files=value] [--color=when]\n"
+6 "\t[--context[=num]] [--directories=action] [--label] [--line-buffered]\n"
+7 "\t[pattern] [file ...]\n"
+8 "Binary file %s matches\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/es_ES.ISO8859-1.msg b/usr.bin/grep/nls/es_ES.ISO8859-1.msg
new file mode 100644
index 0000000..441ba01
--- /dev/null
+++ b/usr.bin/grep/nls/es_ES.ISO8859-1.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: es_ES.ISO8859-1.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/es_ES.ISO8859-1.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(entrada estándar)"
+2 "no se puede leer el fichero comprimido bzip2"
+3 "opción desconocida de %s"
+4 "uso: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A no] [-B no] [-C[no]]\n"
+5 "\t[-e pauta] [-f fichero] [--binary-files=valor] [--color=cuando]\n"
+6 "\t[--context[=no]] [--directories=acción] [--label] [--line-buffered]\n"
+7 "\t[pauta] [fichero ...]\n"
+8 "fichero binario %s se ajusta\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/gl_ES.ISO8859-1.msg b/usr.bin/grep/nls/gl_ES.ISO8859-1.msg
new file mode 100644
index 0000000..ab50cea
--- /dev/null
+++ b/usr.bin/grep/nls/gl_ES.ISO8859-1.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: gl_ES.ISO8859-1.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/gl_ES.ISO8859-1.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(entrada estándar)"
+2 "non se pode ler o ficheiro comprimido bzip2"
+3 "opción descoñecida de %s"
+4 "uso: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A no] [-B no] [-C[no]]\n"
+5 "\t[-e pauta] [-f ficheiro] [--binary-files=valor] [--color=cando]\n"
+6 "\t[--context[=no]] [--directories=acción] [--label] [--line-buffered]\n"
+7 "\t[pauta] [ficheiro ...]\n"
+8 "ficheiro binario %s conforma\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/hu_HU.ISO8859-2.msg b/usr.bin/grep/nls/hu_HU.ISO8859-2.msg
new file mode 100644
index 0000000..69dc2ec
--- /dev/null
+++ b/usr.bin/grep/nls/hu_HU.ISO8859-2.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: hu_HU.ISO8859-2.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/hu_HU.ISO8859-2.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(szabványos bemenet)"
+2 "bzip2 tömörített fájl nem olvasható"
+3 "ismeretlen %s opció"
+4 "használat: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A szám] [-B szám] [-C[szám]]\n"
+5 "\t[-e minta] [-f fájl] [--binary-files=érték] [--color=mikor]\n"
+6 "\t[--context[=szám]] [--directories=mûvelet] [--label] [--line-buffered]\n"
+7 "\t[minta] [fájl ...]\n"
+8 "%s bináris fájl illeszkedik\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/ja_JP.SJIS.msg b/usr.bin/grep/nls/ja_JP.SJIS.msg
new file mode 100644
index 0000000..99357b6
--- /dev/null
+++ b/usr.bin/grep/nls/ja_JP.SJIS.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: ja_JP.SJIS.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/ja_JP.SJIS.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(•W€“ü—Í)"
+2 "bzip2 ˆ³kƒtƒ@ƒCƒ‹‚ð“Ç‚Ýž‚Þ‚±‚Æ‚ª‚Å‚«‚Ü‚¹‚ñ"
+3 "%s ƒIƒvƒVƒ‡ƒ“‚ÌŽw’è’l‚ÉŒë‚肪‚ ‚è‚Ü‚·"
+4 "Žg‚¢•û: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A ”Žš] [-B ”Žš] [-C[”Žš]]\n"
+5 "\t[-e ƒpƒ^[ƒ“] [-f ƒtƒ@ƒCƒ‹–¼] [--binary-files=’l] [--color=’l]\n"
+6 "\t[--context[=”Žš]] [--directories=“®ì] [--label] [--line-buffered]\n"
+7 "\t[ƒpƒ^[ƒ“] [ƒtƒ@ƒCƒ‹–¼ ...]\n"
+8 "ƒoƒCƒiƒŠƒtƒ@ƒCƒ‹ %s ‚Ƀ}ƒbƒ`‚µ‚Ü‚µ‚½\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/ja_JP.UTF-8.msg b/usr.bin/grep/nls/ja_JP.UTF-8.msg
new file mode 100644
index 0000000..3d4aed3
--- /dev/null
+++ b/usr.bin/grep/nls/ja_JP.UTF-8.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: ja_JP.UTF-8.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/ja_JP.UTF-8.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(標準入力)"
+2 "bzip2 圧縮ファイルを読ã¿è¾¼ã‚€ã“ã¨ãŒã§ãã¾ã›ã‚“"
+3 "%s オプションã®æŒ‡å®šå€¤ã«èª¤ã‚ŠãŒã‚ã‚Šã¾ã™"
+4 "使ã„æ–¹: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A æ•°å­—] [-B æ•°å­—] [-C[æ•°å­—]]\n"
+5 "\t[-e パターン] [-f ファイルå] [--binary-files=値] [--color=値]\n"
+6 "\t[--context[=数字]] [--directories=動作] [--label] [--line-buffered]\n"
+7 "\t[パターン] [ファイルå ...]\n"
+8 "ãƒã‚¤ãƒŠãƒªãƒ•ã‚¡ã‚¤ãƒ« %s ã«ãƒžãƒƒãƒã—ã¾ã—ãŸ\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/ja_JP.eucJP.msg b/usr.bin/grep/nls/ja_JP.eucJP.msg
new file mode 100644
index 0000000..08e0e7d
--- /dev/null
+++ b/usr.bin/grep/nls/ja_JP.eucJP.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: ja_JP.eucJP.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/ja_JP.eucJP.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(ɸ½àÆþÎÏ)"
+2 "bzip2 °µ½Ì¥Õ¥¡¥¤¥ë¤òÆɤ߹þ¤à¤³¤È¤¬¤Ç¤­¤Þ¤»¤ó"
+3 "%s ¥ª¥×¥·¥ç¥ó¤Î»ØÄêÃͤ˸í¤ê¤¬¤¢¤ê¤Þ¤¹"
+4 "»È¤¤Êý: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A ¿ô»ú] [-B ¿ô»ú] [-C[¿ô»ú]]\n"
+5 "\t[-e ¥Ñ¥¿¡¼¥ó] [-f ¥Õ¥¡¥¤¥ë̾] [--binary-files=ÃÍ] [--color=ÃÍ]\n"
+6 "\t[--context[=¿ô»ú]] [--directories=Æ°ºî] [--label] [--line-buffered]\n"
+7 "\t[¥Ñ¥¿¡¼¥ó] [¥Õ¥¡¥¤¥ë̾ ...]\n"
+8 "¥Ð¥¤¥Ê¥ê¥Õ¥¡¥¤¥ë %s ¤Ë¥Þ¥Ã¥Á¤·¤Þ¤·¤¿\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/pt_BR.ISO8859-1.msg b/usr.bin/grep/nls/pt_BR.ISO8859-1.msg
new file mode 100644
index 0000000..7b6c20e
--- /dev/null
+++ b/usr.bin/grep/nls/pt_BR.ISO8859-1.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: pt_BR.ISO8859-1.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/pt_BR.ISO8859-1.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(entrada padrão)"
+2 "não se posso ler o fichero comprimido bzip2"
+3 "opcão não conhecida de %s"
+4 "uso: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A num] [-B num] [-C[num]]\n"
+5 "\t[-e padrão] [-f arquivo] [--binary-files=valor] [--color=quando]\n"
+6 "\t[--context[=num]] [--directories=ação] [--label] [--line-buffered]\n"
+7 "\t[padrão] [arquivo ...]\n"
+8 "arquivo binário %s casa com o padrão\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/ru_RU.KOI8-R.msg b/usr.bin/grep/nls/ru_RU.KOI8-R.msg
new file mode 100644
index 0000000..4c69f73
--- /dev/null
+++ b/usr.bin/grep/nls/ru_RU.KOI8-R.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: ru_RU.KOI8-R.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/ru_RU.KOI8-R.msg 210622 2010-07-29 18:02:57Z gabor $
+$
+$set 1
+$quote "
+1 "(ÓÔÁÎÄÁÒÔÎÙÊ ××ÏÄ)"
+2 "ÎÅ ÍÏÇÕ ÐÒÏÞÉÔÁÔØ ÓÖÁÔÙÊ × bzip2 ÆÁÊÌ"
+3 "ÎÅÉÚ×ÅÓÔÎÙÊ ËÌÀÞ %s"
+4 "ÉÓÐÏÌØÚÏ×ÁÎÉÅ: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A ÞÉÓ] [-B ÞÉÓ] [-C[ÞÉÓ]]\n"
+5 "\t[-e ÛÁÂÌÏÎ] [-f ÆÁÊÌ] [--binary-files=ÚÎÁÞÅÎÉÅ] [--color=ËÏÇÄÁ]\n"
+6 "\t[--context[=ÞÉÓ]] [--directories=ÄÅÊÓÔ×ÉÅ] [--label] [--line-buffered]\n"
+7 "\t[ÛÁÂÌÏÎ] [ÆÁÊÌ ...]\n"
+8 "Ä×ÏÉÞÎÙÊ ÆÁÊÌ %s ÓÏ×ÐÁÄÁÅÔ\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/uk_UA.UTF-8.msg b/usr.bin/grep/nls/uk_UA.UTF-8.msg
new file mode 100644
index 0000000..e6b9f54
--- /dev/null
+++ b/usr.bin/grep/nls/uk_UA.UTF-8.msg
@@ -0,0 +1,13 @@
+$ $NetBSD: uk_UA.UTF-8.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/uk_UA.UTF-8.msg 210927 2010-08-06 10:34:48Z gabor $
+$set 1
+$quote "
+1 "(Ñтандартний ввід)"
+2 "не можу прочитати ÑтиÑнутий bzip2 файл"
+3 "невiдома Ð¾Ð¿Ñ†Ñ–Ñ %s"
+4 "викориÑтаннÑ: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A чиÑ] [-B чиÑ] [-C[чиÑ]]\n"
+5 "\t[-e шаблон] [-f файл] [--binary-files=значеннÑ] [--color=коли]\n"
+6 "\t[--context[=чиÑ] [--directories=діÑ] [--label] [--line-buffered]\n"
+7 "\t[шаблон] [файл ...]\n"
+8 "двійковий файл %s Ñпівпадає\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/nls/zh_CN.UTF-8.msg b/usr.bin/grep/nls/zh_CN.UTF-8.msg
new file mode 100644
index 0000000..e9d40cd
--- /dev/null
+++ b/usr.bin/grep/nls/zh_CN.UTF-8.msg
@@ -0,0 +1,14 @@
+$ $NetBSD: zh_CN.UTF-8.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $
+$ $FreeBSD: head/usr.bin/grep/nls/zh_CN.UTF-8.msg 212927 2010-09-20 19:42:52Z delphij $
+$
+$set 1
+$quote "
+1 "(标准输入)"
+2 "è¯»å– bzip2 压缩文件时出错"
+3 "选项 %s 无法识别"
+4 "用法: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A 行数] [-B 行数] [-C[行数]]\n"
+5 "\t[-e 模å¼] [-f 文件] [--binary-files=值] [--color=何时]\n"
+6 "\t[--context[=行数]] [--directories=动作] [--label] [--line-buffered]\n"
+7 "\t[模å¼] [文件å ...]\n"
+8 "二进制文件 %s 包å«æ¨¡å¼\n"
+9 "%s (BSD grep) %s\n"
diff --git a/usr.bin/grep/queue.c b/usr.bin/grep/queue.c
new file mode 100644
index 0000000..e3c6be1
--- /dev/null
+++ b/usr.bin/grep/queue.c
@@ -0,0 +1,116 @@
+/* $NetBSD: queue.c,v 1.5 2011/08/31 16:24:57 plunky Exp $ */
+/* $FreeBSD: head/usr.bin/grep/queue.c 211496 2010-08-19 09:28:59Z des $ */
+/*-
+ * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * A really poor man's queue. It does only what it has to and gets out of
+ * Dodge. It is used in place of <sys/queue.h> to get a better performance.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: queue.c,v 1.5 2011/08/31 16:24:57 plunky Exp $");
+
+#include <sys/param.h>
+#include <sys/queue.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "grep.h"
+
+struct qentry {
+ STAILQ_ENTRY(qentry) list;
+ struct str data;
+};
+
+static STAILQ_HEAD(, qentry) queue = STAILQ_HEAD_INITIALIZER(queue);
+static unsigned long long count;
+
+static struct qentry *dequeue(void);
+
+void
+enqueue(struct str *x)
+{
+ struct qentry *item;
+
+ item = grep_malloc(sizeof(struct qentry));
+ item->data.dat = grep_malloc(sizeof(char) * x->len);
+ item->data.len = x->len;
+ item->data.line_no = x->line_no;
+ item->data.off = x->off;
+ memcpy(item->data.dat, x->dat, x->len);
+ item->data.file = x->file;
+
+ STAILQ_INSERT_TAIL(&queue, item, list);
+
+ if (++count > Bflag) {
+ item = dequeue();
+ free(item->data.dat);
+ free(item);
+ }
+}
+
+static struct qentry *
+dequeue(void)
+{
+ struct qentry *item;
+
+ item = STAILQ_FIRST(&queue);
+ if (item == NULL)
+ return (NULL);
+
+ STAILQ_REMOVE_HEAD(&queue, list);
+ --count;
+ return (item);
+}
+
+void
+printqueue(void)
+{
+ struct qentry *item;
+
+ while ((item = dequeue()) != NULL) {
+ printline(&item->data, '-', NULL, 0);
+ free(item->data.dat);
+ free(item);
+ }
+}
+
+void
+clearqueue(void)
+{
+ struct qentry *item;
+
+ while ((item = dequeue()) != NULL) {
+ free(item->data.dat);
+ free(item);
+ }
+}
diff --git a/usr.bin/grep/util.c b/usr.bin/grep/util.c
new file mode 100644
index 0000000..a3c9e4c
--- /dev/null
+++ b/usr.bin/grep/util.c
@@ -0,0 +1,500 @@
+/* $NetBSD: util.c,v 1.19 2018/02/05 22:14:26 mrg Exp $ */
+/* $FreeBSD: head/usr.bin/grep/util.c 211496 2010-08-19 09:28:59Z des $ */
+/* $OpenBSD: util.c,v 1.39 2010/07/02 22:18:03 tedu Exp $ */
+
+/*-
+ * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav
+ * Copyright (C) 2008-2010 Gabor Kovesdan <gabor@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: util.c,v 1.19 2018/02/05 22:14:26 mrg Exp $");
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <fts.h>
+#include <libgen.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include "grep.h"
+
+static bool first, first_global = true;
+static unsigned long long since_printed;
+
+static int procline(struct str *l, int);
+
+bool
+file_matching(const char *fname)
+{
+ char *fname_base, *fname_copy;
+ unsigned int i;
+ bool ret;
+
+ ret = finclude ? false : true;
+ fname_copy = grep_strdup(fname);
+ fname_base = basename(fname_copy);
+
+ for (i = 0; i < fpatterns; ++i) {
+ if (fnmatch(fpattern[i].pat, fname, 0) == 0 ||
+ fnmatch(fpattern[i].pat, fname_base, 0) == 0) {
+ if (fpattern[i].mode == EXCL_PAT) {
+ free(fname_copy);
+ return (false);
+ } else
+ ret = true;
+ }
+ }
+ free(fname_copy);
+ return (ret);
+}
+
+static inline bool
+dir_matching(const char *dname)
+{
+ unsigned int i;
+ bool ret;
+
+ ret = dinclude ? false : true;
+
+ for (i = 0; i < dpatterns; ++i) {
+ if (dname != NULL &&
+ fnmatch(dname, dpattern[i].pat, 0) == 0) {
+ if (dpattern[i].mode == EXCL_PAT)
+ return (false);
+ else
+ ret = true;
+ }
+ }
+ return (ret);
+}
+
+/*
+ * Processes a directory when a recursive search is performed with
+ * the -R option. Each appropriate file is passed to procfile().
+ */
+int
+grep_tree(char **argv)
+{
+ FTS *fts;
+ FTSENT *p;
+ char *d, *dir = NULL;
+ int c, fts_flags;
+ bool ok;
+
+ c = fts_flags = 0;
+
+ switch(linkbehave) {
+ case LINK_EXPLICIT:
+ fts_flags = FTS_COMFOLLOW;
+ break;
+ case LINK_SKIP:
+ fts_flags = FTS_PHYSICAL;
+ break;
+ default:
+ fts_flags = FTS_LOGICAL;
+
+ }
+
+ fts_flags |= FTS_NOSTAT | FTS_NOCHDIR;
+
+ if (!(fts = fts_open(argv, fts_flags, NULL)))
+ err(2, "fts_open");
+ while ((p = fts_read(fts)) != NULL) {
+ switch (p->fts_info) {
+ case FTS_DNR:
+ /* FALLTHROUGH */
+ case FTS_ERR:
+ errx(2, "%s: %s", p->fts_path, strerror(p->fts_errno));
+ break;
+ case FTS_D:
+ /* FALLTHROUGH */
+ case FTS_DP:
+ break;
+ case FTS_DC:
+ /* Print a warning for recursive directory loop */
+ warnx("warning: %s: recursive directory loop",
+ p->fts_path);
+ break;
+ default:
+ /* Check for file exclusion/inclusion */
+ ok = true;
+ if (dexclude || dinclude) {
+ if ((d = strrchr(p->fts_path, '/')) != NULL) {
+ dir = grep_malloc(sizeof(char) *
+ (d - p->fts_path + 1));
+ memcpy(dir, p->fts_path,
+ d - p->fts_path);
+ dir[d - p->fts_path] = '\0';
+ }
+ ok = dir_matching(dir);
+ free(dir);
+ dir = NULL;
+ }
+ if (fexclude || finclude)
+ ok &= file_matching(p->fts_path);
+
+ if (ok)
+ c += procfile(p->fts_path);
+ break;
+ }
+ }
+
+ fts_close(fts);
+ return (c);
+}
+
+/*
+ * Opens a file and processes it. Each file is processed line-by-line
+ * passing the lines to procline().
+ */
+int
+procfile(const char *fn)
+{
+ struct file *f;
+ struct stat sb;
+ struct str ln;
+ mode_t s;
+ int c, t;
+
+ if (mflag && (mcount <= 0))
+ return (0);
+
+ if (strcmp(fn, "-") == 0) {
+ fn = label != NULL ? label : getstr(1);
+ f = grep_open(NULL);
+ } else {
+ if (!stat(fn, &sb)) {
+ /* Check if we need to process the file */
+ s = sb.st_mode & S_IFMT;
+ if (s == S_IFDIR && dirbehave == DIR_SKIP)
+ return (0);
+ if ((s == S_IFIFO || s == S_IFCHR || s == S_IFBLK
+ || s == S_IFSOCK) && devbehave == DEV_SKIP)
+ return (0);
+ }
+ f = grep_open(fn);
+ }
+ if (f == NULL) {
+ if (!sflag)
+ warn("%s", fn);
+ if (errno == ENOENT)
+ notfound = true;
+ return (0);
+ }
+
+ ln.file = grep_malloc(strlen(fn) + 1);
+ strcpy(ln.file, fn);
+ ln.line_no = 0;
+ ln.len = 0;
+ tail = 0;
+ ln.off = -1;
+
+ for (first = true, c = 0; c == 0 || !(lflag || qflag); ) {
+ ln.off += ln.len + 1;
+ if ((ln.dat = grep_fgetln(f, &ln.len)) == NULL || ln.len == 0)
+ break;
+ if (ln.len > 0 && ln.dat[ln.len - 1] == line_sep)
+ --ln.len;
+ ln.line_no++;
+
+ /* Return if we need to skip a binary file */
+ if (f->binary && binbehave == BINFILE_SKIP) {
+ grep_close(f);
+ free(ln.file);
+ free(f);
+ return (0);
+ }
+ /* Process the file line-by-line */
+ t = procline(&ln, f->binary);
+ c += t;
+
+ /* Count the matches if we have a match limit */
+ if (mflag) {
+ mcount -= t;
+ if (mcount <= 0)
+ break;
+ }
+ }
+ if (Bflag > 0)
+ clearqueue();
+ grep_close(f);
+
+ if (cflag) {
+ if (!hflag)
+ printf("%s:", ln.file);
+ printf("%u%c", c, line_sep);
+ }
+ if (lflag && !qflag && c != 0)
+ printf("%s%c", fn, line_sep);
+ if (Lflag && !qflag && c == 0)
+ printf("%s%c", fn, line_sep);
+ if (c && !cflag && !lflag && !Lflag &&
+ binbehave == BINFILE_BIN && f->binary && !qflag)
+ printf(getstr(8), fn);
+
+ free(ln.file);
+ free(f);
+ return (c);
+}
+
+#define iswword(x) (iswalnum((x)) || (x) == L'_')
+
+/*
+ * Processes a line comparing it with the specified patterns. Each pattern
+ * is looped to be compared along with the full string, saving each and every
+ * match, which is necessary to colorize the output and to count the
+ * matches. The matching lines are passed to printline() to display the
+ * appropriate output.
+ */
+static int
+procline(struct str *l, int nottext)
+{
+ regmatch_t matches[MAX_LINE_MATCHES];
+ regmatch_t pmatch;
+ size_t st = 0;
+ unsigned int i;
+ int c = 0, m = 0, r = 0;
+
+ /* Loop to process the whole line */
+ while (st <= l->len) {
+ pmatch.rm_so = st;
+ pmatch.rm_eo = l->len;
+
+ /* Loop to compare with all the patterns */
+ for (i = 0; i < patterns; i++) {
+/*
+ * XXX: grep_search() is a workaround for speed up and should be
+ * removed in the future. See fastgrep.c.
+ */
+ if (fg_pattern[i].pattern) {
+ r = grep_search(&fg_pattern[i],
+ (unsigned char *)l->dat,
+ l->len, &pmatch);
+ r = (r == 0) ? 0 : REG_NOMATCH;
+ st = pmatch.rm_eo;
+ } else {
+ r = regexec(&r_pattern[i], l->dat, 1,
+ &pmatch, eflags);
+ r = (r == 0) ? 0 : REG_NOMATCH;
+ st = pmatch.rm_eo;
+ }
+ if (r == REG_NOMATCH)
+ continue;
+ /* Check for full match */
+ if (xflag &&
+ (pmatch.rm_so != 0 ||
+ (size_t)pmatch.rm_eo != l->len))
+ continue;
+ /* Check for whole word match */
+ if (fg_pattern[i].word && pmatch.rm_so != 0) {
+ wchar_t wbegin, wend;
+
+ wbegin = wend = L' ';
+ if (pmatch.rm_so != 0 &&
+ sscanf(&l->dat[pmatch.rm_so - 1],
+ "%lc", &wbegin) != 1)
+ continue;
+ if ((size_t)pmatch.rm_eo != l->len &&
+ sscanf(&l->dat[pmatch.rm_eo],
+ "%lc", &wend) != 1)
+ continue;
+ if (iswword(wbegin) || iswword(wend))
+ continue;
+ }
+ c = 1;
+ if (m < MAX_LINE_MATCHES)
+ matches[m++] = pmatch;
+ /* matches - skip further patterns */
+ if ((color != NULL && !oflag) || qflag || lflag)
+ break;
+ }
+
+ if (vflag) {
+ c = !c;
+ break;
+ }
+ /* One pass if we are not recording matches */
+ if ((color != NULL && !oflag) || qflag || lflag)
+ break;
+
+ if (st == (size_t)pmatch.rm_so)
+ break; /* No matches */
+ }
+
+ if (c && binbehave == BINFILE_BIN && nottext)
+ return (c); /* Binary file */
+
+ /* Dealing with the context */
+ if ((tail || c) && !cflag && !qflag && !lflag && !Lflag) {
+ if (c) {
+ if ((Aflag || Bflag) && !first_global &&
+ (first || since_printed > Bflag))
+ printf("--\n");
+ tail = Aflag;
+ if (Bflag > 0)
+ printqueue();
+ printline(l, ':', matches, m);
+ } else {
+ printline(l, '-', matches, m);
+ tail--;
+ }
+ first = false;
+ first_global = false;
+ since_printed = 0;
+ } else {
+ if (Bflag)
+ enqueue(l);
+ since_printed++;
+ }
+ return (c);
+}
+
+/*
+ * Safe malloc() for internal use.
+ */
+void *
+grep_malloc(size_t size)
+{
+ void *ptr;
+
+ if ((ptr = malloc(size)) == NULL)
+ err(2, "malloc");
+ return (ptr);
+}
+
+/*
+ * Safe calloc() for internal use.
+ */
+void *
+grep_calloc(size_t nmemb, size_t size)
+{
+ void *ptr;
+
+ if ((ptr = calloc(nmemb, size)) == NULL)
+ err(2, "calloc");
+ return (ptr);
+}
+
+/*
+ * Safe realloc() for internal use.
+ */
+void *
+grep_realloc(void *ptr, size_t size)
+{
+
+ if ((ptr = realloc(ptr, size)) == NULL)
+ err(2, "realloc");
+ return (ptr);
+}
+
+/*
+ * Safe strdup() for internal use.
+ */
+char *
+grep_strdup(const char *str)
+{
+ char *ret;
+
+ if ((ret = strdup(str)) == NULL)
+ err(2, "strdup");
+ return (ret);
+}
+
+/*
+ * Prints a matching line according to the command line options.
+ */
+void
+printline(struct str *line, int sep, regmatch_t *matches, int m)
+{
+ size_t a = 0;
+ int i, n = 0;
+
+ if (!hflag) {
+ if (nullflag == 0)
+ fputs(line->file, stdout);
+ else {
+ printf("%s", line->file);
+ putchar(0);
+ }
+ ++n;
+ }
+ if (nflag) {
+ if (n > 0)
+ putchar(sep);
+ printf("%d", line->line_no);
+ ++n;
+ }
+ if (bflag) {
+ if (n > 0)
+ putchar(sep);
+ printf("%lld", (long long)line->off);
+ ++n;
+ }
+ if (n)
+ putchar(sep);
+ /* --color and -o */
+ if ((oflag || color) && m > 0) {
+ for (i = 0; i < m; i++) {
+ if (!oflag)
+ fwrite(line->dat + a, matches[i].rm_so - a, 1,
+ stdout);
+ if (color)
+ fprintf(stdout, "\33[%sm\33[K", color);
+
+ fwrite(line->dat + matches[i].rm_so,
+ matches[i].rm_eo - matches[i].rm_so, 1,
+ stdout);
+
+ if (color)
+ fprintf(stdout, "\33[m\33[K");
+ a = matches[i].rm_eo;
+ if (oflag)
+ putchar('\n');
+ }
+ if (!oflag) {
+ if (line->len - a > 0)
+ fwrite(line->dat + a, line->len - a, 1, stdout);
+ putchar(line_sep);
+ }
+ } else {
+ fwrite(line->dat, line->len, 1, stdout);
+ putchar(line_sep);
+ }
+}
diff --git a/usr.bin/head/head.1 b/usr.bin/head/head.1
new file mode 100644
index 0000000..a7fa0bc
--- /dev/null
+++ b/usr.bin/head/head.1
@@ -0,0 +1,95 @@
+.\" $NetBSD: head.1,v 1.14 2017/07/04 06:59:34 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)head.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd May 4, 2004
+.Dt HEAD 1
+.Os
+.Sh NAME
+.Nm head
+.Nd display first lines of a file
+.Sh SYNOPSIS
+.Nm
+.Op Fl qv
+.Op Fl n Ar count
+.Op Fl c Ar byte_count
+.Op Ar file ...
+.Sh DESCRIPTION
+This filter displays the first
+.Ar count
+lines of each of the specified files, or of the standard input if no
+files are specified.
+If
+.Ar count
+is omitted it defaults to 10.
+If
+.Fl c Ar byte_count
+is specified,
+.Nm
+counts bytes instead of lines.
+.Pp
+If more than a single file is specified, or the
+.Fl v
+option is used, each file is preceded by a header consisting of the string
+.Dq ==> XXX \*[Le]=
+where
+.Dq XXX
+is the name of the file.
+The
+.Fl q
+flag disables the printing of the header in all cases.
+.Sh EXIT STATUS
+.Ex -std head
+.Sh COMPATIBILITY
+The historic command line syntax of
+.Nm
+is supported by this implementation.
+.Pp
+This command is mostly compatible with GNU extensions to
+.Nm .
+.Sh SEE ALSO
+.Xr tail 1
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 3.0 .
+It was enhanced to include the
+.Fl c ,
+.Fl q ,
+and
+.Fl v
+options for
+.Nx 2.1 .
diff --git a/usr.bin/head/head.c b/usr.bin/head/head.c
new file mode 100644
index 0000000..1d24d48
--- /dev/null
+++ b/usr.bin/head/head.c
@@ -0,0 +1,204 @@
+/* $NetBSD: head.c,v 1.24 2016/05/12 01:56:44 nonaka Exp $ */
+
+/*
+ * Copyright (c) 1980, 1987, 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1980, 1987, 1992, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)head.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: head.c,v 1.24 2016/05/12 01:56:44 nonaka Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * head - give the first few lines of a stream or of each of a set of files
+ *
+ * Bill Joy UCB August 24, 1977
+ */
+
+static void head(FILE *, intmax_t, intmax_t);
+static void obsolete(char *[]);
+__dead static void usage(void);
+
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ FILE *fp;
+ int first;
+ intmax_t linecnt;
+ intmax_t bytecnt;
+ char *ep;
+ int eval = 0;
+ int qflag = 0;
+ int vflag = 0;
+
+ (void)setlocale(LC_ALL, "");
+ obsolete(argv);
+ linecnt = 10;
+ bytecnt = 0;
+ while ((ch = getopt(argc, argv, "c:n:qv")) != -1)
+ switch(ch) {
+ case 'c':
+ errno = 0;
+ bytecnt = strtoimax(optarg, &ep, 10);
+ if ((bytecnt == INTMAX_MAX && errno == ERANGE) ||
+ *ep || bytecnt <= 0)
+ errx(1, "illegal byte count -- %s", optarg);
+ break;
+
+ case 'n':
+ errno = 0;
+ linecnt = strtoimax(optarg, &ep, 10);
+ if ((linecnt == INTMAX_MAX && errno == ERANGE) ||
+ *ep || linecnt <= 0)
+ errx(1, "illegal line count -- %s", optarg);
+ break;
+
+ case 'q':
+ qflag = 1;
+ vflag = 0;
+ break;
+
+ case 'v':
+ qflag = 0;
+ vflag = 1;
+ break;
+
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (*argv)
+ for (first = 1; *argv; ++argv) {
+ if ((fp = fopen(*argv, "r")) == NULL) {
+ warn("%s", *argv);
+ eval = 1;
+ continue;
+ }
+ if (vflag || (qflag == 0 && argc > 1)) {
+ (void)printf("%s==> %s <==\n",
+ first ? "" : "\n", *argv);
+ first = 0;
+ }
+ head(fp, linecnt, bytecnt);
+ (void)fclose(fp);
+ }
+ else
+ head(stdin, linecnt, bytecnt);
+ exit(eval);
+}
+
+static void
+head(FILE *fp, intmax_t cnt, intmax_t bytecnt)
+{
+ char buf[65536];
+ size_t len, rv, rv2;
+ int ch;
+
+ if (bytecnt) {
+ while (bytecnt) {
+ len = sizeof(buf);
+ if (bytecnt > (intmax_t)sizeof(buf))
+ len = sizeof(buf);
+ else
+ len = bytecnt;
+ rv = fread(buf, 1, len, fp);
+ if (rv == 0)
+ break; /* Distinguish EOF and error? */
+ rv2 = fwrite(buf, 1, rv, stdout);
+ if (rv2 != rv) {
+ if (feof(stdout))
+ errx(1, "EOF on stdout");
+ else
+ err(1, "failure writing to stdout");
+ }
+ bytecnt -= rv;
+ }
+ } else {
+ while ((ch = getc(fp)) != EOF) {
+ if (putchar(ch) == EOF)
+ err(1, "stdout");
+ if (ch == '\n' && --cnt == 0)
+ break;
+ }
+ }
+}
+
+static void
+obsolete(char *argv[])
+{
+ char *ap;
+
+ while ((ap = *++argv)) {
+ /* Return if "--" or not "-[0-9]*". */
+ if (ap[0] != '-' || ap[1] == '-' ||
+ !isdigit((unsigned char)ap[1]))
+ return;
+ if ((ap = malloc(strlen(*argv) + 2)) == NULL)
+ err(1, NULL);
+ ap[0] = '-';
+ ap[1] = 'n';
+ (void)strcpy(ap + 2, *argv + 1);
+ *argv = ap;
+ }
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: %s [-n lines] [file ...]\n",
+ getprogname());
+ exit(1);
+}
diff --git a/usr.bin/iconv/iconv.1 b/usr.bin/iconv/iconv.1
new file mode 100644
index 0000000..69a4dcc
--- /dev/null
+++ b/usr.bin/iconv/iconv.1
@@ -0,0 +1,122 @@
+.\" $NetBSD: iconv.1,v 1.5 2013/05/12 12:25:12 rodent Exp $
+.\"
+.\" Copyright (c)2003 Citrus Project,
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd March 20, 2008
+.Dt ICONV 1
+.Os
+.\" ----------------------------------------------------------------------
+.Sh NAME
+.Nm iconv
+.Nd codeset conversion utility
+.\" ----------------------------------------------------------------------
+.Sh SYNOPSIS
+.Nm
+.Op Fl cs
+.Fl f
+.Ar from_name
+.Fl t
+.Ar to_name
+.Op Ar file ...
+.Nm
+.Fl f
+.Ar from_name
+.Op Fl cs
+.Op Fl t Ar to_name
+.Op Ar file ...
+.Nm
+.Fl t
+.Ar to_name
+.Op Fl cs
+.Op Fl f Ar from_name
+.Op Ar file ...
+.Nm
+.Fl l
+.\" ----------------------------------------------------------------------
+.Sh DESCRIPTION
+The
+.Nm
+utility converts the codeset of
+.Ar file
+(or from standard input if no file is specified) from codeset
+.Ar from_name
+to codeset
+.Ar to_name
+and outputs the
+converted text on standard output.
+.Pp
+The following options are available:
+.Bl -tag -width 0123
+.It Fl c
+Prevent output of any invalid characters.
+By default,
+.Nm
+outputs an
+.Dq invalid character
+specified by the
+.Ar to_name
+codeset when it encounters a character which is valid in the
+.Ar from_name
+codeset but does not have a corresponding character in the
+.Ar to_name
+codeset.
+.It Fl f
+Specifies the source codeset name as
+.Ar from_name .
+.It Fl l
+Lists available codeset names.
+Note that not all combinations of
+.Ar from_name
+and
+.Ar to_name
+are valid.
+.It Fl s
+Silent.
+By default,
+.Nm
+outputs the number of
+.Dq invalid characters
+to standard error if they exist.
+This option prevents this behaviour.
+.It Fl t
+Specifies the destination codeset name as
+.Ar to_name .
+.El
+.\" ----------------------------------------------------------------------
+.Sh EXIT STATUS
+.Ex -std iconv
+.\" ----------------------------------------------------------------------
+.Sh SEE ALSO
+.Xr iconv 3
+.\" ----------------------------------------------------------------------
+.Sh STANDARDS
+.Nm
+conforms to
+.St -p1003.1-2001 .
+.\" ----------------------------------------------------------------------
+.Sh HISTORY
+.Nm
+first appeared in
+.Nx 2.0 .
diff --git a/usr.bin/iconv/iconv.c b/usr.bin/iconv/iconv.c
new file mode 100644
index 0000000..dd9a5d4
--- /dev/null
+++ b/usr.bin/iconv/iconv.c
@@ -0,0 +1,236 @@
+/* $NetBSD: iconv.c,v 1.19 2013/10/07 02:00:46 dholland Exp $ */
+
+/*-
+ * Copyright (c)2003 Citrus Project,
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+__RCSID("$NetBSD: iconv.c,v 1.19 2013/10/07 02:00:46 dholland Exp $");
+#endif /* LIBC_SCCS and not lint */
+
+#include <err.h>
+#include <errno.h>
+#include <iconv.h>
+#include <langinfo.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+static void usage(void) __dead;
+static int scmp(const void *, const void *);
+static void show_codesets(void);
+static void do_conv(const char *, FILE *, const char *, const char *, int, int);
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "Usage:\t%1$s [-cs] -f <from_code> -t <to_code> [file ...]\n"
+ "\t%1$s -f <from_code> [-cs] [-t <to_code>] [file ...]\n"
+ "\t%1$s -t <to_code> [-cs] [-f <from_code>] [file ...]\n"
+ "\t%1$s -l\n", getprogname());
+ exit(1);
+}
+
+/*
+ * qsort() helper function
+ */
+static int
+scmp(const void *v1, const void *v2)
+{
+ const char * const *s1 = v1;
+ const char * const *s2 = v2;
+
+ return strcasecmp(*s1, *s2);
+}
+
+static void
+show_codesets(void)
+{
+ char **list;
+ size_t sz, i;
+
+ if (__iconv_get_list(&list, &sz))
+ err(EXIT_FAILURE, "__iconv_get_list()");
+
+ qsort(list, sz, sizeof(char *), scmp);
+
+ for (i = 0; i < sz; i++)
+ (void)printf("%s\n", list[i]);
+
+ __iconv_free_list(list, sz);
+}
+
+#define INBUFSIZE 1024
+#define OUTBUFSIZE (INBUFSIZE * 2)
+/*ARGSUSED*/
+static void
+do_conv(const char *fn, FILE *fp, const char *from, const char *to, int silent,
+ int hide_invalid)
+{
+ char inbuf[INBUFSIZE], outbuf[OUTBUFSIZE], *out;
+ const char *in;
+ size_t inbytes, outbytes, ret, invalids;
+ iconv_t cd;
+ uint32_t flags = 0;
+ int serrno;
+
+ if (hide_invalid)
+ flags |= __ICONV_F_HIDE_INVALID;
+ cd = iconv_open(to, from);
+ if (cd == (iconv_t)-1)
+ err(EXIT_FAILURE, "iconv_open(%s, %s)", to, from);
+
+ invalids = 0;
+ while ((inbytes = fread(inbuf, 1, INBUFSIZE, fp)) > 0) {
+ in = inbuf;
+ while (inbytes > 0) {
+ size_t inval;
+
+ out = outbuf;
+ outbytes = OUTBUFSIZE;
+ ret = __iconv(cd, &in, &inbytes, &out, &outbytes,
+ flags, &inval);
+ serrno = errno;
+ invalids += inval;
+ if (outbytes < OUTBUFSIZE)
+ (void)fwrite(outbuf, 1, OUTBUFSIZE - outbytes,
+ stdout);
+ errno = serrno;
+ if (ret == (size_t)-1 && errno != E2BIG) {
+ /*
+ * XXX: iconv(3) is bad interface.
+ * invalid character count is lost here.
+ * instead, we just provide __iconv function.
+ */
+ if (errno != EINVAL || in == inbuf)
+ err(EXIT_FAILURE, "iconv()");
+
+ /* incomplete input character */
+ (void)memmove(inbuf, in, inbytes);
+ ret = fread(inbuf + inbytes, 1,
+ INBUFSIZE - inbytes, fp);
+ if (ret == 0) {
+ fflush(stdout);
+ if (feof(fp))
+ errx(EXIT_FAILURE,
+ "unexpected end of file; "
+ "the last character is "
+ "incomplete.");
+ else
+ err(EXIT_FAILURE, "fread()");
+ }
+ in = inbuf;
+ inbytes += ret;
+ }
+ }
+ }
+ /* reset the shift state of the output buffer */
+ outbytes = OUTBUFSIZE;
+ out = outbuf;
+ ret = iconv(cd, NULL, NULL, &out, &outbytes);
+ if (ret == (size_t)-1)
+ err(EXIT_FAILURE, "iconv()");
+ if (outbytes < OUTBUFSIZE)
+ (void)fwrite(outbuf, 1, OUTBUFSIZE - outbytes, stdout);
+
+ if (invalids > 0 && !silent)
+ warnx("warning: invalid characters: %lu",
+ (unsigned long)invalids);
+
+ iconv_close(cd);
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch, i;
+ int opt_l = 0, opt_s = 0, opt_c = 0;
+ char *opt_f = NULL, *opt_t = NULL;
+ FILE *fp;
+
+ setlocale(LC_ALL, "");
+ setprogname(argv[0]);
+
+ while ((ch = getopt(argc, argv, "cslf:t:")) != EOF) {
+ switch (ch) {
+ case 'c':
+ opt_c = 1;
+ break;
+ case 's':
+ opt_s = 1;
+ break;
+ case 'l':
+ /* list */
+ opt_l = 1;
+ break;
+ case 'f':
+ /* from */
+ opt_f = estrdup(optarg);
+ break;
+ case 't':
+ /* to */
+ opt_t = estrdup(optarg);
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (opt_l) {
+ if (argc > 0 || opt_s || opt_f != NULL || opt_t != NULL) {
+ warnx("-l is not allowed with other flags.");
+ usage();
+ }
+ show_codesets();
+ } else {
+ if (opt_f == NULL) {
+ if (opt_t == NULL)
+ usage();
+ opt_f = nl_langinfo(CODESET);
+ } else if (opt_t == NULL)
+ opt_t = nl_langinfo(CODESET);
+
+ if (argc == 0)
+ do_conv("<stdin>", stdin, opt_f, opt_t, opt_s, opt_c);
+ else {
+ for (i = 0; i < argc; i++) {
+ fp = fopen(argv[i], "r");
+ if (fp == NULL)
+ err(EXIT_FAILURE, "Cannot open `%s'",
+ argv[i]);
+ do_conv(argv[i], fp, opt_f, opt_t, opt_s,
+ opt_c);
+ (void)fclose(fp);
+ }
+ }
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/usr.bin/id/groups.1 b/usr.bin/id/groups.1
new file mode 100644
index 0000000..684f0d5
--- /dev/null
+++ b/usr.bin/id/groups.1
@@ -0,0 +1,59 @@
+.\" $NetBSD: groups.1,v 1.11 2017/07/04 07:00:30 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)groups.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt GROUPS 1
+.Os
+.Sh NAME
+.Nm groups
+.Nd show group memberships
+.Sh SYNOPSIS
+.Nm
+.Op Ar user
+.Sh DESCRIPTION
+The
+.Nm
+utility has been obsoleted by the
+.Xr id 1
+utility, and is equivalent to
+.Dq Nm id Fl Gn Op Ar user .
+The command
+.Dq Nm id Fl p
+is suggested for normal interactive use.
+.Pp
+The
+.Nm
+utility displays the groups to which you (or the optionally specified user)
+belong.
+.Sh EXIT STATUS
+.Ex -std groups
+.Sh SEE ALSO
+.Xr id 1
diff --git a/usr.bin/id/id.1 b/usr.bin/id/id.1
new file mode 100644
index 0000000..ad66b62
--- /dev/null
+++ b/usr.bin/id/id.1
@@ -0,0 +1,136 @@
+.\" $NetBSD: id.1,v 1.14 2017/07/04 07:01:07 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)id.1 8.2 (Berkeley) 5/5/94
+.\"
+.Dd May 5, 1994
+.Dt ID 1
+.Os
+.Sh NAME
+.Nm id
+.Nd return user identity
+.Sh SYNOPSIS
+.Nm
+.Op Ar user
+.Nm
+.Fl G Op Fl n
+.Op Ar user
+.Nm
+.Fl g Op Fl nr
+.Op Ar user
+.Nm
+.Fl p
+.Op Ar user
+.Nm
+.Fl u Op Fl nr
+.Op Ar user
+.Sh DESCRIPTION
+The
+.Nm
+utility displays the user and group names and numeric IDs, of the
+calling process, to the standard output.
+If the real and effective IDs are different, both are displayed,
+otherwise only the real ID is displayed.
+.Pp
+If a
+.Ar user
+(login name or user ID)
+is specified, the user and group IDs of that user are displayed.
+In this case, the real and effective IDs are assumed to be the same.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl G
+Display the different group IDs (effective, real and supplementary)
+as white-space separated numbers, in no particular order.
+.It Fl g
+Display the effective group ID as a number.
+.It Fl n
+Display the name of the user or group ID for the
+.Fl G ,
+.Fl g
+and
+.Fl u
+options instead of the number.
+If any of the ID numbers cannot be mapped into names, the number will be
+displayed as usual.
+.It Fl p
+Make the output human-readable.
+If the user name returned by
+.Xr getlogin 2
+is different from the login name referenced by the user ID, the name
+returned by
+.Xr getlogin 2
+is displayed, preceded by the keyword ``login''.
+The user ID as a name is displayed, preceded by the keyword ``uid''.
+If the effective user ID is different from the real user ID, the real user
+ID is displayed as a name, preceded by the keyword ``euid''.
+If the effective group ID is different from the real group ID, the real group
+ID is displayed as a name, preceded by the keyword ``rgid''.
+The list of groups to which the user belongs is then displayed as names,
+preceded by the keyword ``groups''.
+Each display is on a separate line.
+.It Fl r
+Display the real ID for the
+.Fl g
+and
+.Fl u
+options instead of the effective ID.
+.It Fl u
+Display the effective user ID as a number.
+.El
+.Sh EXIT STATUS
+.Ex -std id
+.Sh SEE ALSO
+.Xr who 1
+.Sh STANDARDS
+The
+.Nm
+function is expected to conform to
+.St -p1003.2 .
+.Sh HISTORY
+The
+historic
+.Xr groups 1
+command is equivalent to
+.Dq Nm id Fl Gn Op Ar user .
+.Pp
+The
+historic
+.Xr whoami 1
+command is equivalent to
+.Dq Nm id Fl un .
+.Pp
+The
+.Nm
+command first appeared in
+.Bx 4.4 .
diff --git a/usr.bin/id/id.c b/usr.bin/id/id.c
new file mode 100644
index 0000000..720cc49
--- /dev/null
+++ b/usr.bin/id/id.c
@@ -0,0 +1,370 @@
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)id.c 8.3 (Berkeley) 4/28/95";
+#else
+__RCSID("$NetBSD: id.c,v 1.32 2011/09/16 15:39:26 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void current(void);
+static void pretty(struct passwd *);
+static void group(struct passwd *, int);
+__dead static void usage(void);
+static void user(struct passwd *);
+static struct passwd *who(char *);
+
+static int maxgroups;
+static gid_t *groups;
+
+int
+main(int argc, char *argv[])
+{
+ struct group *gr;
+ struct passwd *pw;
+ int ch, id;
+ int Gflag, gflag, nflag, pflag, rflag, uflag;
+ const char *opts;
+
+ Gflag = gflag = nflag = pflag = rflag = uflag = 0;
+
+ if (strcmp(getprogname(), "groups") == 0) {
+ Gflag = 1;
+ nflag = 1;
+ opts = "";
+ if (argc > 2)
+ usage();
+ } else if (strcmp(getprogname(), "whoami") == 0) {
+ uflag = 1;
+ nflag = 1;
+ opts = "";
+ if (argc > 1)
+ usage();
+ } else
+ opts = "Ggnpru";
+
+ while ((ch = getopt(argc, argv, opts)) != -1)
+ switch (ch) {
+ case 'G':
+ Gflag = 1;
+ break;
+ case 'g':
+ gflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'p':
+ pflag = 1;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 'u':
+ uflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ switch (Gflag + gflag + pflag + uflag) {
+ case 1:
+ break;
+ case 0:
+ if (!nflag && !rflag)
+ break;
+ /* FALLTHROUGH */
+ default:
+ usage();
+ }
+
+ if (strcmp(opts, "") != 0 && argc > 1)
+ usage();
+
+ pw = *argv ? who(*argv) : NULL;
+
+ maxgroups = sysconf(_SC_NGROUPS_MAX);
+ if ((groups = malloc((maxgroups + 1) * sizeof(gid_t))) == NULL)
+ err(1, NULL);
+
+ if (gflag) {
+ id = pw ? pw->pw_gid : rflag ? getgid() : getegid();
+ if (nflag && (gr = getgrgid(id)))
+ (void)printf("%s\n", gr->gr_name);
+ else
+ (void)printf("%u\n", id);
+ goto done;
+ }
+
+ if (uflag) {
+ id = pw ? pw->pw_uid : rflag ? getuid() : geteuid();
+ if (nflag && (pw = getpwuid(id)))
+ (void)printf("%s\n", pw->pw_name);
+ else
+ (void)printf("%u\n", id);
+ goto done;
+ }
+
+ if (Gflag) {
+ group(pw, nflag);
+ goto done;
+ }
+
+ if (pflag) {
+ pretty(pw);
+ goto done;
+ }
+
+ if (pw)
+ user(pw);
+ else
+ current();
+done:
+ free(groups);
+
+ return 0;
+}
+
+static void
+pretty(struct passwd *pw)
+{
+ struct group *gr;
+ u_int eid, rid;
+ char *login;
+
+ if (pw) {
+ (void)printf("uid\t%s\n", pw->pw_name);
+ (void)printf("groups\t");
+ group(pw, 1);
+ } else {
+ if ((login = getlogin()) == NULL)
+ err(1, "getlogin");
+
+ pw = getpwuid(rid = getuid());
+ if (pw == NULL || strcmp(login, pw->pw_name))
+ (void)printf("login\t%s\n", login);
+ if (pw)
+ (void)printf("uid\t%s\n", pw->pw_name);
+ else
+ (void)printf("uid\t%u\n", rid);
+
+ if ((eid = geteuid()) != rid) {
+ if ((pw = getpwuid(eid)) != NULL)
+ (void)printf("euid\t%s\n", pw->pw_name);
+ else
+ (void)printf("euid\t%u\n", eid);
+ }
+ if ((rid = getgid()) != (eid = getegid())) {
+ if ((gr = getgrgid(rid)) != NULL)
+ (void)printf("rgid\t%s\n", gr->gr_name);
+ else
+ (void)printf("rgid\t%u\n", rid);
+ }
+ (void)printf("groups\t");
+ group(NULL, 1);
+ }
+}
+
+static void
+current(void)
+{
+ struct group *gr;
+ struct passwd *pw;
+ gid_t gid, egid, lastid;
+ uid_t uid, euid;
+ int cnt, ngroups;
+ const char *fmt;
+
+ uid = getuid();
+ (void)printf("uid=%ju", (uintmax_t)uid);
+ if ((pw = getpwuid(uid)) != NULL)
+ (void)printf("(%s)", pw->pw_name);
+ gid = getgid();
+ (void)printf(" gid=%ju", (uintmax_t)gid);
+ if ((gr = getgrgid(gid)) != NULL)
+ (void)printf("(%s)", gr->gr_name);
+ if ((euid = geteuid()) != uid) {
+ (void)printf(" euid=%ju", (uintmax_t)euid);
+ if ((pw = getpwuid(euid)) != NULL)
+ (void)printf("(%s)", pw->pw_name);
+ }
+ if ((egid = getegid()) != gid) {
+ (void)printf(" egid=%ju", (uintmax_t)egid);
+ if ((gr = getgrgid(egid)) != NULL)
+ (void)printf("(%s)", gr->gr_name);
+ }
+ if ((ngroups = getgroups(maxgroups, groups)) != 0) {
+ for (fmt = " groups=%ju", lastid = -1, cnt = 0; cnt < ngroups;
+ fmt = ",%ju", lastid = gid, cnt++) {
+ gid = groups[cnt];
+ if (lastid == gid)
+ continue;
+ (void)printf(fmt, (uintmax_t)gid);
+ if ((gr = getgrgid(gid)) != NULL)
+ (void)printf("(%s)", gr->gr_name);
+ }
+ }
+ (void)printf("\n");
+}
+
+static void
+user(struct passwd *pw)
+{
+ struct group *gr;
+ const char *fmt;
+ int cnt, id, lastid, ngroups;
+ gid_t *glist = groups;
+
+ id = pw->pw_uid;
+ (void)printf("uid=%u(%s)", id, pw->pw_name);
+ (void)printf(" gid=%lu", (u_long)pw->pw_gid);
+ if ((gr = getgrgid(pw->pw_gid)) != NULL)
+ (void)printf("(%s)", gr->gr_name);
+ ngroups = maxgroups + 1;
+ if (getgrouplist(pw->pw_name, pw->pw_gid, glist, &ngroups) == -1) {
+ glist = malloc(ngroups * sizeof(gid_t));
+ (void) getgrouplist(pw->pw_name, pw->pw_gid, glist, &ngroups);
+ }
+ for (fmt = " groups=%u", lastid = -1, cnt = 0; cnt < ngroups;
+ fmt=",%u", lastid = id, cnt++) {
+ id = glist[cnt];
+ if (lastid == id)
+ continue;
+ (void)printf(fmt, id);
+ if ((gr = getgrgid(id)) != NULL)
+ (void)printf("(%s)", gr->gr_name);
+ }
+ (void)printf("\n");
+ if (glist != groups)
+ free(glist);
+}
+
+static void
+group(struct passwd *pw, int nflag)
+{
+ struct group *gr;
+ int cnt, ngroups;
+ gid_t id, lastid;
+ const char *fmt;
+ gid_t *glist = groups;
+
+ if (pw) {
+ ngroups = maxgroups;
+ if (getgrouplist(pw->pw_name, pw->pw_gid, glist, &ngroups)
+ == -1) {
+ glist = malloc(ngroups * sizeof(gid_t));
+ (void) getgrouplist(pw->pw_name, pw->pw_gid, glist,
+ &ngroups);
+ }
+ } else {
+ glist[0] = getgid();
+ ngroups = getgroups(maxgroups, glist + 1) + 1;
+ }
+ fmt = nflag ? "%s" : "%u";
+ for (lastid = -1, cnt = 0; cnt < ngroups; ++cnt) {
+ if (lastid == (id = glist[cnt]) || (cnt && id == glist[0]))
+ continue;
+ if (nflag) {
+ if ((gr = getgrgid(id)) != NULL)
+ (void)printf(fmt, gr->gr_name);
+ else
+ (void)printf(*fmt == ' ' ? " %u" : "%u",
+ id);
+ fmt = " %s";
+ } else {
+ (void)printf(fmt, id);
+ fmt = " %u";
+ }
+ lastid = id;
+ }
+ (void)printf("\n");
+ if (glist != groups)
+ free(glist);
+}
+
+static struct passwd *
+who(char *u)
+{
+ struct passwd *pw;
+ long id;
+ char *ep;
+
+ /*
+ * Translate user argument into a pw pointer. First, try to
+ * get it as specified. If that fails, try it as a number.
+ */
+ if ((pw = getpwnam(u)) != NULL)
+ return pw;
+ id = strtol(u, &ep, 10);
+ if (*u && !*ep && (pw = getpwuid(id)))
+ return pw;
+ errx(1, "%s: No such user", u);
+ /* NOTREACHED */
+ return NULL;
+}
+
+static void
+usage(void)
+{
+
+ if (strcmp(getprogname(), "groups") == 0) {
+ (void)fprintf(stderr, "usage: groups [user]\n");
+ } else if (strcmp(getprogname(), "whoami") == 0) {
+ (void)fprintf(stderr, "usage: whoami\n");
+ } else {
+ (void)fprintf(stderr, "usage: id [user]\n");
+ (void)fprintf(stderr, " id -G [-n] [user]\n");
+ (void)fprintf(stderr, " id -g [-nr] [user]\n");
+ (void)fprintf(stderr, " id -p [user]\n");
+ (void)fprintf(stderr, " id -u [-nr] [user]\n");
+ }
+ exit(1);
+}
diff --git a/usr.bin/id/whoami.1 b/usr.bin/id/whoami.1
new file mode 100644
index 0000000..53d9405
--- /dev/null
+++ b/usr.bin/id/whoami.1
@@ -0,0 +1,58 @@
+.\" $NetBSD: whoami.1,v 1.10 2016/10/02 21:06:18 abhinav Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)whoami.1 8.1 (Berkeley) 6/6/93
+.\" $NetBSD: whoami.1,v 1.10 2016/10/02 21:06:18 abhinav Exp $
+.\"
+.Dd June 6, 1993
+.Dt WHOAMI 1
+.Os
+.Sh NAME
+.Nm whoami
+.Nd display effective user id
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+utility has been obsoleted by the
+.Xr id 1
+utility, and is equivalent to
+.Dq Nm id Fl un .
+The command
+.Dq Nm id Fl p
+is suggested for normal interactive use.
+.Pp
+The
+.Nm
+utility displays your effective user ID as a name.
+.Sh EXIT STATUS
+.Ex -std whoami
+.Sh SEE ALSO
+.Xr id 1
diff --git a/usr.bin/ipcrm/ipcrm.1 b/usr.bin/ipcrm/ipcrm.1
new file mode 100644
index 0000000..2e2cad3
--- /dev/null
+++ b/usr.bin/ipcrm/ipcrm.1
@@ -0,0 +1,97 @@
+.\" $NetBSD: ipcrm.1,v 1.11 2008/06/01 10:25:29 wiz Exp $
+.\"
+.\" Copyright (c) 1994 Adam Glass
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. The name of the Author may not be used to endorse or promote products
+.\" derived from this software without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY Adam Glass ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL Adam Glass BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $NetBSD: ipcrm.1,v 1.11 2008/06/01 10:25:29 wiz Exp $
+.\"
+.Dd May 31, 2008
+.Dt IPCRM 1
+.Os
+.Sh NAME
+.Nm ipcrm
+.Nd remove the specified message queues, semaphore sets, and shared memory segments
+.Sh SYNOPSIS
+.Nm
+.Op Fl M Ar shmkey
+.Op Fl m Ar shmid
+.Op Fl Q Ar msgkey
+.Op Fl q Ar msqid
+.Op Fl S Ar semkey
+.Op Fl s Ar semid
+.Ar ...
+.Sh DESCRIPTION
+.Nm
+removes the specified message queues, semaphores, and shared memory
+segments.
+These System V IPC objects can be specified by their
+creation ID or any associated key.
+.Pp
+The following options are used to specify which IPC objects will be removed.
+Any number and combination of these options can be used:
+.Bl -tag -width indent
+.It Fl M Ar shmkey
+Mark the shared memory segment associated with key
+.Ar shmkey
+for removal.
+This marked segment will be destroyed after the last detach.
+.It Fl m Ar shmid
+Mark the shared memory segment associated with ID
+.Ar shmid
+for removal.
+This marked segment will be destroyed after the last detach.
+.It Fl Q Ar msgkey
+Remove the message queue associated with key
+.Ar msgkey
+from the system.
+.It Fl q Ar msqid
+Remove the message queue associated with the ID
+.Ar msqid
+from the system.
+.It Fl S Ar semkey
+Remove the semaphore set associated with key
+.Ar semkey
+from the system.
+.It Fl s Ar semid
+Removes the semaphore set associated with ID
+.Ar semid
+from the system.
+.El
+.Pp
+If the
+.Ar id
+or
+.Ar key
+argument is
+.Dq all
+then all entries of the appropriate type are removed.
+.Pp
+The identifiers and keys associated with these System V IPC objects can be
+determined by using
+.Xr ipcs 1 .
+.Sh SEE ALSO
+.Xr ipcs 1 ,
+.Xr shmat 2 ,
+.Xr shmctl 2 ,
+.Xr shmdt 2 ,
+.Xr shmget 2
diff --git a/usr.bin/ipcrm/ipcrm.c b/usr.bin/ipcrm/ipcrm.c
new file mode 100644
index 0000000..b4d3706
--- /dev/null
+++ b/usr.bin/ipcrm/ipcrm.c
@@ -0,0 +1,313 @@
+/* $NetBSD: ipcrm.c,v 1.16 2009/01/18 01:06:42 lukem Exp $ */
+
+/*
+ * Copyright (c) 1994 Adam Glass
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Adam Glass.
+ * 4. The name of the Author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Adam Glass ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL Adam Glass BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/msg.h>
+#include <sys/sem.h>
+#include <sys/shm.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <err.h>
+#include <signal.h>
+#include <sys/sysctl.h>
+
+#define IPC_TO_STR(x) (x == 'Q' ? "msq" : (x == 'M' ? "shm" : "sem"))
+#define IPC_TO_STRING(x) (x == 'Q' ? "message queue" : \
+ (x == 'M' ? "shared memory segment" : "semaphore"))
+
+static sig_atomic_t signaled;
+
+static void usage(void) __dead;
+static int msgrm(key_t, int);
+static int shmrm(key_t, int);
+static int semrm(key_t, int);
+static int msgrmall(void);
+static int shmrmall(void);
+static int semrmall(void);
+static void not_configured(int);
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s [-M shmkey] [-m shmid] [-Q msgkey]\n",
+ getprogname());
+ (void)fprintf(stderr, "\t[-q msqid] [-S semkey] [-s semid] ...\n");
+ exit(1);
+}
+
+static int
+msgrm(key_t key, int id)
+{
+ if (key) {
+ id = msgget(key, 0);
+ if (id == -1)
+ return -1;
+ }
+ return msgctl(id, IPC_RMID, NULL);
+}
+
+static int
+shmrm(key_t key, int id)
+{
+ if (key) {
+ id = shmget(key, 0, 0);
+ if (id == -1)
+ return -1;
+ }
+ return shmctl(id, IPC_RMID, NULL);
+}
+
+static int
+semrm(key_t key, int id)
+{
+
+ if (key) {
+ id = semget(key, 0, 0);
+ if (id == -1)
+ return -1;
+ }
+ return semctl(id, 0, IPC_RMID, NULL);
+}
+
+static int
+msgrmall(void)
+{
+ int mib[4];
+ struct msg_sysctl_info *msgsi;
+ int32_t i;
+ size_t len;
+ int result = 0;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_INFO;
+ mib[3] = KERN_SYSVIPC_MSG_INFO;
+
+ if (sysctl(mib, 4, NULL, &len, NULL, 0) == -1)
+ err(1, "sysctl(KERN_SYSVIPC_MSG_INFO)");
+
+ if ((msgsi = malloc(len)) == NULL)
+ err(1, "malloc");
+ if (sysctl(mib, 4, msgsi, &len, NULL, 0) == -1) {
+ free(msgsi);
+ err(1, "sysctl(KERN_SYSVIPC_MSG_INFO)");
+ }
+
+ for (i = 0; i < msgsi->msginfo.msgmni; i++) {
+ struct msgid_ds_sysctl *msgptr = &msgsi->msgids[i];
+ if (msgptr->msg_qbytes != 0)
+ result -= msgrm((key_t)0,
+ (int)IXSEQ_TO_IPCID(i, msgptr->msg_perm));
+ }
+ free(msgsi);
+ return result;
+}
+
+static int
+shmrmall(void)
+{
+ int mib[4];
+ struct shm_sysctl_info *shmsi;
+ size_t i, len;
+ int result = 0;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_INFO;
+ mib[3] = KERN_SYSVIPC_SHM_INFO;
+
+ if (sysctl(mib, 4, NULL, &len, NULL, 0) == -1)
+ err(1, "sysctl(KERN_SYSVIPC_SHM_INFO)");
+
+ if ((shmsi = malloc(len)) == NULL)
+ err(1, "malloc");
+ if (sysctl(mib, 4, shmsi, &len, NULL, 0) == -1) {
+ free(shmsi);
+ err(1, "sysctl(KERN_SYSVIPC_SHM_INFO)");
+ }
+
+ for (i = 0; i < shmsi->shminfo.shmmni; i++) {
+ struct shmid_ds_sysctl *shmptr = &shmsi->shmids[i];
+ if (shmptr->shm_perm.mode & 0x0800)
+ result -= shmrm((key_t)0,
+ (int)IXSEQ_TO_IPCID(i, shmptr->shm_perm));
+ }
+ free(shmsi);
+ return result;
+}
+
+static int
+semrmall(void)
+{
+ int mib[4];
+ struct sem_sysctl_info *semsi;
+ size_t len;
+ int32_t i;
+ int result = 0;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_INFO;
+ mib[3] = KERN_SYSVIPC_SEM_INFO;
+
+ if (sysctl(mib, 4, NULL, &len, NULL, 0) == -1)
+ err(1, "sysctl(KERN_SYSVIPC_SEM_INFO)");
+
+ if ((semsi = malloc(len)) == NULL)
+ err(1, "malloc");
+ if (sysctl(mib, 4, semsi, &len, NULL, 0) == -1) {
+ free(semsi);
+ err(1, "sysctl(KERN_SYSVIPC_SEM_INFO)");
+ }
+
+ for (i = 0; i < semsi->seminfo.semmni; i++) {
+ struct semid_ds_sysctl *semptr = &semsi->semids[i];
+ if ((semptr->sem_perm.mode & SEM_ALLOC) != 0)
+ result -= semrm((key_t)0,
+ (int)IXSEQ_TO_IPCID(i, semptr->sem_perm));
+ }
+ free(semsi);
+ return result;
+}
+
+static void
+/*ARGSUSED*/
+not_configured(int n)
+{
+ signaled++;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c, result, errflg, target_id;
+ key_t target_key;
+
+ setprogname(argv[0]);
+ errflg = 0;
+ (void)signal(SIGSYS, not_configured);
+ while ((c = getopt(argc, argv, "q:m:s:Q:M:S:")) != -1) {
+ signaled = 0;
+ target_id = 0;
+ target_key = 0;
+ result = 0;
+
+ if (optarg != NULL && strcmp(optarg, "all") == 0) {
+ switch (c) {
+ case 'm':
+ case 'M':
+ result = shmrmall();
+ break;
+ case 'q':
+ case 'Q':
+ result = msgrmall();
+ break;
+ case 's':
+ case 'S':
+ result = semrmall();
+ break;
+ default:
+ usage();
+ }
+ } else {
+ switch (c) {
+ case 'q':
+ case 'm':
+ case 's':
+ target_id = atoi(optarg);
+ break;
+ case 'Q':
+ case 'M':
+ case 'S':
+ target_key = atol(optarg);
+ if (target_key == IPC_PRIVATE) {
+ warnx("can't remove private %ss",
+ IPC_TO_STRING(c));
+ continue;
+ }
+ break;
+ default:
+ usage();
+ }
+ switch (c) {
+ case 'q':
+ result = msgrm((key_t)0, target_id);
+ break;
+ case 'm':
+ result = shmrm((key_t)0, target_id);
+ break;
+ case 's':
+ result = semrm((key_t)0, target_id);
+ break;
+ case 'Q':
+ result = msgrm(target_key, 0);
+ break;
+ case 'M':
+ result = shmrm(target_key, 0);
+ break;
+ case 'S':
+ result = semrm(target_key, 0);
+ break;
+ }
+ }
+ if (result < 0) {
+ if (!signaled) {
+ if (target_id) {
+ warn("%sid(%d): ",
+ IPC_TO_STR(toupper(c)), target_id);
+ errflg++;
+ } else if (target_key) {
+ warn("%skey(%ld): ", IPC_TO_STR(c),
+ (long)target_key);
+ errflg++;
+ }
+ } else {
+ errflg++;
+ warnx("%ss are not configured in "
+ "the running kernel",
+ IPC_TO_STRING(toupper(c)));
+ }
+ }
+ }
+
+ if (optind != argc) {
+ warnx("Unknown argument: %s", argv[optind]);
+ usage();
+ }
+ return errflg;
+}
diff --git a/usr.bin/ipcs/ipcs.1 b/usr.bin/ipcs/ipcs.1
new file mode 100644
index 0000000..86bd34e
--- /dev/null
+++ b/usr.bin/ipcs/ipcs.1
@@ -0,0 +1,141 @@
+.\" $NetBSD: ipcs.1,v 1.20 2014/06/11 14:57:55 joerg Exp $
+.\"
+.\" Copyright (c) 1994 SigmaSoft, Th. Lockert
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd June 11, 2014
+.Dt IPCS 1
+.Os
+.Sh NAME
+.Nm ipcs
+.Nd report System V interprocess communication facilities status
+.Sh SYNOPSIS
+.Nm
+.Op Fl abcmopqstMQST
+.Sh DESCRIPTION
+The
+.Nm
+program provides information on System V interprocess communication
+(IPC) facilities on the system.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Show the maximum amount of information possible when
+displaying active semaphores, message queues,
+and shared memory segments.
+(This is shorthand for specifying the
+.Fl b ,
+.Fl c ,
+.Fl o ,
+.Fl p ,
+and
+.Fl t
+options.)
+.It Fl b
+Show the maximum allowed sizes for active semaphores, message queues,
+and shared memory segments.
+The
+.Dq maximum allowed size
+is the maximum number of bytes in a message on a message queue,
+the size of a shared memory segment,
+or the number of semaphores in a set of semaphores.
+.It Fl c
+Show the creator's name and group for active semaphores, message queues,
+and shared memory segments.
+.It Fl m
+Display information about active shared memory segments.
+.It Fl o
+Show outstanding usage for active message queues,
+and shared memory segments.
+The
+.Dq outstanding usage
+is the number of messages in a message queue, or the number
+of processes attached to a shared memory segment.
+.It Fl p
+Show the process ID information for active semaphores, message queues,
+and shared memory segments.
+The
+.Dq process ID information
+is the last process to send a message to or receive a message from
+a message queue,
+the process that created a semaphore, or the last process to attach
+or detach a shared memory segment.
+.It Fl q
+Display information about active message queues.
+.It Fl s
+Display information about active semaphores.
+.It Fl t
+Show access times for active semaphores, message queues,
+and shared memory segments.
+The access times is the time
+of the last control operation on an IPC object,
+the last send or receive of a message,
+the last attach or detach of a shared memory segment,
+or the last operation on a semaphore.
+.It Fl M
+Display system information about shared memory.
+.It Fl Q
+Display system information about messages queues.
+.It Fl S
+Display system information about semaphores.
+.It Fl T
+Display system information about shared memory, message queues
+and semaphores.
+(This is shorthand for specifying the
+.Fl M ,
+.Fl Q ,
+and
+.Fl S
+options.)
+.El
+.Pp
+If none of the
+.Fl M ,
+.Fl m ,
+.Fl Q ,
+.Fl q ,
+.Fl S ,
+.Fl s ,
+or
+.Fl T
+options are specified, information about all active IPC facilities is
+listed.
+.Sh RESTRICTIONS
+System data structures may change while
+.Nm
+is running; the output of
+.Nm
+is not guaranteed to be consistent.
+.Sh SEE ALSO
+.Xr ipcrm 1 ,
+.Xr shmat 2 ,
+.Xr shmctl 2 ,
+.Xr shmdt 2 ,
+.Xr shmget 2
+.Sh AUTHORS
+.An Thorsten Lockert Aq Mt tholo@sigmasoft.com
+.Sh BUGS
+This manual page is woefully incomplete, because it does not
+at all attempt to explain the information printed by
+.Nm .
diff --git a/usr.bin/ipcs/ipcs.c b/usr.bin/ipcs/ipcs.c
new file mode 100644
index 0000000..8fbae90
--- /dev/null
+++ b/usr.bin/ipcs/ipcs.c
@@ -0,0 +1,678 @@
+/* $NetBSD: ipcs.c,v 1.43 2014/06/11 14:57:55 joerg Exp $ */
+
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Simon Burge.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1994 SigmaSoft, Th. Lockert <tholo@sigmasoft.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/inttypes.h>
+#include <sys/ipc.h>
+#include <sys/sem.h>
+#include <sys/shm.h>
+#include <sys/msg.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <limits.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define SHMINFO 1
+#define SHMTOTAL 2
+#define MSGINFO 4
+#define MSGTOTAL 8
+#define SEMINFO 16
+#define SEMTOTAL 32
+
+#define BIGGEST 1
+#define CREATOR 2
+#define OUTSTANDING 4
+#define PID 8
+#define TIME 16
+
+static int display = 0;
+static int option = 0;
+
+static void cvt_time(time_t, char *, size_t);
+static char *fmt_perm(u_short);
+static void msg_sysctl(void);
+static void sem_sysctl(void);
+static void shm_sysctl(void);
+static void show_msginfo(time_t, time_t, time_t, int, u_int64_t, mode_t,
+ uid_t, gid_t, uid_t, gid_t, u_int64_t, u_int64_t, u_int64_t, pid_t, pid_t);
+static void show_msginfo_hdr(void);
+static void show_msgtotal(struct msginfo *);
+static void show_seminfo_hdr(void);
+static void show_seminfo(time_t, time_t, int, u_int64_t, mode_t, uid_t,
+ gid_t, uid_t, gid_t, int16_t);
+static void show_semtotal(struct seminfo *);
+static void show_shminfo(time_t, time_t, time_t, int, u_int64_t, mode_t,
+ uid_t, gid_t, uid_t, gid_t, u_int32_t, u_int64_t, pid_t, pid_t);
+static void show_shminfo_hdr(void);
+static void show_shmtotal(struct shminfo *);
+static void usage(void) __dead;
+static void unconfsem(void);
+static void unconfmsg(void);
+static void unconfshm(void);
+
+static void
+unconfsem(void)
+{
+ warnx("SVID semaphores facility not configured in the system");
+}
+
+static void
+unconfmsg(void)
+{
+ warnx("SVID messages facility not configured in the system");
+}
+
+static void
+unconfshm(void)
+{
+ warnx("SVID shared memory facility not configured in the system");
+}
+
+static char *
+fmt_perm(u_short mode)
+{
+ static char buffer[12];
+
+ buffer[0] = '-';
+ buffer[1] = '-';
+ buffer[2] = ((mode & 0400) ? 'r' : '-');
+ buffer[3] = ((mode & 0200) ? 'w' : '-');
+ buffer[4] = ((mode & 0100) ? 'a' : '-');
+ buffer[5] = ((mode & 0040) ? 'r' : '-');
+ buffer[6] = ((mode & 0020) ? 'w' : '-');
+ buffer[7] = ((mode & 0010) ? 'a' : '-');
+ buffer[8] = ((mode & 0004) ? 'r' : '-');
+ buffer[9] = ((mode & 0002) ? 'w' : '-');
+ buffer[10] = ((mode & 0001) ? 'a' : '-');
+ buffer[11] = '\0';
+ return (&buffer[0]);
+}
+
+static void
+cvt_time(time_t t, char *buf, size_t buflen)
+{
+ struct tm *tm;
+
+ if (t == 0)
+ (void)strlcpy(buf, "no-entry", buflen);
+ else {
+ tm = localtime(&t);
+ (void)snprintf(buf, buflen, "%2d:%02d:%02d",
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+ }
+}
+int
+main(int argc, char *argv[])
+{
+ int i;
+ time_t now;
+
+ while ((i = getopt(argc, argv, "MmQqSsabcoptT")) != -1)
+ switch (i) {
+ case 'M':
+ display |= SHMTOTAL;
+ break;
+ case 'm':
+ display |= SHMINFO;
+ break;
+ case 'Q':
+ display |= MSGTOTAL;
+ break;
+ case 'q':
+ display |= MSGINFO;
+ break;
+ case 'S':
+ display |= SEMTOTAL;
+ break;
+ case 's':
+ display |= SEMINFO;
+ break;
+ case 'T':
+ display |= SHMTOTAL | MSGTOTAL | SEMTOTAL;
+ break;
+ case 'a':
+ option |= BIGGEST | CREATOR | OUTSTANDING | PID | TIME;
+ break;
+ case 'b':
+ option |= BIGGEST;
+ break;
+ case 'c':
+ option |= CREATOR;
+ break;
+ case 'o':
+ option |= OUTSTANDING;
+ break;
+ case 'p':
+ option |= PID;
+ break;
+ case 't':
+ option |= TIME;
+ break;
+ default:
+ usage();
+ }
+
+ if (argc - optind > 0)
+ usage();
+
+ (void)time(&now);
+ (void)printf("IPC status from <running system> as of %s\n",
+ /* and extra \n from ctime(3) */
+ ctime(&now));
+
+ if (display == 0)
+ display = SHMINFO | MSGINFO | SEMINFO;
+
+ if (display & (MSGINFO | MSGTOTAL))
+ msg_sysctl();
+ if (display & (SHMINFO | SHMTOTAL))
+ shm_sysctl();
+ if (display & (SEMINFO | SEMTOTAL))
+ sem_sysctl();
+ return 0;
+}
+
+static void
+show_msgtotal(struct msginfo *msginfo)
+{
+ (void)printf("msginfo:\n");
+ (void)printf("\tmsgmax: %6d\t(max characters in a message)\n",
+ msginfo->msgmax);
+ (void)printf("\tmsgmni: %6d\t(# of message queues)\n",
+ msginfo->msgmni);
+ (void)printf("\tmsgmnb: %6d\t(max characters in a message queue)\n",
+ msginfo->msgmnb);
+ (void)printf("\tmsgtql: %6d\t(max # of messages in system)\n",
+ msginfo->msgtql);
+ (void)printf("\tmsgssz: %6d\t(size of a message segment)\n",
+ msginfo->msgssz);
+ (void)printf("\tmsgseg: %6d\t(# of message segments in system)\n\n",
+ msginfo->msgseg);
+}
+
+static void
+show_shmtotal(struct shminfo *shminfo)
+{
+ (void)printf("shminfo:\n");
+ (void)printf("\tshmmax: %" PRIu64 "\t(max shared memory segment size)\n",
+ shminfo->shmmax);
+ (void)printf("\tshmmin: %7d\t(min shared memory segment size)\n",
+ shminfo->shmmin);
+ (void)printf("\tshmmni: %7d\t(max number of shared memory identifiers)\n",
+ shminfo->shmmni);
+ (void)printf("\tshmseg: %7d\t(max shared memory segments per process)\n",
+ shminfo->shmseg);
+ (void)printf("\tshmall: %7d\t(max amount of shared memory in pages)\n\n",
+ shminfo->shmall);
+}
+
+static void
+show_semtotal(struct seminfo *seminfo)
+{
+ (void)printf("seminfo:\n");
+ (void)printf("\tsemmap: %6d\t(# of entries in semaphore map)\n",
+ seminfo->semmap);
+ (void)printf("\tsemmni: %6d\t(# of semaphore identifiers)\n",
+ seminfo->semmni);
+ (void)printf("\tsemmns: %6d\t(# of semaphores in system)\n",
+ seminfo->semmns);
+ (void)printf("\tsemmnu: %6d\t(# of undo structures in system)\n",
+ seminfo->semmnu);
+ (void)printf("\tsemmsl: %6d\t(max # of semaphores per id)\n",
+ seminfo->semmsl);
+ (void)printf("\tsemopm: %6d\t(max # of operations per semop call)\n",
+ seminfo->semopm);
+ (void)printf("\tsemume: %6d\t(max # of undo entries per process)\n",
+ seminfo->semume);
+ (void)printf("\tsemusz: %6d\t(size in bytes of undo structure)\n",
+ seminfo->semusz);
+ (void)printf("\tsemvmx: %6d\t(semaphore maximum value)\n",
+ seminfo->semvmx);
+ (void)printf("\tsemaem: %6d\t(adjust on exit max value)\n\n",
+ seminfo->semaem);
+}
+
+static void
+show_msginfo_hdr(void)
+{
+ (void)printf("Message Queues:\n");
+ (void)printf("T ID KEY MODE OWNER GROUP");
+ if (option & CREATOR)
+ (void)printf(" CREATOR CGROUP");
+ if (option & OUTSTANDING)
+ (void)printf(" CBYTES QNUM");
+ if (option & BIGGEST)
+ (void)printf(" QBYTES");
+ if (option & PID)
+ (void)printf(" LSPID LRPID");
+ if (option & TIME)
+ (void)printf(" STIME RTIME CTIME");
+ (void)printf("\n");
+}
+
+static void
+show_msginfo(time_t s_time, time_t r_time, time_t c_time, int ipcid,
+ u_int64_t key,
+ mode_t mode, uid_t uid, gid_t gid, uid_t cuid, gid_t cgid,
+ u_int64_t cbytes, u_int64_t qnum, u_int64_t qbytes, pid_t lspid,
+ pid_t lrpid)
+{
+ char s_time_buf[100], r_time_buf[100], c_time_buf[100];
+
+ if (option & TIME) {
+ cvt_time(s_time, s_time_buf, sizeof(s_time_buf));
+ cvt_time(r_time, r_time_buf, sizeof(r_time_buf));
+ cvt_time(c_time, c_time_buf, sizeof(c_time_buf));
+ }
+
+ (void)printf("q %9d %10lld %s %8s %8s", ipcid, (long long)key, fmt_perm(mode),
+ user_from_uid(uid, 0), group_from_gid(gid, 0));
+
+ if (option & CREATOR)
+ (void)printf(" %8s %8s", user_from_uid(cuid, 0),
+ group_from_gid(cgid, 0));
+
+ if (option & OUTSTANDING)
+ (void)printf(" %6lld %5lld", (long long)cbytes, (long long)qnum);
+
+ if (option & BIGGEST)
+ (void)printf(" %6lld", (long long)qbytes);
+
+ if (option & PID)
+ (void)printf(" %5d %5d", lspid, lrpid);
+
+ if (option & TIME)
+ (void)printf(" %s %s %s", s_time_buf, r_time_buf, c_time_buf);
+
+ (void)printf("\n");
+}
+
+static void
+show_shminfo_hdr(void)
+{
+ (void)printf("Shared Memory:\n");
+ (void)printf("T ID KEY MODE OWNER GROUP");
+ if (option & CREATOR)
+ (void)printf(" CREATOR CGROUP");
+ if (option & OUTSTANDING)
+ (void)printf(" NATTCH");
+ if (option & BIGGEST)
+ (void)printf(" SEGSZ");
+ if (option & PID)
+ (void)printf(" CPID LPID");
+ if (option & TIME)
+ (void)printf(" ATIME DTIME CTIME");
+ (void)printf("\n");
+}
+
+static void
+show_shminfo(time_t atime, time_t dtime, time_t c_time, int ipcid, u_int64_t key,
+ mode_t mode, uid_t uid, gid_t gid, uid_t cuid, gid_t cgid,
+ u_int32_t nattch, u_int64_t segsz, pid_t cpid, pid_t lpid)
+{
+ char atime_buf[100], dtime_buf[100], c_time_buf[100];
+
+ if (option & TIME) {
+ cvt_time(atime, atime_buf, sizeof(atime_buf));
+ cvt_time(dtime, dtime_buf, sizeof(dtime_buf));
+ cvt_time(c_time, c_time_buf, sizeof(c_time_buf));
+ }
+
+ (void)printf("m %9d %10lld %s %8s %8s", ipcid, (long long)key, fmt_perm(mode),
+ user_from_uid(uid, 0), group_from_gid(gid, 0));
+
+ if (option & CREATOR)
+ (void)printf(" %8s %8s", user_from_uid(cuid, 0),
+ group_from_gid(cgid, 0));
+
+ if (option & OUTSTANDING)
+ (void)printf(" %6d", nattch);
+
+ if (option & BIGGEST)
+ (void)printf(" %7llu", (long long)segsz);
+
+ if (option & PID)
+ (void)printf(" %5d %5d", cpid, lpid);
+
+ if (option & TIME)
+ (void)printf(" %s %s %s",
+ atime_buf,
+ dtime_buf,
+ c_time_buf);
+
+ (void)printf("\n");
+}
+
+static void
+show_seminfo_hdr(void)
+{
+ (void)printf("Semaphores:\n");
+ (void)printf("T ID KEY MODE OWNER GROUP");
+ if (option & CREATOR)
+ (void)printf(" CREATOR CGROUP");
+ if (option & BIGGEST)
+ (void)printf(" NSEMS");
+ if (option & TIME)
+ (void)printf(" OTIME CTIME");
+ (void)printf("\n");
+}
+
+static void
+show_seminfo(time_t otime, time_t c_time, int ipcid, u_int64_t key, mode_t mode,
+ uid_t uid, gid_t gid, uid_t cuid, gid_t cgid, int16_t nsems)
+{
+ char c_time_buf[100], otime_buf[100];
+
+ if (option & TIME) {
+ cvt_time(otime, otime_buf, sizeof(otime_buf));
+ cvt_time(c_time, c_time_buf, sizeof(c_time_buf));
+ }
+
+ (void)printf("s %9d %10lld %s %8s %8s", ipcid, (long long)key, fmt_perm(mode),
+ user_from_uid(uid, 0), group_from_gid(gid, 0));
+
+ if (option & CREATOR)
+ (void)printf(" %8s %8s", user_from_uid(cuid, 0),
+ group_from_gid(cgid, 0));
+
+ if (option & BIGGEST)
+ (void)printf(" %5d", nsems);
+
+ if (option & TIME)
+ (void)printf(" %s %s", otime_buf, c_time_buf);
+
+ (void)printf("\n");
+}
+
+static void
+msg_sysctl(void)
+{
+ struct msg_sysctl_info *msgsi;
+ void *buf;
+ int mib[4];
+ size_t len;
+ int i, valid;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_MSG;
+ len = sizeof(valid);
+ if (sysctl(mib, 3, &valid, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_MSG)");
+ return;
+ }
+ if (!valid) {
+ unconfmsg();
+ return;
+ }
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_INFO;
+ mib[3] = KERN_SYSVIPC_MSG_INFO;
+
+ if (!(display & MSGINFO)) {
+ /* totals only */
+ len = sizeof(struct msginfo);
+ } else {
+ if (sysctl(mib, 4, NULL, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_MSG_INFO)");
+ return;
+ }
+ }
+
+ if ((buf = malloc(len)) == NULL)
+ err(1, "malloc");
+ msgsi = (struct msg_sysctl_info *)buf;
+ if (sysctl(mib, 4, msgsi, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_MSG_INFO)");
+ goto done;
+ }
+
+ if (display & MSGTOTAL)
+ show_msgtotal(&msgsi->msginfo);
+
+ if (display & MSGINFO) {
+ show_msginfo_hdr();
+ for (i = 0; i < msgsi->msginfo.msgmni; i++) {
+ struct msgid_ds_sysctl *msqptr = &msgsi->msgids[i];
+ if (msqptr->msg_qbytes != 0)
+ show_msginfo(msqptr->msg_stime,
+ msqptr->msg_rtime,
+ msqptr->msg_ctime,
+ IXSEQ_TO_IPCID(i, msqptr->msg_perm),
+ msqptr->msg_perm._key,
+ msqptr->msg_perm.mode,
+ msqptr->msg_perm.uid,
+ msqptr->msg_perm.gid,
+ msqptr->msg_perm.cuid,
+ msqptr->msg_perm.cgid,
+ msqptr->_msg_cbytes,
+ msqptr->msg_qnum,
+ msqptr->msg_qbytes,
+ msqptr->msg_lspid,
+ msqptr->msg_lrpid);
+ }
+ (void)printf("\n");
+ }
+done:
+ free(buf);
+}
+
+static void
+shm_sysctl(void)
+{
+ struct shm_sysctl_info *shmsi;
+ void *buf;
+ int mib[4];
+ size_t len;
+ uint32_t i;
+ long valid;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_SHM;
+ len = sizeof(valid);
+ if (sysctl(mib, 3, &valid, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_SHM)");
+ return;
+ }
+ if (!valid) {
+ unconfshm();
+ return;
+ }
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_INFO;
+ mib[3] = KERN_SYSVIPC_SHM_INFO;
+
+ if (!(display & SHMINFO)) {
+ /* totals only */
+ len = sizeof(struct shminfo);
+ } else {
+ if (sysctl(mib, 4, NULL, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_SHM_INFO)");
+ return;
+ }
+ }
+
+ if ((buf = malloc(len)) == NULL)
+ err(1, "malloc");
+ shmsi = (struct shm_sysctl_info *)buf;
+ if (sysctl(mib, 4, shmsi, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_SHM_INFO)");
+ goto done;
+ }
+
+ if (display & SHMTOTAL)
+ show_shmtotal(&shmsi->shminfo);
+
+ if (display & SHMINFO) {
+ show_shminfo_hdr();
+ for (i = 0; i < shmsi->shminfo.shmmni; i++) {
+ struct shmid_ds_sysctl *shmptr = &shmsi->shmids[i];
+ if (shmptr->shm_perm.mode & 0x0800)
+ show_shminfo(shmptr->shm_atime,
+ shmptr->shm_dtime,
+ shmptr->shm_ctime,
+ IXSEQ_TO_IPCID(i, shmptr->shm_perm),
+ shmptr->shm_perm._key,
+ shmptr->shm_perm.mode,
+ shmptr->shm_perm.uid,
+ shmptr->shm_perm.gid,
+ shmptr->shm_perm.cuid,
+ shmptr->shm_perm.cgid,
+ shmptr->shm_nattch,
+ shmptr->shm_segsz,
+ shmptr->shm_cpid,
+ shmptr->shm_lpid);
+ }
+ (void)printf("\n");
+ }
+done:
+ free(buf);
+}
+
+static void
+sem_sysctl(void)
+{
+ struct sem_sysctl_info *semsi;
+ void *buf;
+ int mib[4];
+ size_t len;
+ int i, valid;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_SEM;
+ len = sizeof(valid);
+ if (sysctl(mib, 3, &valid, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_SEM)");
+ return;
+ }
+ if (!valid) {
+ unconfsem();
+ return;
+ }
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SYSVIPC;
+ mib[2] = KERN_SYSVIPC_INFO;
+ mib[3] = KERN_SYSVIPC_SEM_INFO;
+
+ if (!(display & SEMINFO)) {
+ /* totals only */
+ len = sizeof(struct seminfo);
+ } else {
+ if (sysctl(mib, 4, NULL, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_SEM_INFO)");
+ return;
+ }
+ }
+
+ if ((buf = malloc(len)) == NULL)
+ err(1, "malloc");
+ semsi = (struct sem_sysctl_info *)buf;
+ if (sysctl(mib, 4, semsi, &len, NULL, 0) < 0) {
+ warn("sysctl(KERN_SYSVIPC_SEM_INFO)");
+ goto done;
+ }
+
+ if (display & SEMTOTAL)
+ show_semtotal(&semsi->seminfo);
+
+ if (display & SEMINFO) {
+ show_seminfo_hdr();
+ for (i = 0; i < semsi->seminfo.semmni; i++) {
+ struct semid_ds_sysctl *semaptr = &semsi->semids[i];
+ if ((semaptr->sem_perm.mode & SEM_ALLOC) != 0)
+ show_seminfo(semaptr->sem_otime,
+ semaptr->sem_ctime,
+ IXSEQ_TO_IPCID(i, semaptr->sem_perm),
+ semaptr->sem_perm._key,
+ semaptr->sem_perm.mode,
+ semaptr->sem_perm.uid,
+ semaptr->sem_perm.gid,
+ semaptr->sem_perm.cuid,
+ semaptr->sem_perm.cgid,
+ semaptr->sem_nsems);
+ }
+ (void)printf("\n");
+ }
+done:
+ free(buf);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "Usage: %s [-abcmopqstMQST]\n",
+ getprogname());
+ exit(1);
+}
diff --git a/usr.bin/join/join.1 b/usr.bin/join/join.1
new file mode 100644
index 0000000..d40b099
--- /dev/null
+++ b/usr.bin/join/join.1
@@ -0,0 +1,206 @@
+.\" $NetBSD: join.1,v 1.15 2017/07/04 07:01:53 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)join.1 8.3 (Berkeley) 4/28/95
+.\"
+.Dd April 28, 1995
+.Dt JOIN 1
+.Os
+.Sh NAME
+.Nm join
+.Nd relational database operator
+.Sh SYNOPSIS
+.Nm
+.Op Fl a Ar file_number | Fl v Ar file_number
+.Op Fl e Ar string
+.Op Fl j Ar file_number field
+.Op Fl o Ar list
+.Op Fl t Ar char
+.Op Fl \&1 Ar field
+.Op Fl \&2 Ar field
+.Ar file1 file2
+.Sh DESCRIPTION
+The join utility performs an ``equality join'' on the specified files
+and writes the result to the standard output.
+The ``join field'' is the field in each file by which the files are compared.
+The first field in each line is used by default.
+There is one line in the output for each pair of lines in
+.Ar file1
+and
+.Ar file2
+which have identical join fields.
+Each output line consists of the join field, the remaining fields from
+.Ar file1
+and then the remaining fields from
+.Ar file2 .
+.Pp
+The default field separators are tab and space characters.
+In this case, multiple tabs and spaces count as a single field separator,
+and leading tabs and spaces are ignored.
+The default output field separator is a single space character.
+.Pp
+Many of the options use file and field numbers.
+Both file numbers and field numbers are 1 based, i.e. the first file on
+the command line is file number 1 and the first field is field number 1.
+The following options are available:
+.Bl -tag -width Fl
+.It Fl a Ar file_number
+In addition to the default output, produce a line for each unpairable
+line in file
+.Ar file_number .
+(The argument to
+.Fl a
+must not be preceded by a space; see the
+.Sx COMPATIBILITY
+section.)
+.It Fl e Ar string
+Replace empty output fields with
+.Ar string .
+.It Fl o Ar list
+The
+.Fl o
+option specifies the fields that will be output from each file for
+each line with matching join fields.
+Each element of
+.Ar list
+has the form
+.Ql file_number.field ,
+where
+.Ar file_number
+is a file number and
+.Ar field
+is a field number.
+The elements of list must be either comma (``,'') or whitespace separated.
+(The latter requires quoting to protect it from the shell, or, a simpler
+approach is to use multiple
+.Fl o
+options.)
+.It Fl t Ar char
+Use character
+.Ar char
+as a field delimiter for both input and output.
+Every occurrence of
+.Ar char
+in a line is significant.
+.It Fl v Ar file_number
+Do not display the default output, but display a line for each unpairable
+line in file
+.Ar file_number .
+The options
+.Fl v Ar 1
+and
+.Fl v Ar 2
+may be specified at the same time.
+.It Fl 1 Ar field
+Join on the
+.Ar field Ns 'th
+field of file 1.
+.It Fl 2 Ar field
+Join on the
+.Ar field Ns 'th
+field of file 2.
+.El
+.Pp
+When the default field delimiter characters are used, the files to be joined
+should be ordered in the collating sequence of
+.Xr sort 1 ,
+using the
+.Fl b
+option, on the fields on which they are to be joined, otherwise
+.Nm
+may not report all field matches.
+When the field delimiter characters are specified by the
+.Fl t
+option, the collating sequence should be the same as
+.Xr sort 1
+without the
+.Fl b
+option.
+.Pp
+If one of the arguments
+.Ar file1
+or
+.Ar file2
+is ``-'', the standard input is used.
+.Sh EXIT STATUS
+.Ex -std join
+.Sh COMPATIBILITY
+For compatibility with historic versions of
+.Nm ,
+the following options are available:
+.Bl -tag -width Fl
+.It Fl a
+In addition to the default output, produce a line for each unpairable line
+in both file 1 and file 2.
+(To distinguish between this and
+.Fl a Ar file_number ,
+.Nm
+currently requires that the latter not include any white space.)
+.It Fl j1 Ar field
+Join on the
+.Ar field Ns 'th
+field of file 1.
+.It Fl j2 Ar field
+Join on the
+.Ar field Ns 'th
+field of file 2.
+.It Fl j Ar field
+Join on the
+.Ar field Ns 'th
+field of both file 1 and file 2.
+.It Fl o Ar list ...
+Historical implementations of
+.Nm
+permitted multiple arguments to the
+.Fl o
+option.
+These arguments were of the form ``file_number.field_number'' as described
+for the current
+.Fl o
+option.
+This has obvious difficulties in the presence of files named ``1.2''.
+.El
+.Pp
+These options are available only so historic shell scripts don't require
+modification and should not be used.
+.Sh SEE ALSO
+.Xr awk 1 ,
+.Xr comm 1 ,
+.Xr paste 1 ,
+.Xr sort 1 ,
+.Xr uniq 1
+.Sh STANDARDS
+The
+.Nm
+command is expected to be
+.St -p1003.2
+compatible.
diff --git a/usr.bin/join/join.c b/usr.bin/join/join.c
new file mode 100644
index 0000000..b377ec2
--- /dev/null
+++ b/usr.bin/join/join.c
@@ -0,0 +1,637 @@
+/* $NetBSD: join.c,v 1.31 2011/09/04 20:27:52 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1991 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Steve Hayman of Indiana University, Michiro Hikida and David
+ * Goodenough.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)join.c 5.1 (Berkeley) 11/18/91";
+#else
+__RCSID("$NetBSD: join.c,v 1.31 2011/09/04 20:27:52 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * There's a structure per input file which encapsulates the state of the
+ * file. We repeatedly read lines from each file until we've read in all
+ * the consecutive lines from the file with a common join field. Then we
+ * compare the set of lines with an equivalent set from the other file.
+ */
+typedef struct {
+ char *line; /* line */
+ u_long linealloc; /* line allocated count */
+ char **fields; /* line field(s) */
+ u_long fieldcnt; /* line field(s) count */
+ u_long fieldalloc; /* line field(s) allocated count */
+} LINE;
+
+static char nolineline[1] = { '\0' };
+static LINE noline = {nolineline, 0, 0, 0, 0}; /* arg for outfield if no line to output */
+
+typedef struct {
+ FILE *fp; /* file descriptor */
+ u_long joinf; /* join field (-1, -2, -j) */
+ int unpair; /* output unpairable lines (-a) */
+ int number; /* 1 for file 1, 2 for file 2 */
+
+ LINE *set; /* set of lines with same field */
+ u_long pushback; /* line on the stack */
+ u_long setcnt; /* set count */
+ u_long setalloc; /* set allocated count */
+} INPUT;
+
+static INPUT input1 = { NULL, 0, 0, 1, NULL, -1, 0, 0, },
+ input2 = { NULL, 0, 0, 2, NULL, -1, 0, 0, };
+
+typedef struct {
+ u_long fileno; /* file number */
+ u_long fieldno; /* field number */
+} OLIST;
+
+static OLIST *olist; /* output field list */
+static u_long olistcnt; /* output field list count */
+static u_long olistalloc; /* output field allocated count */
+
+static int joinout = 1; /* show lines with matched join fields (-v) */
+static int needsep; /* need separator character */
+static int spans = 1; /* span multiple delimiters (-t) */
+static char *empty; /* empty field replacement string (-e) */
+static const char *tabchar = " \t"; /* delimiter characters (-t) */
+
+static int cmp(LINE *, u_long, LINE *, u_long);
+__dead static void enomem(void);
+static void fieldarg(char *);
+static void joinlines(INPUT *, INPUT *);
+static void obsolete(char **);
+static void outfield(LINE *, u_long);
+static void outoneline(INPUT *, LINE *);
+static void outtwoline(INPUT *, LINE *, INPUT *, LINE *);
+static void slurp(INPUT *);
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ INPUT *F1, *F2;
+ int aflag, ch, cval, vflag;
+ char *end;
+
+ F1 = &input1;
+ F2 = &input2;
+
+ aflag = vflag = 0;
+ obsolete(argv);
+ while ((ch = getopt(argc, argv, "\01a:e:j:1:2:o:t:v:")) != -1) {
+ switch (ch) {
+ case '\01':
+ aflag = 1;
+ F1->unpair = F2->unpair = 1;
+ break;
+ case '1':
+ if ((F1->joinf = strtol(optarg, &end, 10)) < 1) {
+ warnx("-1 option field number less than 1");
+ usage();
+ }
+ if (*end) {
+ warnx("illegal field number -- %s", optarg);
+ usage();
+ }
+ --F1->joinf;
+ break;
+ case '2':
+ if ((F2->joinf = strtol(optarg, &end, 10)) < 1) {
+ warnx("-2 option field number less than 1");
+ usage();
+ }
+ if (*end) {
+ warnx("illegal field number -- %s", optarg);
+ usage();
+ }
+ --F2->joinf;
+ break;
+ case 'a':
+ aflag = 1;
+ switch(strtol(optarg, &end, 10)) {
+ case 1:
+ F1->unpair = 1;
+ break;
+ case 2:
+ F2->unpair = 1;
+ break;
+ default:
+ warnx("-a option file number not 1 or 2");
+ usage();
+ break;
+ }
+ if (*end) {
+ warnx("illegal file number -- %s", optarg);
+ usage();
+ }
+ break;
+ case 'e':
+ empty = optarg;
+ break;
+ case 'j':
+ if ((F1->joinf = F2->joinf =
+ strtol(optarg, &end, 10)) < 1) {
+ warnx("-j option field number less than 1");
+ usage();
+ }
+ if (*end) {
+ warnx("illegal field number -- %s", optarg);
+ usage();
+ }
+ --F1->joinf;
+ --F2->joinf;
+ break;
+ case 'o':
+ fieldarg(optarg);
+ break;
+ case 't':
+ spans = 0;
+ if (strlen(tabchar = optarg) != 1) {
+ warnx("illegal tab character specification");
+ usage();
+ }
+ break;
+ case 'v':
+ vflag = 1;
+ joinout = 0;
+ switch(strtol(optarg, &end, 10)) {
+ case 1:
+ F1->unpair = 1;
+ break;
+ case 2:
+ F2->unpair = 1;
+ break;
+ default:
+ warnx("-v option file number not 1 or 2");
+ usage();
+ break;
+ }
+ if (*end) {
+ warnx("illegal file number -- %s", optarg);
+ usage();
+ }
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (aflag && vflag)
+ errx(1, "-a and -v options mutually exclusive");
+
+ if (argc != 2)
+ usage();
+
+ /* Open the files; "-" means stdin. */
+ if (!strcmp(*argv, "-"))
+ F1->fp = stdin;
+ else if ((F1->fp = fopen(*argv, "r")) == NULL)
+ err(1, "%s", *argv);
+ ++argv;
+ if (!strcmp(*argv, "-"))
+ F2->fp = stdin;
+ else if ((F2->fp = fopen(*argv, "r")) == NULL)
+ err(1, "%s", *argv);
+ if (F1->fp == stdin && F2->fp == stdin)
+ errx(1, "only one input file may be stdin");
+
+ slurp(F1);
+ slurp(F2);
+ while (F1->setcnt && F2->setcnt) {
+ cval = cmp(F1->set, F1->joinf, F2->set, F2->joinf);
+ if (cval == 0) {
+ /* Oh joy, oh rapture, oh beauty divine! */
+ if (joinout)
+ joinlines(F1, F2);
+ slurp(F1);
+ slurp(F2);
+ } else if (cval < 0) {
+ /* File 1 takes the lead... */
+ if (F1->unpair)
+ joinlines(F1, NULL);
+ slurp(F1);
+ } else {
+ /* File 2 takes the lead... */
+ if (F2->unpair)
+ joinlines(F2, NULL);
+ slurp(F2);
+ }
+ }
+
+ /*
+ * Now that one of the files is used up, optionally output any
+ * remaining lines from the other file.
+ */
+ if (F1->unpair)
+ while (F1->setcnt) {
+ joinlines(F1, NULL);
+ slurp(F1);
+ }
+ if (F1->fp != stdin)
+ fclose(F1->fp);
+
+ if (F2->unpair)
+ while (F2->setcnt) {
+ joinlines(F2, NULL);
+ slurp(F2);
+ }
+ if (F2->fp != stdin)
+ fclose(F2->fp);
+
+ return 0;
+}
+
+static void
+slurp(INPUT *F)
+{
+ LINE *lp;
+ LINE tmp;
+ LINE *nline;
+ size_t len;
+ u_long cnt;
+ char *bp, *fieldp;
+ u_long nsize;
+
+ /*
+ * Read all of the lines from an input file that have the same
+ * join field.
+ */
+ for (F->setcnt = 0;; ++F->setcnt) {
+ /*
+ * If we're out of space to hold line structures, allocate
+ * more. Initialize the structure so that we know that this
+ * is new space.
+ */
+ if (F->setcnt == F->setalloc) {
+ cnt = F->setalloc;
+ if (F->setalloc == 0)
+ nsize = 64;
+ else
+ nsize = F->setalloc << 1;
+ if ((nline = realloc(F->set,
+ nsize * sizeof(LINE))) == NULL)
+ enomem();
+ F->set = nline;
+ F->setalloc = nsize;
+ memset(F->set + cnt, 0,
+ (F->setalloc - cnt) * sizeof(LINE));
+ }
+
+ /*
+ * Get any pushed back line, else get the next line. Allocate
+ * space as necessary. If taking the line from the stack swap
+ * the two structures so that we don't lose the allocated space.
+ * This could be avoided by doing another level of indirection,
+ * but it's probably okay as is.
+ */
+ lp = &F->set[F->setcnt];
+ if (F->pushback != (u_long)-1) {
+ tmp = F->set[F->setcnt];
+ F->set[F->setcnt] = F->set[F->pushback];
+ F->set[F->pushback] = tmp;
+ F->pushback = (u_long)-1;
+ continue;
+ }
+ if ((bp = fgetln(F->fp, &len)) == NULL)
+ return;
+ if (lp->linealloc <= len + 1) {
+ char *n;
+
+ if (lp->linealloc == 0)
+ nsize = 128;
+ else
+ nsize = lp->linealloc;
+ while (nsize <= len + 1)
+ nsize <<= 1;
+ if ((n = realloc(lp->line,
+ nsize * sizeof(char))) == NULL)
+ enomem();
+ lp->line = n;
+ lp->linealloc = nsize;
+ }
+ memmove(lp->line, bp, len);
+
+ /* Replace trailing newline, if it exists. */
+ if (bp[len - 1] == '\n')
+ lp->line[len - 1] = '\0';
+ else
+ lp->line[len] = '\0';
+ bp = lp->line;
+
+ /* Split the line into fields, allocate space as necessary. */
+ lp->fieldcnt = 0;
+ while ((fieldp = strsep(&bp, tabchar)) != NULL) {
+ if (spans && *fieldp == '\0')
+ continue;
+ if (lp->fieldcnt == lp->fieldalloc) {
+ char **n;
+
+ if (lp->fieldalloc == 0)
+ nsize = 16;
+ else
+ nsize = lp->fieldalloc << 1;
+ if ((n = realloc(lp->fields,
+ nsize * sizeof(char *))) == NULL)
+ enomem();
+ lp->fields = n;
+ lp->fieldalloc = nsize;
+ }
+ lp->fields[lp->fieldcnt++] = fieldp;
+ }
+
+ /* See if the join field value has changed. */
+ if (F->setcnt && cmp(lp, F->joinf, lp - 1, F->joinf)) {
+ F->pushback = F->setcnt;
+ break;
+ }
+ }
+}
+
+static int
+cmp(LINE *lp1, u_long fieldno1, LINE *lp2, u_long fieldno2)
+{
+
+ if (lp1->fieldcnt <= fieldno1)
+ return (lp2->fieldcnt <= fieldno2 ? 0 : 1);
+ if (lp2->fieldcnt <= fieldno2)
+ return (-1);
+ return (strcmp(lp1->fields[fieldno1], lp2->fields[fieldno2]));
+}
+
+static void
+joinlines(INPUT *F1, INPUT *F2)
+{
+ u_long cnt1, cnt2;
+
+ /*
+ * Output the results of a join comparison. The output may be from
+ * either file 1 or file 2 (in which case the first argument is the
+ * file from which to output) or from both.
+ */
+ if (F2 == NULL) {
+ for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
+ outoneline(F1, &F1->set[cnt1]);
+ return;
+ }
+ for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
+ for (cnt2 = 0; cnt2 < F2->setcnt; ++cnt2)
+ outtwoline(F1, &F1->set[cnt1], F2, &F2->set[cnt2]);
+}
+
+static void
+outoneline(INPUT *F, LINE *lp)
+{
+ u_long cnt;
+
+ /*
+ * Output a single line from one of the files, according to the
+ * join rules. This happens when we are writing unmatched single
+ * lines. Output empty fields in the right places.
+ */
+ if (olist)
+ for (cnt = 0; cnt < olistcnt; ++cnt) {
+ if (olist[cnt].fileno == (u_long)F->number)
+ outfield(lp, olist[cnt].fieldno);
+ else
+ outfield(&noline, 1);
+ }
+ else
+ for (cnt = 0; cnt < lp->fieldcnt; ++cnt)
+ outfield(lp, cnt);
+ (void)printf("\n");
+ if (ferror(stdout))
+ err(1, "stdout");
+ needsep = 0;
+}
+
+static void
+outtwoline(INPUT *F1, LINE *lp1, INPUT *F2, LINE *lp2)
+{
+ u_long cnt;
+
+ /* Output a pair of lines according to the join list (if any). */
+ if (olist) {
+ for (cnt = 0; cnt < olistcnt; ++cnt)
+ if (olist[cnt].fileno == 1)
+ outfield(lp1, olist[cnt].fieldno);
+ else /* if (olist[cnt].fileno == 2) */
+ outfield(lp2, olist[cnt].fieldno);
+ } else {
+ /*
+ * Output the join field, then the remaining fields from F1
+ * and F2.
+ */
+ outfield(lp1, F1->joinf);
+ for (cnt = 0; cnt < lp1->fieldcnt; ++cnt)
+ if (F1->joinf != cnt)
+ outfield(lp1, cnt);
+ for (cnt = 0; cnt < lp2->fieldcnt; ++cnt)
+ if (F2->joinf != cnt)
+ outfield(lp2, cnt);
+ }
+ (void)printf("\n");
+ if (ferror(stdout))
+ err(1, "stdout");
+ needsep = 0;
+}
+
+static void
+outfield(LINE *lp, u_long fieldno)
+{
+ if (needsep++)
+ (void)printf("%c", *tabchar);
+ if (!ferror(stdout)) {
+ if (lp->fieldcnt <= fieldno) {
+ if (empty != NULL)
+ (void)printf("%s", empty);
+ } else {
+ if (*lp->fields[fieldno] == '\0')
+ return;
+ (void)printf("%s", lp->fields[fieldno]);
+ }
+ }
+ if (ferror(stdout))
+ err(1, "stdout");
+}
+
+/*
+ * Convert an output list argument "2.1, 1.3, 2.4" into an array of output
+ * fields.
+ */
+static void
+fieldarg(char *option)
+{
+ u_long fieldno;
+ char *end, *token;
+ OLIST *n;
+
+ while ((token = strsep(&option, ", \t")) != NULL) {
+ if (*token == '\0')
+ continue;
+ if ((token[0] != '1' && token[0] != '2') || token[1] != '.')
+ errx(1, "malformed -o option field");
+ fieldno = strtol(token + 2, &end, 10);
+ if (*end)
+ errx(1, "malformed -o option field");
+ if (fieldno == 0)
+ errx(1, "field numbers are 1 based");
+ if (olistcnt == olistalloc) {
+ if ((n = realloc(olist,
+ (olistalloc + 50) * sizeof(OLIST))) == NULL)
+ enomem();
+ olist = n;
+ olistalloc += 50;
+ }
+ olist[olistcnt].fileno = token[0] - '0';
+ olist[olistcnt].fieldno = fieldno - 1;
+ ++olistcnt;
+ }
+}
+
+static void
+obsolete(char **argv)
+{
+ size_t len;
+ char **p, *ap, *t;
+
+ while ((ap = *++argv) != NULL) {
+ /* Return if "--". */
+ if (ap[0] == '-' && ap[1] == '-')
+ return;
+ switch (ap[1]) {
+ case 'a':
+ /*
+ * The original join allowed "-a", which meant the
+ * same as -a1 plus -a2. POSIX 1003.2, Draft 11.2
+ * only specifies this as "-a 1" and "a -2", so we
+ * have to use another option flag, one that is
+ * unlikely to ever be used or accidentally entered
+ * on the command line. (Well, we could reallocate
+ * the argv array, but that hardly seems worthwhile.)
+ */
+ if (ap[2] == '\0')
+ ap[1] = '\01';
+ break;
+ case 'j':
+ /*
+ * The original join allowed "-j[12] arg" and "-j arg".
+ * Convert the former to "-[12] arg". Don't convert
+ * the latter since getopt(3) can handle it.
+ */
+ switch(ap[2]) {
+ case '1':
+ if (ap[3] != '\0')
+ goto jbad;
+ ap[1] = '1';
+ ap[2] = '\0';
+ break;
+ case '2':
+ if (ap[3] != '\0')
+ goto jbad;
+ ap[1] = '2';
+ ap[2] = '\0';
+ break;
+ case '\0':
+ break;
+ default:
+jbad: errx(1, "illegal option -- %s", ap);
+ usage();
+ }
+ break;
+ case 'o':
+ /*
+ * The original join allowed "-o arg arg". Convert to
+ * "-o arg -o arg".
+ */
+ if (ap[2] != '\0')
+ break;
+ for (p = argv + 2; *p; ++p) {
+ if ((p[0][0] != '1' && p[0][0] != '2') ||
+ p[0][1] != '.')
+ break;
+ len = strlen(*p);
+ if (len - 2 != strspn(*p + 2, "0123456789"))
+ break;
+ if ((t = malloc(len + 3)) == NULL)
+ enomem();
+ t[0] = '-';
+ t[1] = 'o';
+ memmove(t + 2, *p, len + 1);
+ *p = t;
+ }
+ argv = p - 1;
+ break;
+ }
+ }
+}
+
+static void
+enomem(void)
+{
+ errx(1, "no memory");
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: %s [-a fileno | -v fileno] [-e string] [-j fileno field]\n"
+ " [-o list] [-t char] [-1 field] [-2 field] file1 file2\n",
+ getprogname());
+ exit(1);
+}
diff --git a/usr.bin/ldd/Makefile.common b/usr.bin/ldd/Makefile.common
new file mode 100644
index 0000000..6193b71
--- /dev/null
+++ b/usr.bin/ldd/Makefile.common
@@ -0,0 +1,16 @@
+# $NetBSD: Makefile.common,v 1.3 2013/04/25 07:12:46 matt Exp $
+
+LDELFSO=${NETBSDSRCDIR}/libexec/ld.elf_so
+CPPFLAGS+= -I${LDELFSO} -DLIBDIR=\"${LIBDIR}\"
+CPPFLAGS+= -D_RTLD_SOURCE
+.if !empty(MACHINE_ARCH:Mearm*)
+CPPFLAGS+= -I${NETBSDSRCDIR}/lib/libexecinfo
+.endif
+#CPPFLAGS+= -DDEBUG
+.PATH: ${LDELFSO}
+
+.if (${MACHINE_ARCH} == "sparc") || (${MACHINE_ARCH} == "sparc64") || \
+ (${MACHINE_ARCH} == "arm") || (${MACHINE_ARCH} == "m68k") || \
+ (${MACHINE_ARCH} == "powerpc") || (${MACHINE_ARCH:Mmips*} != "")
+CPPFLAGS+= -DVARPSZ
+.endif
diff --git a/usr.bin/ldd/Makefile.elf b/usr.bin/ldd/Makefile.elf
new file mode 100644
index 0000000..3acc9a9
--- /dev/null
+++ b/usr.bin/ldd/Makefile.elf
@@ -0,0 +1,10 @@
+# $NetBSD: Makefile.elf,v 1.5 2013/05/07 13:00:35 christos Exp $
+
+# Makefile fragment to build a (32 or 64 bit) libldd_elfxx.a.
+# Expects CPPFLAGS to have ELFSIZE set, and LIB to be set.
+
+SRCS= ldd_elfxx.c
+SRCS+= xmalloc.c debug.c expand.c map_object.c load.c search.c \
+ headers.c paths.c tls.c symver.c
+
+.include "Makefile.common"
diff --git a/usr.bin/ldd/build/Makefile b/usr.bin/ldd/build/Makefile
new file mode 100644
index 0000000..312eb00
--- /dev/null
+++ b/usr.bin/ldd/build/Makefile
@@ -0,0 +1,42 @@
+# $NetBSD: Makefile,v 1.5 2019/01/27 05:17:48 kre Exp $
+
+NOSANITIZER= # defined
+
+.include <bsd.own.mk> # for MKDYNAMICROOT definition
+
+PROG= ldd
+SRCS= ldd.c
+MAN= ldd.1
+
+.PATH: ${.CURDIR}/..
+
+.if ${OBJECT_FMTS:Melf32} != ""
+LIB_ELF32DIR!= cd ${.CURDIR}/../elf32 && ${PRINTOBJDIR}
+EXTRA_LIBS+= ${LIB_ELF32DIR}/libldd_elf32.a
+.else
+CPPFLAGS.ldd.c+= -DELF64_ONLY
+.endif
+
+.if (${MACHINE_ARCH} == "mips64el") || (${MACHINE_ARCH} == "mips64eb")
+LIB_ELF32COMPATDIR!= cd ${.CURDIR}/../elf32_compat && ${PRINTOBJDIR}
+EXTRA_LIBS+= ${LIB_ELF32COMPATDIR}/libldd_elf32_compat.a
+.endif
+
+.if ${OBJECT_FMTS:Melf64} != ""
+LIB_ELF64DIR!= cd ${.CURDIR}/../elf64 && ${PRINTOBJDIR}
+EXTRA_LIBS+= ${LIB_ELF64DIR}/libldd_elf64.a
+CPPFLAGS.ldd.c+= -DELFSIZE=64
+.else
+CPPFLAGS.ldd.c+= -DELFSIZE=32
+.endif
+
+LDADD+= ${EXTRA_LIBS}
+DPADD+= ${EXTRA_LIBS}
+
+.include "Makefile.common"
+
+.if (${MKDYNAMICROOT} == "no")
+LDSTATIC?= -static
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/ldd/dummy.c b/usr.bin/ldd/dummy.c
new file mode 100644
index 0000000..51f4a6e
--- /dev/null
+++ b/usr.bin/ldd/dummy.c
@@ -0,0 +1,3 @@
+/* $NetBSD: dummy.c,v 1.1 2009/01/07 00:39:24 mrg Exp $ */
+
+/* This file left intentially blank. */
diff --git a/usr.bin/ldd/elf32/Makefile b/usr.bin/ldd/elf32/Makefile
new file mode 100644
index 0000000..71e00b2
--- /dev/null
+++ b/usr.bin/ldd/elf32/Makefile
@@ -0,0 +1,33 @@
+# $NetBSD: Makefile,v 1.12 2019/01/27 05:16:55 kre Exp $
+
+NOSANITIZER= # defined
+
+.include <bsd.own.mk>
+.include <bsd.init.mk>
+
+RTLD_FUNCS = \
+ _rtld_tls_allocate \
+ _rtld_tls_free \
+
+.for _d in ${RTLD_FUNCS}
+CPPFLAGS+= -D${_d}=_elf32_${_d}
+.endfor
+
+CPPFLAGS+= -DELFSIZE=32
+LIB= ldd_elf32
+
+# XXX Force one member
+SRCS= dummy.c
+
+LIBISPRIVATE= yes
+.PATH: ${.CURDIR}/..
+
+.ifdef MLIBDIR
+CPPFLAGS+= -DRTLD_ARCH_SUBDIR=\"${MLIBDIR}\"
+.endif
+
+.if ${OBJECT_FMTS:Melf32} != ""
+.include "../Makefile.elf"
+.endif
+
+.include <bsd.lib.mk>
diff --git a/usr.bin/ldd/elf32_compat/Makefile b/usr.bin/ldd/elf32_compat/Makefile
new file mode 100644
index 0000000..e604c20
--- /dev/null
+++ b/usr.bin/ldd/elf32_compat/Makefile
@@ -0,0 +1,26 @@
+# $NetBSD: Makefile,v 1.4 2019/01/27 05:15:42 kre Exp $
+
+NOSANITIZER= # defined
+
+.include <bsd.own.mk>
+.include <bsd.init.mk>
+
+CPPFLAGS+= -DELFSIZE=32 -DELF32_COMPAT
+LIB= ldd_elf32_compat
+
+# XXX Force one member
+SRCS= dummy.c
+
+LIBISPRIVATE= yes
+.PATH: ${.CURDIR}/..
+
+.ifdef COMPAT_MLIBDIR
+MLIBDIR= ${COMPAT_MLIBDIR}
+CPPFLAGS+= -DRTLD_ARCH_SUBDIR=\"${MLIBDIR}\"
+.endif
+
+.ifdef MLIBDIR
+.include "../Makefile.elf"
+.endif
+
+.include <bsd.lib.mk>
diff --git a/usr.bin/ldd/elf64/Makefile b/usr.bin/ldd/elf64/Makefile
new file mode 100644
index 0000000..f5cb2ab
--- /dev/null
+++ b/usr.bin/ldd/elf64/Makefile
@@ -0,0 +1,51 @@
+# $NetBSD: Makefile,v 1.11 2019/01/27 05:14:45 kre Exp $
+
+NOSANITIZER= # defined
+
+.include <bsd.own.mk>
+
+CPPFLAGS+= -DELFSIZE=64
+LIB= ldd_elf64
+
+# XXX Force one member
+SRCS= dummy.c
+
+LIBISPRIVATE= yes
+.PATH: ${.CURDIR}/..
+
+.if ${OBJECT_FMTS:Melf64} != ""
+
+# XXX we need to make sure that we don't accidentally get the elf32
+# XXX versions of these.
+
+RTLD_FUNCS = \
+ _rtld_expand_path \
+ _rtld_digest_dynamic \
+ _rtld_digest_phdr \
+ _rtld_load_needed_objects \
+ _rtld_load_object \
+ _rtld_map_object \
+ _rtld_obj_free \
+ _rtld_obj_new \
+ _rtld_object_add_name \
+ _rtld_object_match_name \
+ _rtld_add_paths \
+ _rtld_process_hints \
+ _rtld_sysctl \
+ _rtld_tls_allocate \
+ _rtld_tls_free \
+ _rtld_load_library
+
+.for _d in ${RTLD_FUNCS}
+CPPFLAGS+= -D${_d}=_elf64_${_d}
+.endfor
+
+.if (${MACHINE_ARCH} == "mips64el") || (${MACHINE_ARCH} == "mips64eb")
+CPPFLAGS+= -DRTLD_ARCH_SUBDIR=\"${MLIBDIR}\"
+.endif
+
+.include "../Makefile.elf"
+
+.endif
+
+.include <bsd.lib.mk>
diff --git a/usr.bin/ldd/ldd.1 b/usr.bin/ldd/ldd.1
new file mode 100644
index 0000000..6e49e9c
--- /dev/null
+++ b/usr.bin/ldd/ldd.1
@@ -0,0 +1,126 @@
+.\" $NetBSD: ldd.1,v 1.20 2017/12/25 05:08:49 maya Exp $
+.\"
+.\" Copyright (c) 1998 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Paul Kranenburg.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd December 25, 2017
+.Dt LDD 1
+.Os
+.Sh NAME
+.Nm ldd
+.Nd list dynamic object dependencies
+.Sh SYNOPSIS
+.Nm
+.Op Fl o
+.Op Fl f Ar format
+.Ar program ...
+.Sh DESCRIPTION
+.Nm
+displays all shared objects that are needed to run the given program.
+Contrary to
+.Xr nm 1 ,
+the list includes
+.Dq indirect
+dependencies that are the result of needed shared objects which themselves
+depend on yet other shared objects.
+Zero, one or two
+.Fl f
+options may be given.
+The argument is a format string passed to
+.Xr rtld 1
+and allows customization of
+.Nm ldd Ns 's
+output.
+The first format argument is used for library objects and defaults to
+.Qq "\et-l%o.%m => %p\en" .
+The second format argument is used for non-library objects and defaults to
+.Qq "\et%o => %p\en" .
+.Pp
+These arguments are interpreted as format strings a la
+.Xr printf 3
+to customize the trace output and allow
+.Nm
+to be operated as a filter more conveniently.
+The following conversions can be used:
+.Bl -tag -width xxxx
+.It \&%a
+The main program's name
+.Po also known as
+.Dq __progname
+.Pc .
+.It \&%A
+The value of the environment variable
+.Ev LD_TRACE_LOADED_OBJECTS_PROGNAME
+in a.out and the program name from the argument vector from elf.
+.It \&%o
+The library name.
+.It \&%m
+The library's major version number.
+.It \&%n
+The library's minor version number (a.out only, ignored in elf).
+.It \&%p
+The full pathname as determined by
+.Nm rtld Ns 's
+library search rules.
+.It \&%x
+The library's load address
+.El
+.Pp
+Additionally,
+.Sy \en
+and
+.Sy \et
+are recognized and have their usual meaning.
+.Pp
+The
+.Fl o
+option is an alias for
+.Fl f
+.Ar \&%a:-l\&%o.\&%m => \&%p\en ,
+which makes
+.Nm
+behave analogously to
+.Ic nm Fl o .
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr ld 1 ,
+.Xr ld.elf_so 1 ,
+.Xr nm 1 ,
+.Xr rtld 1
+.Sh HISTORY
+A
+.Nm
+utility first appeared in SunOS 4.0, it appeared in its current form
+in
+.Nx 0.9a .
+.Sh BUGS
+The
+a.out
+.Nm
+actually runs the program it has been requested to analyze which in specially
+constructed environments can have security implications.
diff --git a/usr.bin/ldd/ldd.c b/usr.bin/ldd/ldd.c
new file mode 100644
index 0000000..a2794f8
--- /dev/null
+++ b/usr.bin/ldd/ldd.c
@@ -0,0 +1,237 @@
+/* $NetBSD: ldd.c,v 1.23 2017/12/25 05:08:49 maya Exp $ */
+
+/*-
+ * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Paul Kranenburg.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright 1996 John D. Polstra.
+ * Copyright 1996 Matt Thomas <matt@3am-software.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by John Polstra.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: ldd.c,v 1.23 2017/12/25 05:08:49 maya Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "debug.h"
+#include "rtld.h"
+#include "ldd.h"
+
+/*
+ * Data declarations.
+ */
+static char *error_message; /* Message for dlopen(), or NULL */
+bool _rtld_trust; /* False for setuid and setgid programs */
+/*
+ * This may be ELF64 or ELF32 but since they are used opaquely it doesn't
+ * really matter.
+ */
+Obj_Entry *_rtld_objlist; /* Head of linked list of shared objects */
+Obj_Entry **_rtld_objtail = &_rtld_objlist;
+ /* Link field of last object in list */
+u_int _rtld_objcount; /* Number of shared objects */
+u_int _rtld_objloads; /* Number of objects loaded */
+
+Obj_Entry *_rtld_objmain; /* The main program shared object */
+size_t _rtld_pagesz;
+
+Search_Path *_rtld_default_paths;
+Search_Path *_rtld_paths;
+Library_Xform *_rtld_xforms;
+
+static void usage(void) __dead;
+char *main_local;
+char *main_progname;
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s [-f <format 1>] [-f <format 2>] <filename>"
+ " ...\n", getprogname());
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *fmt1 = NULL, *fmt2 = NULL;
+ int c, exit_status = EXIT_SUCCESS;
+
+#ifdef DEBUG
+ debug = 1;
+#endif
+ while ((c = getopt(argc, argv, "f:o")) != -1) {
+ switch (c) {
+ case 'f':
+ if (fmt1) {
+ if (fmt2)
+ errx(1, "Too many formats");
+ fmt2 = optarg;
+ } else
+ fmt1 = optarg;
+ break;
+ case 'o':
+ if (fmt1 || fmt2)
+ errx(1, "Cannot use -o and -f together");
+ fmt1 = "%a:-l%o.%m => %p\n";
+ break;
+ default:
+ usage();
+ /*NOTREACHED*/
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc <= 0) {
+ usage();
+ /*NOTREACHED*/
+ }
+
+ for (; argc != 0; argc--, argv++) {
+ int fd;
+
+ fd = open(*argv, O_RDONLY);
+ if (fd == -1) {
+ exit_status = EXIT_FAILURE;
+ warn("%s", *argv);
+ continue;
+ }
+ if (elf_ldd(fd, *argv, fmt1, fmt2) == -1
+ /* Alpha never had 32 bit support. */
+#if (defined(_LP64) && !defined(ELF64_ONLY)) || defined(MIPS_N32)
+ && elf32_ldd(fd, *argv, fmt1, fmt2) == -1
+#if defined(__mips__) && 0 /* XXX this is still hosed for some reason */
+ && elf32_ldd_compat(fd, *argv, fmt1, fmt2) == -1
+#endif
+#endif
+ ) {
+ exit_status = EXIT_FAILURE;
+ warnx("%s", error_message);
+ }
+ close(fd);
+ }
+
+ return exit_status;
+}
+
+/*
+ * Error reporting function. Use it like printf. If formats the message
+ * into a buffer, and sets things up so that the next call to dlerror()
+ * will return the message.
+ */
+void
+_rtld_error(const char *fmt, ...)
+{
+ static char buf[512];
+ va_list ap;
+ va_start(ap, fmt);
+ xvsnprintf(buf, sizeof buf, fmt, ap);
+ error_message = buf;
+ va_end(ap);
+}
+
+char *
+dlerror()
+{
+ char *msg = error_message;
+ error_message = NULL;
+ return msg;
+}
+
+void
+_rtld_die(void)
+{
+ const char *msg = dlerror();
+
+ if (msg == NULL)
+ msg = "Fatal error";
+ xerrx(1, "%s", msg);
+}
+
+void
+_rtld_shared_enter(void)
+{
+}
+
+void
+_rtld_shared_exit(void)
+{
+}
+
+void
+_rtld_exclusive_enter(sigset_t *mask)
+{
+}
+
+void
+_rtld_exclusive_exit(sigset_t *mask)
+{
+}
diff --git a/usr.bin/ldd/ldd.h b/usr.bin/ldd/ldd.h
new file mode 100644
index 0000000..a3b4520
--- /dev/null
+++ b/usr.bin/ldd/ldd.h
@@ -0,0 +1,47 @@
+/* $NetBSD: ldd.h,v 1.7 2012/07/08 00:53:44 matt Exp $ */
+
+/*
+ * Copyright (c) 2008 Matthew R. Green
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+int elf32_ldd(int, char *, const char *, const char *);
+
+#ifdef _LP64
+#define LDD_ELF64
+#endif
+
+#ifdef LDD_ELF64
+int elf64_ldd(int, char *, const char *, const char *);
+#define elf_ldd elf64_ldd
+#elif defined(ELF32_COMPAT)
+#define elf_ldd elf32_compat_ldd
+#else
+#define elf_ldd elf32_ldd
+#endif
+
+extern char *main_local;
+extern char *main_progname;
diff --git a/usr.bin/ldd/ldd_elfxx.c b/usr.bin/ldd/ldd_elfxx.c
new file mode 100644
index 0000000..d5def64
--- /dev/null
+++ b/usr.bin/ldd/ldd_elfxx.c
@@ -0,0 +1,269 @@
+/* $NetBSD: ldd_elfxx.c,v 1.7 2017/01/10 21:11:25 christos Exp $ */
+
+/*-
+ * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Paul Kranenburg.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright 1996 John D. Polstra.
+ * Copyright 1996 Matt Thomas <matt@3am-software.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by John Polstra.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: ldd_elfxx.c,v 1.7 2017/01/10 21:11:25 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "debug.h"
+#include "rtld.h"
+#include "ldd.h"
+
+static void print_needed(Obj_Entry *, const char *, const char *);
+static void fmtprint(const char *, Obj_Entry *, const char *, const char *);
+
+/*
+ * elfxx_ldd() - bit-size independent ELF ldd implementation.
+ * returns 0 on success and -1 on failure.
+ */
+int
+ELFNAME(ldd)(int fd, char *path, const char *fmt1, const char *fmt2)
+{
+ struct stat st;
+
+ if (lseek(fd, 0, SEEK_SET) < 0 ||
+ fstat(fd, &st) < 0) {
+ _rtld_error("%s: %s", path, strerror(errno));
+ return -1;
+ }
+
+ _rtld_pagesz = sysconf(_SC_PAGESIZE);
+
+#ifdef RTLD_ARCH_SUBDIR
+ _rtld_add_paths(path, &_rtld_default_paths,
+ RTLD_DEFAULT_LIBRARY_PATH "/" RTLD_ARCH_SUBDIR);
+#endif
+ _rtld_add_paths(path, &_rtld_default_paths, RTLD_DEFAULT_LIBRARY_PATH);
+
+ _rtld_paths = NULL;
+ _rtld_trust = (st.st_mode & (S_ISUID | S_ISGID)) == 0;
+ if (_rtld_trust)
+ _rtld_add_paths(path, &_rtld_paths, getenv("LD_LIBRARY_PATH"));
+
+ _rtld_process_hints(path, &_rtld_paths, &_rtld_xforms, _PATH_LD_HINTS);
+ _rtld_objmain = _rtld_map_object(xstrdup(path), fd, &st);
+ if (_rtld_objmain == NULL)
+ return -1;
+
+ _rtld_objmain->path = xstrdup(path);
+ _rtld_digest_dynamic(path, _rtld_objmain);
+
+ /* Link the main program into the list of objects. */
+ *_rtld_objtail = _rtld_objmain;
+ _rtld_objtail = &_rtld_objmain->next;
+ ++_rtld_objmain->refcount;
+
+ (void) _rtld_load_needed_objects(_rtld_objmain, 0);
+
+ if (fmt1 == NULL)
+ printf("%s:\n", _rtld_objmain->path);
+ main_local = path;
+ main_progname = _rtld_objmain->path;
+ print_needed(_rtld_objmain, fmt1, fmt2);
+
+ while (_rtld_objlist != NULL) {
+ Obj_Entry *obj = _rtld_objlist;
+ _rtld_objlist = obj->next;
+ while (obj->rpaths != NULL) {
+ const Search_Path *rpath = obj->rpaths;
+ obj->rpaths = rpath->sp_next;
+ xfree(__UNCONST(rpath->sp_path));
+ xfree(__UNCONST(rpath));
+ }
+ while (obj->needed != NULL) {
+ const Needed_Entry *needed = obj->needed;
+ obj->needed = needed->next;
+ xfree(__UNCONST(needed));
+ }
+ (void) munmap(obj->mapbase, obj->mapsize);
+ xfree(obj->path);
+ xfree(obj);
+ }
+
+ _rtld_objmain = NULL;
+ _rtld_objtail = &_rtld_objlist;
+ /* Need to free _rtld_paths? */
+
+ return 0;
+}
+
+void
+fmtprint(const char *libname, Obj_Entry *obj, const char *fmt1,
+ const char *fmt2)
+{
+ const char *libpath = obj ? obj->path : "not found";
+ char libnamebuf[200];
+ char *libmajor = NULL;
+ const char *fmt;
+ char *cp;
+ int c;
+
+ if (strncmp(libname, "lib", 3) == 0 &&
+ (cp = strstr(libname, ".so")) != NULL) {
+ size_t i = cp - (libname + 3);
+
+ if (i >= sizeof(libnamebuf))
+ i = sizeof(libnamebuf) - 1;
+ (void)memcpy(libnamebuf, libname + 3, i);
+ libnamebuf[i] = '\0';
+ if (cp[3] && isdigit((unsigned char)cp[4]))
+ libmajor = &cp[4];
+ libname = libnamebuf;
+ }
+
+ if (fmt1 == NULL)
+ fmt1 = libmajor != NULL ?
+ "\t-l%o.%m => %p\n" :
+ "\t-l%o => %p\n";
+ if (fmt2 == NULL)
+ fmt2 = "\t%o => %p\n";
+
+ fmt = libname == libnamebuf ? fmt1 : fmt2;
+ while ((c = *fmt++) != '\0') {
+ switch (c) {
+ default:
+ putchar(c);
+ continue;
+ case '\\':
+ switch (c = *fmt) {
+ case '\0':
+ continue;
+ case 'n':
+ putchar('\n');
+ break;
+ case 't':
+ putchar('\t');
+ break;
+ }
+ break;
+ case '%':
+ switch (c = *fmt) {
+ case '\0':
+ continue;
+ case '%':
+ default:
+ putchar(c);
+ break;
+ case 'A':
+ printf("%s", main_local);
+ break;
+ case 'a':
+ printf("%s", main_progname);
+ break;
+ case 'o':
+ printf("%s", libname);
+ break;
+ case 'm':
+ printf("%s", libmajor);
+ break;
+ case 'n':
+ /* XXX: not supported for elf */
+ break;
+ case 'p':
+ printf("%s", libpath);
+ break;
+ case 'x':
+ printf("%p", obj ? obj->mapbase : 0);
+ break;
+ }
+ break;
+ }
+ ++fmt;
+ }
+}
+
+void
+print_needed(Obj_Entry *obj, const char *fmt1, const char *fmt2)
+{
+ const Needed_Entry *needed;
+
+ for (needed = obj->needed; needed != NULL; needed = needed->next) {
+ const char *libname = obj->strtab + needed->name;
+
+ if (needed->obj != NULL) {
+ if (!needed->obj->printed) {
+ fmtprint(libname, needed->obj, fmt1, fmt2);
+ needed->obj->printed = 1;
+ print_needed(needed->obj, fmt1, fmt2);
+ }
+ } else {
+ fmtprint(libname, needed->obj, fmt1, fmt2);
+ }
+ }
+}
diff --git a/usr.bin/locale/locale.1 b/usr.bin/locale/locale.1
new file mode 100644
index 0000000..5cd4b0e
--- /dev/null
+++ b/usr.bin/locale/locale.1
@@ -0,0 +1,113 @@
+.\" $NetBSD: locale.1,v 1.2 2003/07/04 07:28:14 wiz Exp $
+.\"
+.\" Copyright (c) 2003 Alexey Zelkin <phantom@FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" FreeBSD: src/usr.bin/locale/locale.1,v 1.3 2003/06/26 11:01:03 phantom Exp
+.\"
+.Dd July 4, 2003
+.Dt LOCALE 1
+.Os
+.Sh NAME
+.Nm locale
+.Nd get locale-specific information
+.Sh SYNOPSIS
+.Nm
+.Op Fl a | Fl m
+.Nm
+.Op Fl ck
+.Op Ar keyword ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is supposed to provide most locale specific information to
+the standard output.
+.Pp
+When
+.Nm
+is invoked without arguments it will print out a summary of the
+current locale environment depending on environment variable settings
+and internal status.
+.Pp
+When
+.Nm
+is invoked with arguments and no options specified it will print out
+.Em keyword Ns No 's
+value determined using current locale settings.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl a
+Write names of all available locales.
+While looking for locales
+.Nm
+will respect the
+.Ev PATH_LOCALE
+environment variable, and use it instead of the system default
+locale directory.
+.It Fl c
+Write the category name for the selected keywords.
+.It Fl k
+Write the name and value of the selected keywords.
+.It Fl m
+Write names of all available charmaps.
+.El
+.Sh IMPLEMENTATION DETAILS
+Special
+.Pf ( Fx Ns -
+/
+.Nx Ns -specific )
+keyword
+.Ar list
+can be used to retrieve a human readable list of available keywords.
+.Sh DIAGNOSTICS
+.Ex -std locale
+.Sh STANDARDS
+.Nm
+conforms to
+.St -p1003.1-2001 .
+.Sh HISTORY
+.Nm
+first appeared in
+.Nx 2.0 .
+.Sh AUTHORS
+This implementation of
+.Nm
+was originally written by
+.An Alexey Zelkin
+.Aq phantom@FreeBSD.org
+for
+.Fx .
+.Sh BUGS
+Since
+.Nx
+does not support
+.Em charmap Ns No s
+in their
+.Em POSIX
+meaning
+.Nm
+emulates the
+.Fl m
+option via CODESETs listing of all available locales.
diff --git a/usr.bin/locale/locale.c b/usr.bin/locale/locale.c
new file mode 100644
index 0000000..bd02d53
--- /dev/null
+++ b/usr.bin/locale/locale.c
@@ -0,0 +1,707 @@
+/* $NetBSD: locale.c,v 1.8 2012/01/20 16:31:30 joerg Exp $ */
+
+/*-
+ * Copyright (c) 2002, 2003 Alexey Zelkin <phantom@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * FreeBSD: src/usr.bin/locale/locale.c,v 1.10 2003/06/26 11:05:56 phantom Exp
+ */
+
+#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+__RCSID("$NetBSD: locale.c,v 1.8 2012/01/20 16:31:30 joerg Exp $");
+#endif /* LIBC_SCCS and not lint */
+
+/*
+ * XXX: implement missing era_* (LC_TIME) keywords (require libc &
+ * nl_langinfo(3) extensions)
+ *
+ * XXX: correctly handle reserved 'charmap' keyword and '-m' option (require
+ * localedef(1) implementation). Currently it's handled via
+ * nl_langinfo(CODESET).
+ */
+
+#include <sys/types.h>
+#include <assert.h>
+#include <dirent.h>
+#include <err.h>
+#include <locale.h>
+#include <langinfo.h>
+#include <limits.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stringlist.h>
+#include <unistd.h>
+
+#include "citrus_namespace.h"
+#include "citrus_region.h"
+#include "citrus_lookup.h"
+#include "setlocale_local.h"
+
+/* Local prototypes */
+void init_locales_list(void);
+void init_locales_list_alias(void);
+void list_charmaps(void);
+void list_locales(void);
+const char *lookup_localecat(int);
+char *kwval_lconv(int);
+int kwval_lookup(char *, char **, int *, int *);
+void showdetails(char *);
+void showkeywordslist(void);
+void showlocale(void);
+void usage(void);
+
+/* Global variables */
+static StringList *locales = NULL;
+
+int all_locales = 0;
+int all_charmaps = 0;
+int prt_categories = 0;
+int prt_keywords = 0;
+int more_params = 0;
+
+struct _lcinfo {
+ const char *name;
+ int id;
+} lcinfo [] = {
+ { "LC_CTYPE", LC_CTYPE },
+ { "LC_COLLATE", LC_COLLATE },
+ { "LC_TIME", LC_TIME },
+ { "LC_NUMERIC", LC_NUMERIC },
+ { "LC_MONETARY", LC_MONETARY },
+ { "LC_MESSAGES", LC_MESSAGES }
+};
+#define NLCINFO (sizeof(lcinfo)/sizeof(lcinfo[0]))
+
+/* ids for values not referenced by nl_langinfo() */
+#define KW_ZERO 10000
+#define KW_GROUPING (KW_ZERO+1)
+#define KW_INT_CURR_SYMBOL (KW_ZERO+2)
+#define KW_CURRENCY_SYMBOL (KW_ZERO+3)
+#define KW_MON_DECIMAL_POINT (KW_ZERO+4)
+#define KW_MON_THOUSANDS_SEP (KW_ZERO+5)
+#define KW_MON_GROUPING (KW_ZERO+6)
+#define KW_POSITIVE_SIGN (KW_ZERO+7)
+#define KW_NEGATIVE_SIGN (KW_ZERO+8)
+#define KW_INT_FRAC_DIGITS (KW_ZERO+9)
+#define KW_FRAC_DIGITS (KW_ZERO+10)
+#define KW_P_CS_PRECEDES (KW_ZERO+11)
+#define KW_P_SEP_BY_SPACE (KW_ZERO+12)
+#define KW_N_CS_PRECEDES (KW_ZERO+13)
+#define KW_N_SEP_BY_SPACE (KW_ZERO+14)
+#define KW_P_SIGN_POSN (KW_ZERO+15)
+#define KW_N_SIGN_POSN (KW_ZERO+16)
+#define KW_INT_P_CS_PRECEDES (KW_ZERO+17)
+#define KW_INT_P_SEP_BY_SPACE (KW_ZERO+18)
+#define KW_INT_N_CS_PRECEDES (KW_ZERO+19)
+#define KW_INT_N_SEP_BY_SPACE (KW_ZERO+20)
+#define KW_INT_P_SIGN_POSN (KW_ZERO+21)
+#define KW_INT_N_SIGN_POSN (KW_ZERO+22)
+
+struct _kwinfo {
+ const char *name;
+ int isstr; /* true - string, false - number */
+ int catid; /* LC_* */
+ int value_ref;
+ const char *comment;
+} kwinfo [] = {
+ { "charmap", 1, LC_CTYPE, CODESET, "" }, /* hack */
+
+ { "decimal_point", 1, LC_NUMERIC, RADIXCHAR, "" },
+ { "thousands_sep", 1, LC_NUMERIC, THOUSEP, "" },
+ { "grouping", 1, LC_NUMERIC, KW_GROUPING, "" },
+ { "radixchar", 1, LC_NUMERIC, RADIXCHAR,
+ "Same as decimal_point (BSD only)" }, /* compat */
+ { "thousep", 1, LC_NUMERIC, THOUSEP,
+ "Same as thousands_sep (BSD only)" }, /* compat */
+
+ { "int_curr_symbol", 1, LC_MONETARY, KW_INT_CURR_SYMBOL, "" },
+ { "currency_symbol", 1, LC_MONETARY, KW_CURRENCY_SYMBOL, "" },
+ { "mon_decimal_point", 1, LC_MONETARY, KW_MON_DECIMAL_POINT, "" },
+ { "mon_thousands_sep", 1, LC_MONETARY, KW_MON_THOUSANDS_SEP, "" },
+ { "mon_grouping", 1, LC_MONETARY, KW_MON_GROUPING, "" },
+ { "positive_sign", 1, LC_MONETARY, KW_POSITIVE_SIGN, "" },
+ { "negative_sign", 1, LC_MONETARY, KW_NEGATIVE_SIGN, "" },
+
+ { "int_frac_digits", 0, LC_MONETARY, KW_INT_FRAC_DIGITS, "" },
+ { "frac_digits", 0, LC_MONETARY, KW_FRAC_DIGITS, "" },
+ { "p_cs_precedes", 0, LC_MONETARY, KW_P_CS_PRECEDES, "" },
+ { "p_sep_by_space", 0, LC_MONETARY, KW_P_SEP_BY_SPACE, "" },
+ { "n_cs_precedes", 0, LC_MONETARY, KW_N_CS_PRECEDES, "" },
+ { "n_sep_by_space", 0, LC_MONETARY, KW_N_SEP_BY_SPACE, "" },
+ { "p_sign_posn", 0, LC_MONETARY, KW_P_SIGN_POSN, "" },
+ { "n_sign_posn", 0, LC_MONETARY, KW_N_SIGN_POSN, "" },
+ { "int_p_cs_precedes", 0, LC_MONETARY, KW_INT_P_CS_PRECEDES, "" },
+ { "int_p_sep_by_space", 0, LC_MONETARY, KW_INT_P_SEP_BY_SPACE, "" },
+ { "int_n_cs_precedes", 0, LC_MONETARY, KW_INT_N_CS_PRECEDES, "" },
+ { "int_n_sep_by_space", 0, LC_MONETARY, KW_INT_N_SEP_BY_SPACE, "" },
+ { "int_p_sign_posn", 0, LC_MONETARY, KW_INT_P_SIGN_POSN, "" },
+ { "int_n_sign_posn", 0, LC_MONETARY, KW_INT_N_SIGN_POSN, "" },
+
+ { "d_t_fmt", 1, LC_TIME, D_T_FMT, "" },
+ { "d_fmt", 1, LC_TIME, D_FMT, "" },
+ { "t_fmt", 1, LC_TIME, T_FMT, "" },
+ { "am_str", 1, LC_TIME, AM_STR, "" },
+ { "pm_str", 1, LC_TIME, PM_STR, "" },
+ { "t_fmt_ampm", 1, LC_TIME, T_FMT_AMPM, "" },
+ { "day_1", 1, LC_TIME, DAY_1, "" },
+ { "day_2", 1, LC_TIME, DAY_2, "" },
+ { "day_3", 1, LC_TIME, DAY_3, "" },
+ { "day_4", 1, LC_TIME, DAY_4, "" },
+ { "day_5", 1, LC_TIME, DAY_5, "" },
+ { "day_6", 1, LC_TIME, DAY_6, "" },
+ { "day_7", 1, LC_TIME, DAY_7, "" },
+ { "abday_1", 1, LC_TIME, ABDAY_1, "" },
+ { "abday_2", 1, LC_TIME, ABDAY_2, "" },
+ { "abday_3", 1, LC_TIME, ABDAY_3, "" },
+ { "abday_4", 1, LC_TIME, ABDAY_4, "" },
+ { "abday_5", 1, LC_TIME, ABDAY_5, "" },
+ { "abday_6", 1, LC_TIME, ABDAY_6, "" },
+ { "abday_7", 1, LC_TIME, ABDAY_7, "" },
+ { "mon_1", 1, LC_TIME, MON_1, "" },
+ { "mon_2", 1, LC_TIME, MON_2, "" },
+ { "mon_3", 1, LC_TIME, MON_3, "" },
+ { "mon_4", 1, LC_TIME, MON_4, "" },
+ { "mon_5", 1, LC_TIME, MON_5, "" },
+ { "mon_6", 1, LC_TIME, MON_6, "" },
+ { "mon_7", 1, LC_TIME, MON_7, "" },
+ { "mon_8", 1, LC_TIME, MON_8, "" },
+ { "mon_9", 1, LC_TIME, MON_9, "" },
+ { "mon_10", 1, LC_TIME, MON_10, "" },
+ { "mon_11", 1, LC_TIME, MON_11, "" },
+ { "mon_12", 1, LC_TIME, MON_12, "" },
+ { "abmon_1", 1, LC_TIME, ABMON_1, "" },
+ { "abmon_2", 1, LC_TIME, ABMON_2, "" },
+ { "abmon_3", 1, LC_TIME, ABMON_3, "" },
+ { "abmon_4", 1, LC_TIME, ABMON_4, "" },
+ { "abmon_5", 1, LC_TIME, ABMON_5, "" },
+ { "abmon_6", 1, LC_TIME, ABMON_6, "" },
+ { "abmon_7", 1, LC_TIME, ABMON_7, "" },
+ { "abmon_8", 1, LC_TIME, ABMON_8, "" },
+ { "abmon_9", 1, LC_TIME, ABMON_9, "" },
+ { "abmon_10", 1, LC_TIME, ABMON_10, "" },
+ { "abmon_11", 1, LC_TIME, ABMON_11, "" },
+ { "abmon_12", 1, LC_TIME, ABMON_12, "" },
+ { "era", 1, LC_TIME, ERA, "(unavailable)" },
+ { "era_d_fmt", 1, LC_TIME, ERA_D_FMT, "(unavailable)" },
+ { "era_d_t_fmt", 1, LC_TIME, ERA_D_T_FMT, "(unavailable)" },
+ { "era_t_fmt", 1, LC_TIME, ERA_T_FMT, "(unavailable)" },
+ { "alt_digits", 1, LC_TIME, ALT_DIGITS, "" },
+
+ { "yesexpr", 1, LC_MESSAGES, YESEXPR, "" },
+ { "noexpr", 1, LC_MESSAGES, NOEXPR, "" },
+ { "yesstr", 1, LC_MESSAGES, YESSTR,
+ "(POSIX legacy)" }, /* compat */
+ { "nostr", 1, LC_MESSAGES, NOSTR,
+ "(POSIX legacy)" } /* compat */
+
+};
+#define NKWINFO (sizeof(kwinfo)/sizeof(kwinfo[0]))
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ int tmp;
+
+ while ((ch = getopt(argc, argv, "ackm")) != -1) {
+ switch (ch) {
+ case 'a':
+ all_locales = 1;
+ break;
+ case 'c':
+ prt_categories = 1;
+ break;
+ case 'k':
+ prt_keywords = 1;
+ break;
+ case 'm':
+ all_charmaps = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* validate arguments */
+ if (all_locales && all_charmaps)
+ usage();
+ if ((all_locales || all_charmaps) && argc > 0)
+ usage();
+ if ((all_locales || all_charmaps) && (prt_categories || prt_keywords))
+ usage();
+ if ((prt_categories || prt_keywords) && argc <= 0)
+ usage();
+
+ /* process '-a' */
+ if (all_locales) {
+ list_locales();
+ exit(0);
+ }
+
+ /* process '-m' */
+ if (all_charmaps) {
+ list_charmaps();
+ exit(0);
+ }
+
+ /* check for special case '-k list' */
+ tmp = 0;
+ if (prt_keywords && argc > 0)
+ while (tmp < argc)
+ if (strcasecmp(argv[tmp++], "list") == 0) {
+ showkeywordslist();
+ exit(0);
+ }
+
+ /* process '-c' and/or '-k' */
+ if (prt_categories || prt_keywords || argc > 0) {
+ setlocale(LC_ALL, "");
+ while (argc > 0) {
+ showdetails(*argv);
+ argv++;
+ argc--;
+ }
+ exit(0);
+ }
+
+ /* no arguments, show current locale state */
+ showlocale();
+
+ return (0);
+}
+
+void
+usage(void)
+{
+ printf("usage: locale [ -a | -m ]\n"
+ " locale [ -ck ] name ...\n");
+ exit(1);
+}
+
+/*
+ * Output information about all available locales
+ *
+ * XXX actually output of this function does not guarantee that locale
+ * is really available to application, since it can be broken or
+ * inconsistent thus setlocale() will fail. Maybe add '-V' function to
+ * also validate these locales?
+ */
+void
+list_locales(void)
+{
+ size_t i;
+
+ init_locales_list();
+ for (i = 0; i < locales->sl_cur; i++) {
+ printf("%s\n", locales->sl_str[i]);
+ }
+}
+
+/*
+ * qsort() helper function
+ */
+static int
+scmp(const void *s1, const void *s2)
+{
+ return strcmp(*(const char **)s1, *(const char **)s2);
+}
+
+/*
+ * Output information about all available charmaps
+ *
+ * XXX this function is doing a task in hackish way, i.e. by scaning
+ * list of locales, spliting their codeset part and building list of
+ * them.
+ */
+void
+list_charmaps(void)
+{
+ size_t i;
+ char *s, *cs;
+ StringList *charmaps;
+
+ /* initialize StringList */
+ charmaps = sl_init();
+ if (charmaps == NULL)
+ err(1, "could not allocate memory");
+
+ /* fetch locales list */
+ init_locales_list();
+
+ /* split codesets and build their list */
+ for (i = 0; i < locales->sl_cur; i++) {
+ s = locales->sl_str[i];
+ if ((cs = strchr(s, '.')) != NULL) {
+ cs++;
+ if (sl_find(charmaps, cs) == NULL)
+ sl_add(charmaps, cs);
+ }
+ }
+
+ /* add US-ASCII, if not yet added */
+ if (sl_find(charmaps, "US-ASCII") == NULL)
+ sl_add(charmaps, "US-ASCII");
+
+ /* sort the list */
+ qsort(charmaps->sl_str, charmaps->sl_cur, sizeof(char *), scmp);
+
+ /* print results */
+ for (i = 0; i < charmaps->sl_cur; i++) {
+ printf("%s\n", charmaps->sl_str[i]);
+ }
+}
+
+/*
+ * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE
+ * environment variable is set)
+ */
+void
+init_locales_list(void)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ char *s;
+
+ /* why call this function twice ? */
+ if (locales != NULL)
+ return;
+
+ /* initialize StringList */
+ locales = sl_init();
+ if (locales == NULL)
+ err(1, "could not allocate memory");
+
+ /* get actual locales directory name */
+ setlocale(LC_CTYPE, "C");
+ if (_PathLocale == NULL)
+ errx(1, "unable to find locales storage");
+
+ /* open locales directory */
+ dirp = opendir(_PathLocale);
+ if (dirp == NULL)
+ err(1, "could not open directory '%s'", _PathLocale);
+
+ /* scan directory and store its contents except "." and ".." */
+ while ((dp = readdir(dirp)) != NULL) {
+ /* exclude "." and "..", _LOCALE_ALIAS_NAME */
+ if ((dp->d_name[0] != '.' || (dp->d_name[1] != '\0' &&
+ (dp->d_name[1] != '.' || dp->d_name[2] != '\0'))) &&
+ strcmp(_LOCALE_ALIAS_NAME, dp->d_name) != 0) {
+ s = strdup(dp->d_name);
+ if (s == NULL)
+ err(1, "could not allocate memory");
+ sl_add(locales, s);
+ }
+ }
+ closedir(dirp);
+
+ /* make sure that 'POSIX' and 'C' locales are present in the list.
+ * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but
+ * we also list 'C' for constistency
+ */
+ if (sl_find(locales, "POSIX") == NULL)
+ sl_add(locales, "POSIX");
+
+ if (sl_find(locales, "C") == NULL)
+ sl_add(locales, "C");
+
+ init_locales_list_alias();
+
+ /* make output nicer, sort the list */
+ qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp);
+}
+
+void
+init_locales_list_alias(void)
+{
+ char aliaspath[PATH_MAX];
+ struct _lookup *hlookup;
+ struct _region key, dat;
+ size_t n;
+ char *s, *t;
+
+ _DIAGASSERT(locales != NULL);
+ _DIAGASSERT(_PathLocale != NULL);
+
+ (void)snprintf(aliaspath, sizeof(aliaspath),
+ "%s/" _LOCALE_ALIAS_NAME, _PathLocale);
+
+ if (_lookup_seq_open(&hlookup, aliaspath,
+ _LOOKUP_CASE_SENSITIVE) == 0) {
+ while (_lookup_seq_next(hlookup, &key, &dat) == 0) {
+ n = _region_size((const struct _region *)&key);
+ s = _region_head((const struct _region *)&key);
+ for (t = s; n > 0 && *s!= '/'; --n, ++s);
+ n = (size_t)(s - t);
+ s = malloc(n + 1);
+ if (s == NULL)
+ err(1, "could not allocate memory");
+ memcpy(s, t, n);
+ s[n] = '\0';
+ if (sl_find(locales, s) == NULL)
+ sl_add(locales, s);
+ else
+ free(s);
+ }
+ _lookup_seq_close(hlookup);
+ }
+}
+
+/*
+ * Show current locale status, depending on environment variables
+ */
+void
+showlocale(void)
+{
+ size_t i;
+ const char *lang, *vval, *eval;
+
+ setlocale(LC_ALL, "");
+
+ lang = getenv("LANG");
+ if (lang == NULL) {
+ lang = "";
+ }
+ printf("LANG=\"%s\"\n", lang);
+ /* XXX: if LANG is null, then set it to "C" to get implied values? */
+
+ for (i = 0; i < NLCINFO; i++) {
+ vval = setlocale(lcinfo[i].id, NULL);
+ eval = getenv(lcinfo[i].name);
+ if (eval != NULL && !strcmp(eval, vval)
+ && strcmp(lang, vval)) {
+ /*
+ * Appropriate environment variable set, its value
+ * is valid and not overriden by LC_ALL
+ *
+ * XXX: possible side effect: if both LANG and
+ * overriden environment variable are set into same
+ * value, then it'll be assumed as 'implied'
+ */
+ printf("%s=\"%s\"\n", lcinfo[i].name, vval);
+ } else {
+ printf("%s=\"%s\"\n", lcinfo[i].name, vval);
+ }
+ }
+
+ vval = getenv("LC_ALL");
+ if (vval == NULL) {
+ vval = "";
+ }
+ printf("LC_ALL=\"%s\"\n", vval);
+}
+
+/*
+ * keyword value lookup helper (via localeconv())
+ */
+char *
+kwval_lconv(int id)
+{
+ struct lconv *lc;
+ char *rval;
+
+ rval = NULL;
+ lc = localeconv();
+ switch (id) {
+ case KW_GROUPING:
+ rval = lc->grouping;
+ break;
+ case KW_INT_CURR_SYMBOL:
+ rval = lc->int_curr_symbol;
+ break;
+ case KW_CURRENCY_SYMBOL:
+ rval = lc->currency_symbol;
+ break;
+ case KW_MON_DECIMAL_POINT:
+ rval = lc->mon_decimal_point;
+ break;
+ case KW_MON_THOUSANDS_SEP:
+ rval = lc->mon_thousands_sep;
+ break;
+ case KW_MON_GROUPING:
+ rval = lc->mon_grouping;
+ break;
+ case KW_POSITIVE_SIGN:
+ rval = lc->positive_sign;
+ break;
+ case KW_NEGATIVE_SIGN:
+ rval = lc->negative_sign;
+ break;
+ case KW_INT_FRAC_DIGITS:
+ rval = &(lc->int_frac_digits);
+ break;
+ case KW_FRAC_DIGITS:
+ rval = &(lc->frac_digits);
+ break;
+ case KW_P_CS_PRECEDES:
+ rval = &(lc->p_cs_precedes);
+ break;
+ case KW_P_SEP_BY_SPACE:
+ rval = &(lc->p_sep_by_space);
+ break;
+ case KW_N_CS_PRECEDES:
+ rval = &(lc->n_cs_precedes);
+ break;
+ case KW_N_SEP_BY_SPACE:
+ rval = &(lc->n_sep_by_space);
+ break;
+ case KW_P_SIGN_POSN:
+ rval = &(lc->p_sign_posn);
+ break;
+ case KW_N_SIGN_POSN:
+ rval = &(lc->n_sign_posn);
+ break;
+ case KW_INT_P_CS_PRECEDES:
+ rval = &(lc->int_p_cs_precedes);
+ break;
+ case KW_INT_P_SEP_BY_SPACE:
+ rval = &(lc->int_p_sep_by_space);
+ break;
+ case KW_INT_N_CS_PRECEDES:
+ rval = &(lc->int_n_cs_precedes);
+ break;
+ case KW_INT_N_SEP_BY_SPACE:
+ rval = &(lc->int_n_sep_by_space);
+ break;
+ case KW_INT_P_SIGN_POSN:
+ rval = &(lc->int_p_sign_posn);
+ break;
+ case KW_INT_N_SIGN_POSN:
+ rval = &(lc->int_n_sign_posn);
+ break;
+ default:
+ break;
+ }
+ return (rval);
+}
+
+/*
+ * keyword value and properties lookup
+ */
+int
+kwval_lookup(char *kwname, char **kwval, int *cat, int *isstr)
+{
+ int rval;
+ size_t i;
+
+ rval = 0;
+ for (i = 0; i < NKWINFO; i++) {
+ if (strcasecmp(kwname, kwinfo[i].name) == 0) {
+ rval = 1;
+ *cat = kwinfo[i].catid;
+ *isstr = kwinfo[i].isstr;
+ if (kwinfo[i].value_ref < KW_ZERO) {
+ *kwval = nl_langinfo(kwinfo[i].value_ref);
+ } else {
+ *kwval = kwval_lconv(kwinfo[i].value_ref);
+ }
+ break;
+ }
+ }
+
+ return (rval);
+}
+
+/*
+ * Show details about requested keyword according to '-k' and/or '-c'
+ * command line options specified.
+ */
+void
+showdetails(char *kw)
+{
+ int isstr, cat, tmpval;
+ char *kwval;
+
+ if (kwval_lookup(kw, &kwval, &cat, &isstr) == 0) {
+ /*
+ * invalid keyword specified.
+ * XXX: any actions?
+ */
+ return;
+ }
+
+ if (prt_categories) {
+ printf("%s\n", lookup_localecat(cat));
+ }
+
+ if (prt_keywords) {
+ if (isstr) {
+ printf("%s=\"%s\"\n", kw, kwval);
+ } else {
+ tmpval = (char) *kwval;
+ printf("%s=%d\n", kw, tmpval);
+ }
+ }
+
+ if (!prt_categories && !prt_keywords) {
+ if (isstr) {
+ printf("%s\n", kwval);
+ } else {
+ tmpval = (char) *kwval;
+ printf("%d\n", tmpval);
+ }
+ }
+}
+
+/*
+ * Convert locale category id into string
+ */
+const char *
+lookup_localecat(int cat)
+{
+ size_t i;
+
+ for (i = 0; i < NLCINFO; i++)
+ if (lcinfo[i].id == cat) {
+ return (lcinfo[i].name);
+ }
+ return ("UNKNOWN");
+}
+
+/*
+ * Show list of keywords
+ */
+void
+showkeywordslist(void)
+{
+ size_t i;
+
+#define FMT "%-20s %-12s %-7s %-20s\n"
+
+ printf("List of available keywords\n\n");
+ printf(FMT, "Keyword", "Category", "Type", "Comment");
+ printf("-------------------- ------------ ------- --------------------\n");
+ for (i = 0; i < NKWINFO; i++) {
+ printf(FMT,
+ kwinfo[i].name,
+ lookup_localecat(kwinfo[i].catid),
+ (kwinfo[i].isstr == 0) ? "number" : "string",
+ kwinfo[i].comment);
+ }
+}
diff --git a/usr.bin/logger/logger.1 b/usr.bin/logger/logger.1
new file mode 100644
index 0000000..da47b68
--- /dev/null
+++ b/usr.bin/logger/logger.1
@@ -0,0 +1,117 @@
+.\" $NetBSD: logger.1,v 1.15 2012/05/13 17:08:31 njoly Exp $
+.\"
+.\" Copyright (c) 1983, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)logger.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd April 26, 2012
+.Dt LOGGER 1
+.Os
+.Sh NAME
+.Nm logger
+.Nd make entries in the system log
+.Sh SYNOPSIS
+.Nm
+.Op Fl cins
+.Op Fl d Ar SD
+.Op Fl f Ar file
+.Op Fl m Ar msgid
+.Op Fl p Ar pri
+.Op Fl t Ar tag
+.Op Ar message ...
+.Sh DESCRIPTION
+.Nm
+provides a shell command interface to the
+.Xr syslog 3
+system log module.
+.Pp
+Options:
+.Pp
+.Bl -tag -width "messageXX"
+.It Fl c
+Log to console
+.Dv ( LOG_CONS ) .
+.It Fl d Ar sd
+Log this in the structured data (SD) field.
+.Po
+.Ar sd
+has to be passed as one argument and will require careful quoting when used from
+the shell.
+.Pc
+.It Fl f Ar file
+Log the specified file.
+.It Fl i
+Log the process id of the logger process
+with each line
+.Dv ( LOG_PID ) .
+.It Fl m Ar msgid
+The MSGID used for the message.
+.It Fl n
+Open log file immediately
+.Dv ( LOG_NDELAY ) .
+.It Fl p Ar pri
+Enter the message with the specified priority.
+The priority may be specified numerically or as a
+.Dq facility.level
+pair.
+For example,
+.Dq \-p local3.info
+logs the message(s) as
+.Ar info Ns rmational
+level in the
+.Ar local3
+facility.
+The default is
+.Dq user.notice .
+.It Fl s
+Log the message to standard error, as well as the system log
+.Dv ( LOG_PERROR ) .
+.It Fl t Ar tag
+Mark every line in the log with the specified
+.Ar tag .
+.It Ar message
+Write the message to log; if not specified, and the
+.Fl f
+flag is not provided, standard input is logged.
+.El
+.Sh EXIT STATUS
+.Ex -std logger
+.Sh EXAMPLES
+.Bd -literal -offset indent -compact
+logger System rebooted
+
+logger \-p local0.notice \-t HOSTIDM \-f /dev/idmc
+.Ed
+.Sh SEE ALSO
+.Xr syslog 3 ,
+.Xr syslogd 8
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/logger/logger.c b/usr.bin/logger/logger.c
new file mode 100644
index 0000000..b87aa5d
--- /dev/null
+++ b/usr.bin/logger/logger.c
@@ -0,0 +1,203 @@
+/* $NetBSD: logger.c,v 1.17 2012/04/27 06:30:48 wiz Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1983, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)logger.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: logger.c,v 1.17 2012/04/27 06:30:48 wiz Exp $");
+#endif /* not lint */
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <err.h>
+
+#define SYSLOG_NAMES
+#include <syslog.h>
+
+static int decode(const char *, const CODE *);
+static int pencode(char *);
+__dead static void usage(void);
+
+/*
+ * logger -- read and log utility
+ *
+ * Reads from an input and arranges to write the result on the system
+ * log.
+ */
+int
+main(int argc, char *argv[])
+{
+ int ch, logflags, pri;
+ const char *tag;
+ const char *sd = "-";
+ const char *msgid = "-";
+ char buf[1024];
+
+ tag = NULL;
+ pri = LOG_NOTICE;
+ logflags = 0;
+ while ((ch = getopt(argc, argv, "cd:f:im:np:st:")) != -1)
+ switch((char)ch) {
+ case 'c': /* log to console */
+ logflags |= LOG_CONS;
+ break;
+ case 'd': /* structured data field */
+ sd = optarg;
+ break;
+ case 'f': /* file to log */
+ if (freopen(optarg, "r", stdin) == NULL)
+ err(EXIT_FAILURE, "%s", optarg);
+ break;
+ case 'i': /* log process id also */
+ logflags |= LOG_PID;
+ break;
+ case 'm': /* msgid field */
+ msgid = optarg;
+ break;
+ case 'n': /* open log file immediately */
+ logflags |= LOG_NDELAY;
+ break;
+ case 'p': /* priority */
+ pri = pencode(optarg);
+ break;
+ case 's': /* log to standard error */
+ logflags |= LOG_PERROR;
+ break;
+ case 't': /* tag */
+ tag = optarg;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* setup for logging */
+ openlog(tag != NULL ? tag : getlogin(), logflags, 0);
+ (void)fclose(stdout);
+
+ /* log input line if appropriate */
+ if (argc > 0) {
+ char *p, *endp;
+ size_t len;
+
+ for (p = buf, endp = buf + sizeof(buf) - 2; *argv != NULL;) {
+ len = strlen(*argv);
+ if (p + len > endp && p > buf) {
+ syslogp(pri, msgid, sd, "%s", buf);
+ p = buf;
+ }
+ if (len > sizeof(buf) - 1)
+ syslogp(pri, msgid, sd, "%s", *argv++);
+ else {
+ if (p != buf)
+ *p++ = ' ';
+ memmove(p, *argv++, len);
+ *(p += len) = '\0';
+ }
+ }
+ if (p != buf)
+ syslogp(pri, msgid, sd, "%s", buf);
+ } else /* TODO: allow syslog-protocol messages from file/stdin
+ * but that will require parsing the line to split
+ * it into three fields.
+ */
+ while (fgets(buf, sizeof(buf), stdin) != NULL)
+ syslogp(pri, msgid, sd, "%s", buf);
+
+ exit(EXIT_SUCCESS);
+ /* NOTREACHED */
+}
+
+/*
+ * Decode a symbolic name to a numeric value
+ */
+static int
+pencode(char *s)
+{
+ char *save;
+ int fac, lev;
+
+ for (save = s; *s != '\0' && *s != '.'; ++s)
+ ;
+ if (*s != '\0') {
+ *s = '\0';
+ fac = decode(save, facilitynames);
+ if (fac < 0)
+ errx(EXIT_FAILURE, "unknown facility name: %s", save);
+ *s++ = '.';
+ } else {
+ fac = 0;
+ s = save;
+ }
+ lev = decode(s, prioritynames);
+ if (lev < 0)
+ errx(EXIT_FAILURE, "unknown priority name: %s", s);
+ return ((lev & LOG_PRIMASK) | (fac & LOG_FACMASK));
+}
+
+static int
+decode(const char *name, const CODE *codetab)
+{
+ const CODE *c;
+
+ if (isdigit((unsigned char)*name))
+ return (atoi(name));
+
+ for (c = codetab; c->c_name != NULL; c++)
+ if (strcasecmp(name, c->c_name) == 0)
+ return (c->c_val);
+
+ return (-1);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "Usage: %s [-cins] [-d SD] [-f file] [-m msgid] "
+ "[-p pri] [-t tag] [message ...]\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/logname/logname.1 b/usr.bin/logname/logname.1
new file mode 100644
index 0000000..242ac00
--- /dev/null
+++ b/usr.bin/logname/logname.1
@@ -0,0 +1,72 @@
+.\" $NetBSD: logname.1,v 1.14 2017/07/04 07:02:44 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)logname.1 8.1 (Berkeley) 6/9/93
+.\"
+.Dd June 9, 1993
+.Dt LOGNAME 1
+.Os
+.Sh NAME
+.Nm logname
+.Nd display user's login name
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+utility writes the user's login name to standard output followed by
+a newline.
+.Pp
+The
+.Nm
+utility explicitly ignores the
+.Ev LOGNAME
+and
+.Ev USER
+environment variables
+because the environment cannot be trusted.
+.Sh EXIT STATUS
+.Ex -std logname
+.Sh SEE ALSO
+.Xr who 1 ,
+.Xr whoami 1 ,
+.Xr getlogin 2
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.4 .
diff --git a/usr.bin/logname/logname.c b/usr.bin/logname/logname.c
new file mode 100644
index 0000000..77f1009
--- /dev/null
+++ b/usr.bin/logname/logname.c
@@ -0,0 +1,85 @@
+/* $NetBSD: logname.c,v 1.10 2011/09/04 20:29:12 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)logname.c 8.2 (Berkeley) 4/3/94";
+#endif
+__RCSID("$NetBSD: logname.c,v 1.10 2011/09/04 20:29:12 joerg Exp $");
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <unistd.h>
+#include <err.h>
+
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ char *p;
+
+ setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "")) != -1)
+ switch (ch) {
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+
+ if (argc != optind) {
+ usage();
+ /* NOTREACHED */
+ }
+
+ if ((p = getlogin()) == NULL)
+ err(1, "getlogin");
+ (void)printf("%s\n", p);
+ exit(0);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: logname\n");
+ exit(1);
+}
diff --git a/usr.bin/m4/NOTES b/usr.bin/m4/NOTES
new file mode 100644
index 0000000..d60f80e
--- /dev/null
+++ b/usr.bin/m4/NOTES
@@ -0,0 +1,64 @@
+m4 - macro processor
+
+PD m4 is based on the macro tool distributed with the software
+tools (VOS) package, and described in the "SOFTWARE TOOLS" and
+"SOFTWARE TOOLS IN PASCAL" books. It has been expanded to include
+most of the command set of SysV m4, the standard UN*X macro processor.
+
+Since both PD m4 and UN*X m4 are based on SOFTWARE TOOLS macro,
+there may be certain implementation similarities between
+the two. The PD m4 was produced without ANY references to m4
+sources.
+
+written by: Ozan S. Yigit
+
+References:
+
+ Software Tools distribution: macro
+
+ Kernighan, Brian W. and P. J. Plauger, SOFTWARE
+ TOOLS IN PASCAL, Addison-Wesley, Mass. 1981
+
+ Kernighan, Brian W. and P. J. Plauger, SOFTWARE
+ TOOLS, Addison-Wesley, Mass. 1976
+
+ Kernighan, Brian W. and Dennis M. Ritchie,
+ THE M4 MACRO PROCESSOR, Unix Programmer's Manual,
+ Seventh Edition, Vol. 2, Bell Telephone Labs, 1979
+
+ System V man page for M4
+
+
+Implementation Notes:
+
+[1] PD m4 uses a different (and simpler) stack mechanism than the one
+ described in Software Tools and Software Tools in Pascal books.
+ The triple stack thing is replaced with a single stack containing
+ the call frames and the arguments. Each frame is back-linked to a
+ previous stack frame, which enables us to rewind the stack after
+ each nested call is completed. Each argument is a character pointer
+ to the beginning of the argument string within the string space.
+ The only exceptions to this are (*) arg 0 and arg 1, which are
+ the macro definition and macro name strings, stored dynamically
+ for the hash table.
+
+ . .
+ | . | <-- sp | . |
+ +-------+ +-----+
+ | arg 3 ------------------------------->| str |
+ +-------+ | . |
+ | arg 2 --------------+ .
+ +-------+ |
+ * | | |
+ +-------+ | +-----+
+ | plev | <-- fp +---------------->| str |
+ +-------+ | . |
+ | type | .
+ +-------+
+ | prcf -----------+ plev: paren level
+ +-------+ | type: call type
+ | . | | prcf: prev. call frame
+ . |
+ +-------+ |
+ | <----------+
+ +-------+
diff --git a/usr.bin/m4/PSD.doc/Makefile b/usr.bin/m4/PSD.doc/Makefile
new file mode 100644
index 0000000..9de1f83
--- /dev/null
+++ b/usr.bin/m4/PSD.doc/Makefile
@@ -0,0 +1,10 @@
+# $NetBSD: Makefile,v 1.4 2014/07/05 19:22:04 dholland Exp $
+#
+# @(#)Makefile 8.1 (Berkeley) 6/8/93
+
+SECTION=psd
+ARTICLE=m4
+SRCS= m4.ms
+MACROS= -msU
+
+.include <bsd.doc.mk>
diff --git a/usr.bin/m4/TEST/ack.m4 b/usr.bin/m4/TEST/ack.m4
new file mode 100644
index 0000000..2784c45
--- /dev/null
+++ b/usr.bin/m4/TEST/ack.m4
@@ -0,0 +1,41 @@
+# $NetBSD: ack.m4,v 1.4 1995/09/28 05:37:54 tls Exp $
+#
+# Copyright (c) 1989, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Ozan Yigit.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)ack.m4 8.1 (Berkeley) 6/6/93
+#
+
+define(ack, `ifelse($1,0,incr($2),$2,0,`ack(DECR($1),1)',
+`ack(DECR($1), ack($1,DECR($2)))')')
diff --git a/usr.bin/m4/TEST/hanoi.m4 b/usr.bin/m4/TEST/hanoi.m4
new file mode 100644
index 0000000..6f73431
--- /dev/null
+++ b/usr.bin/m4/TEST/hanoi.m4
@@ -0,0 +1,46 @@
+# $NetBSD: hanoi.m4,v 1.4 1995/09/28 05:37:56 tls Exp $
+#
+# Copyright (c) 1989, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Ozan Yigit.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)hanoi.m4 8.1 (Berkeley) 6/6/93
+#
+
+define(hanoi, `trans(A, B, C, $1)')
+
+define(moved,`move disk from $1 to $2
+')
+
+define(trans, `ifelse($4,1,`moved($1,$2)',
+ `trans($1,$3,$2,DECR($4))moved($1,$2)trans($3,$2,$1,DECR($4))')')
diff --git a/usr.bin/m4/TEST/hash.m4 b/usr.bin/m4/TEST/hash.m4
new file mode 100644
index 0000000..5de2f79
--- /dev/null
+++ b/usr.bin/m4/TEST/hash.m4
@@ -0,0 +1,56 @@
+# $NetBSD: hash.m4,v 1.4 1995/09/28 05:37:58 tls Exp $
+#
+# Copyright (c) 1989, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Ozan Yigit.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)hash.m4 8.1 (Berkeley) 6/6/93
+#
+
+dnl This probably will not run on any m4 that cannot
+dnl handle char constants in eval.
+dnl
+changequote(<,>) define(HASHVAL,99) dnl
+define(hash,<eval(str(substr($1,1),0)%HASHVAL)>) dnl
+define(str,
+ <ifelse($1,",$2,
+ <str(substr(<$1>,1),<eval($2+'substr($1,0,1)')>)>)
+ >) dnl
+define(KEYWORD,<$1,hash($1),>) dnl
+define(TSTART,
+<struct prehash {
+ char *keyword;
+ int hashval;
+} keytab[] = {>) dnl
+define(TEND,< "",0
+};>) dnl
diff --git a/usr.bin/m4/TEST/math.m4 b/usr.bin/m4/TEST/math.m4
new file mode 100644
index 0000000..dff3937
--- /dev/null
+++ b/usr.bin/m4/TEST/math.m4
@@ -0,0 +1,182 @@
+dnl $NetBSD: math.m4,v 1.2 2005/10/06 17:38:09 drochner Exp $
+dnl FreeBSD: /repoman/r/ncvs/src/usr.bin/m4/TEST/math.m4,v 1.1 2004/05/01 03:27:05 smkelly Exp
+dnl A regression test for m4 C operators (ksb,petef)
+dnl If you think you have a short-circuiting m4, run us m4 -DSHORCIRCUIT=yes
+dnl
+dnl first level of precedence
+ifelse(eval(-7),-7,,`failed -
+')dnl
+ifelse(eval(- -2),2,,`failed -
+')dnl
+ifelse(eval(!0),1,,`failed !
+')dnl
+ifelse(eval(!7),0,,`failed !
+')dnl
+ifelse(eval(~-1),0,,`failed ~
+')dnl
+dnl next level of precedence
+ifelse(eval(3*5),15,,`failed *
+')dnl
+ifelse(eval(3*0),0,,`failed *
+')dnl
+ifelse(eval(11/2),5,,`failed /
+')dnl
+ifelse(eval(1/700),0,,`failed /
+')dnl
+ifelse(eval(10%5),0,,`failed %
+')dnl
+ifelse(eval(2%5),2,,`failed %
+')dnl
+ifelse(eval(2%-1),0,,`failed %
+')dnl
+dnl next level of precedence
+ifelse(eval(2+2),4,,`failed +
+')dnl
+ifelse(eval(2+-2),0,,`failed +
+')dnl
+ifelse(eval(2- -2),4,,`failed -
+')dnl
+ifelse(eval(2-2),0,,`failed -
+')dnl
+dnl next level of precedence
+ifelse(eval(1<<4),16,,`failed <<
+')dnl
+ifelse(eval(16>>4),1,,`failed >>
+')dnl
+dnl next level of precedence
+ifelse(eval(4<4),0,,`failed <
+')dnl
+ifelse(eval(4<5),1,,`failed <
+')dnl
+ifelse(eval(4<3),0,,`failed <
+')dnl
+ifelse(eval(4>4),0,,`failed >
+')dnl
+ifelse(eval(4>5),0,,`failed >
+')dnl
+ifelse(eval(4>3),1,,`failed >
+')dnl
+ifelse(eval(4<=4),1,,`failed <=
+')dnl
+ifelse(eval(4<=5),1,,`failed <=
+')dnl
+ifelse(eval(4<=3),0,,`failed <=
+')dnl
+ifelse(eval(4>=4),1,,`failed >=
+')dnl
+ifelse(eval(4>=5),0,,`failed >=
+')dnl
+ifelse(eval(4>=3),1,,`failed >=
+')dnl
+dnl next level of precedence
+ifelse(eval(1==1),1,,`failed ==
+')dnl
+ifelse(eval(1==-1),0,,`failed ==
+')dnl
+ifelse(eval(1!=1),0,,`failed !=
+')dnl
+ifelse(eval(1!=2),1,,`failed !=
+')dnl
+dnl next level of precedence
+ifelse(eval(3&5),1,,`failed &
+')dnl
+ifelse(eval(8&7),0,,`failed &
+')dnl
+dnl next level of precedence
+ifelse(eval(1^1),0,,`failed ^
+')dnl
+ifelse(eval(21^5),16,,`failed ^
+')dnl
+dnl next level of precedence
+ifelse(eval(1|1),1,,`failed |
+')dnl
+ifelse(eval(21|5),21,,`failed |
+')dnl
+ifelse(eval(100|1),101,,`failed |
+')dnl
+dnl next level of precedence
+ifelse(eval(1&&1),1,,`failed &&
+')dnl
+ifelse(eval(0&&1),0,,`failed &&
+')dnl
+ifelse(eval(1&&0),0,,`failed &&
+')dnl
+ifelse(SHORTCIRCUIT,`yes',`ifelse(eval(0&&10/0),0,,`failed && shortcircuit
+')')dnl
+dnl next level of precedence
+ifelse(eval(1||1),1,,`failed ||
+')dnl
+ifelse(eval(1||0),1,,`failed ||
+')dnl
+ifelse(eval(0||0),0,,`failed ||
+')dnl
+ifelse(SHORTCIRCUIT,`yes',`ifelse(eval(1||10/0),1,,`failed || shortcircuit
+')')dnl
+dnl next level of precedence
+ifelse(eval(0 ? 2 : 5),5,,`failed ?:
+')dnl
+ifelse(eval(1 ? 2 : 5),2,,`failed ?:
+')dnl
+ifelse(SHORTCIRCUIT,`yes',`ifelse(eval(0 ? 10/0 : 7),7,,`failed ?: shortcircuit
+')')dnl
+ifelse(SHORTCIRCUIT,`yes',`ifelse(eval(1 ? 7 : 10/0),7,,`failed ?: shortcircuit
+')')dnl
+dnl operator precedence
+ifelse(eval(!0*-2),-2,,`precedence wrong, ! *
+')dnl
+ifelse(eval(~8/~2),3,,`precedence wrong ~ /
+')dnl
+ifelse(eval(~-20%7),5,,`precedence wrong ~ %
+')dnl
+ifelse(eval(3*2+100),106,,`precedence wrong * +
+')dnl
+ifelse(eval(3+2*100),203,,`precedence wrong + *
+')dnl
+ifelse(eval(2%5-6/3),0,,`precedence wrong % -
+')dnl
+ifelse(eval(2/5-5%3),-2,,`precedence wrong / -
+')dnl
+ifelse(eval(2+5%5+1),3,,`precedence wrong % +
+')dnl
+ifelse(eval(7+9<<1),32,,`precedence wrong + <<
+')dnl
+ifelse(eval(35-3>>2),8,,`precedence wrong - >>
+')dnl
+ifelse(eval(9<10<<5),1,,`precedence wrong << <
+')dnl
+ifelse(eval(9>10<<5),0,,`precedence wrong << >
+')dnl
+ifelse(eval(32>>2<32),1,,`precedence wrong >> <
+')dnl
+ifelse(eval(9<=10<<5),1,,`precedence wrong << <
+')dnl
+ifelse(eval(5<<1<=20>>1),1,,`precedence wrong << <=
+')dnl
+ifelse(eval(5<<1>=20>>1),1,,`precedence wrong << >=
+')dnl
+ifelse(eval(0<7==5>=5),1,,`precedence wrong < ==
+')dnl
+ifelse(eval(0<7!=5>=5),0,,`precedence wrong < !=
+')dnl
+ifelse(eval(0>7==5>=5),0,,`precedence wrong > ==
+')dnl
+ifelse(eval(0>7!=5>=5),1,,`precedence wrong > !=
+')dnl
+ifelse(eval(1&7==7),1,,`precedence wrong & ==
+')dnl
+ifelse(eval(0&7!=6),0,,`precedence wrong & !=
+')dnl
+ifelse(eval(9&1|5),5,,`precedence wrong & |
+')dnl
+ifelse(eval(9&1^5),4,,`precedence wrong & ^
+')dnl
+ifelse(eval(9^1|5),13,,`precedence wrong ^ |
+')dnl
+ifelse(eval(5|0&&1),1,,`precedence wrong | &&
+')dnl
+ifelse(eval(5&&0||0&&5||5),1,,`precedence wrong && ||
+')dnl
+ifelse(eval(0 || 1 ? 0 : 1),0,,`precedence wrong || ?:
+')dnl
+ifelse(eval(5&&(0||0)&&(5||5)),0,,`precedence wrong || parens
+')dnl
diff --git a/usr.bin/m4/TEST/sqroot.m4 b/usr.bin/m4/TEST/sqroot.m4
new file mode 100644
index 0000000..6fc8d2c
--- /dev/null
+++ b/usr.bin/m4/TEST/sqroot.m4
@@ -0,0 +1,46 @@
+# $NetBSD: sqroot.m4,v 1.4 1995/09/28 05:38:01 tls Exp $
+#
+# Copyright (c) 1989, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Ozan Yigit.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)sqroot.m4 8.1 (Berkeley) 6/6/93
+#
+
+define(square_root,
+ `ifelse(eval($1<0),1,negative-square-root,
+ `square_root_aux($1, 1, eval(($1+1)/2))')')
+define(square_root_aux,
+ `ifelse($3, $2, $3,
+ $3, eval($1/$2), $3,
+ `square_root_aux($1, $3, eval(($3+($1/$3))/2))')')
diff --git a/usr.bin/m4/TEST/string.m4 b/usr.bin/m4/TEST/string.m4
new file mode 100644
index 0000000..a14f14a
--- /dev/null
+++ b/usr.bin/m4/TEST/string.m4
@@ -0,0 +1,46 @@
+# $NetBSD: string.m4,v 1.4 1995/09/28 05:38:03 tls Exp $
+#
+# Copyright (c) 1989, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Ozan Yigit.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)string.m4 8.1 (Berkeley) 6/6/93
+#
+
+define(string,`integer $1(len(substr($2,1)))
+str($1,substr($2,1),0)
+data $1(len(substr($2,1)))/EOS/
+')
+
+define(str,`ifelse($2,",,data $1(incr($3))/`LET'substr($2,0,1)/
+`str($1,substr($2,1),incr($3))')')
diff --git a/usr.bin/m4/TEST/test.m4 b/usr.bin/m4/TEST/test.m4
new file mode 100644
index 0000000..5b19807
--- /dev/null
+++ b/usr.bin/m4/TEST/test.m4
@@ -0,0 +1,244 @@
+# $NetBSD: test.m4,v 1.4 1995/09/28 05:38:05 tls Exp $
+#
+# Copyright (c) 1989, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Ozan Yigit.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)test.m4 8.1 (Berkeley) 6/6/93
+#
+
+# test file for mp (not comprehensive)
+#
+# v7 m4 does not have `decr'.
+#
+define(DECR,`eval($1-1)')
+#
+# include string macros
+#
+include(string.m4)
+#
+# create some fortrash strings for an even uglier language
+#
+string(TEXT, "text")
+string(DATA, "data")
+string(BEGIN, "begin")
+string(END, "end")
+string(IF, "if")
+string(THEN, "then")
+string(ELSE, "else")
+string(CASE, "case")
+string(REPEAT, "repeat")
+string(WHILE, "while")
+string(DEFAULT, "default")
+string(UNTIL, "until")
+string(FUNCTION, "function")
+string(PROCEDURE, "procedure")
+string(EXTERNAL, "external")
+string(FORWARD, "forward")
+string(TYPE, "type")
+string(VAR, "var")
+string(CONST, "const")
+string(PROGRAM, "program")
+string(INPUT, "input")
+string(OUTPUT, "output")
+#
+divert(2)
+diversion #1
+divert(3)
+diversion #2
+divert(4)
+diversion #3
+divert(5)
+diversion #4
+divert(0)
+define(abc,xxx)
+ifdef(`abc',defined,undefined)
+#
+# v7 m4 does this wrong. The right output is
+# this is A vEry lon sEntEnCE
+# see m4 documentation for translit.
+#
+translit(`this is a very long sentence', abcdefg, ABCDEF)
+#
+# include towers-of-hanoi
+#
+include(hanoi.m4)
+#
+# some reasonable set of disks
+#
+hanoi(6)
+#
+# include ackermann's function
+#
+include(ack.m4)
+#
+# something like (3,3) will blow away un*x m4.
+#
+ack(2,3)
+#
+# include a square_root function for fixed nums
+#
+include(sqroot.m4)
+#
+# some square roots.
+#
+square_root(15)
+square_root(100)
+square_root(-4)
+square_root(21372)
+#
+# some textual material for enjoyment.
+#
+[taken from the 'Clemson University Computer Newsletter',
+ September 1981, pp. 6-7]
+
+I am a wizard in the magical Kingdom of Transformation and I
+slay dragons for a living. Actually, I am a systems programmer.
+One of the problems with systems programming is explaining to
+non-computer enthusiasts what that is. All of the terms I use to
+describe my job are totally meaningless to them. Usually my response
+to questions about my work is to say as little as possible. For
+instance, if someone asks what happened at work this week, I say
+"Nothing much" and then I change the subject.
+
+With the assistance of my brother, a mechanical engineer, I have devised
+an analogy that everyone can understand. The analogy describes the
+"Kingdom of Transformation" where travelers wander and are magically
+transformed. This kingdom is the computer and the travelers are information.
+The purpose of the computer is to change information to a more meaningful
+forma. The law of conservation applies here: The computer never creates
+and never intentionally destroys data. With no further ado, let us travel
+to the Kingdom of Transformation:
+
+In a land far, far away, there is a magical kingdom called the Kingdom of
+Transformation. A king rules over this land and employs a Council of
+Wizardry. The main purpose of this kingdom is to provide a way for
+neighboring kingdoms to transform citizens into more useful citizens. This
+is done by allowing the citizens to enter the kingdom at one of its ports
+and to travel any of the many routes in the kingdom. They are magically
+transformed along the way. The income of the Kingdom of Transformation
+comes from the many toll roads within its boundaries.
+
+The Kingdom of Transformation was created when several kingdoms got
+together and discovered a mutual need for new talents and abilities for
+citizens. They employed CTK, Inc. (Creators of Transformation, Inc.) to
+create this kingdom. CTK designed the country, its transportation routes,
+and its laws of transformation, and created the major highway system.
+
+Hazards
+=======
+
+Because magic is not truly controllable, CTK invariably, but unknowingly,
+creates dragons. Dragons are huge fire-breathing beasts which sometimes
+injure or kill travelers. Fortunately, they do not travel, but always
+remain near their den.
+
+Other hazards also exist which are potentially harmful. As the roads
+become older and more weatherbeaten, pot-holes will develop, trees will
+fall on travelers, etc. CTK maintenance men are called to fix these
+problems.
+
+Wizards
+=======
+
+The wizards play a major role in creating and maintaining the kingdom but
+get little credit for their work because it is performed secretly. The
+wizards do not wan the workers or travelers to learn their incantations
+because many laws would be broken and chaos would result.
+
+CTK's grand design is always general enough to be applicable in many
+different situations. As a result, it is often difficult to use. The
+first duty of the wizards is to tailor the transformation laws so as to be
+more beneficial and easier to use in their particular environment.
+
+After creation of the kingdom, a major duty of the wizards is to search for
+and kill dragons. If travelers do not return on time or if they return
+injured, the ruler of the country contacts the wizards. If the wizards
+determine that the injury or death occurred due to the traveler's
+negligence, they provide the traveler's country with additional warnings.
+If not, they must determine if the cause was a road hazard or a dragon. If
+the suspect a road hazard, they call in a CTK maintenance man to locate the
+hazard and to eliminate it, as in repairing the pothole in the road. If
+they think that cause was a dragon, then they must find and slay it.
+
+The most difficult part of eliminating a dragon is finding it. Sometimes
+the wizard magically knows where the dragon's lair it, but often the wizard
+must send another traveler along the same route and watch to see where he
+disappears. This sounds like a failsafe method for finding dragons (and a
+suicide mission for thr traveler) but the second traveler does not always
+disappear. Some dragons eat any traveler who comes too close; others are
+very picky.
+
+The wizards may call in CTK who designed the highway system and
+transformation laws to help devise a way to locate the dragon. CTK also
+helps provide the right spell or incantation to slay the dragon. (There is
+no general spell to slay dragons; each dragon must be eliminated with a
+different spell.)
+
+Because neither CTK nor wizards are perfect, spells to not always work
+correctly. At best, nothing happens when the wrong spell is uttered. At
+worst, the dragon becomes a much larger dragon or multiplies into several
+smaller ones. In either case, new spells must be found.
+
+If all existing dragons are quiet (i.e. have eaten sufficiently), wizards
+have time to do other things. They hide in castles and practice spells and
+incatations. They also devise shortcuts for travelers and new laws of
+transformation.
+
+Changes in the Kingdom
+======================
+
+As new transformation kingdoms are created and old ones are maintained,
+CTK, Inc. is constantly learning new things. It learns ways to avoid
+creating some of the dragons that they have previously created. It also
+discovers new and better laws of transformation. As a result, CTK will
+periodically create a new grand design which is far better than the old.
+The wizards determine when is a good time to implement this new design.
+This is when the tourist season is slow or when no important travelers
+(VIPs) are to arrive. The kingdom must be closed for the actual
+implementation and is leter reopened as a new and better place to go.
+
+A final question you might ask is what happens when the number of tourists
+becomes too great for the kingdom to handle in a reasonable period of time
+(i.e., the tourist lines at the ports are too long). The Kingdom of
+Transformation has three options: (1) shorten the paths that a tourist must
+travel, or (2) convince CTK to develop a faster breed of horses so that the
+travelers can finish sooner, or (3) annex more territories so that the
+kingdom can handle more travelers.
+
+Thus ends the story of the Kingdom of Transformation. I hope this has
+explained my job to you: I slay dragons for a living.
+
+#
+#should do an automatic undivert..
+#
diff --git a/usr.bin/m4/eval.c b/usr.bin/m4/eval.c
new file mode 100644
index 0000000..94150b1
--- /dev/null
+++ b/usr.bin/m4/eval.c
@@ -0,0 +1,1053 @@
+/* $OpenBSD: eval.c,v 1.66 2008/08/21 21:01:47 espie Exp $ */
+/* $NetBSD: eval.c,v 1.27 2018/07/30 22:58:09 kre Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * eval.c
+ * Facility: m4 macro processor
+ * by: oz
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: eval.c,v 1.27 2018/07/30 22:58:09 kre Exp $");
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include "mdef.h"
+#include "stdd.h"
+#include "extern.h"
+#include "pathnames.h"
+
+static void dodefn(const char *);
+static void dopushdef(const char *, const char *);
+static void dodump(const char *[], int);
+static void dotrace(const char *[], int, int);
+static void doifelse(const char *[], int);
+static int doincl(const char *);
+static int dopaste(const char *);
+static void dochq(const char *[], int);
+static void dochc(const char *[], int);
+static void dom4wrap(const char *);
+static void dodiv(int);
+static void doundiv(const char *[], int);
+static void dosub(const char *[], int);
+static void map(char *, const char *, const char *, const char *);
+static const char *handledash(char *, char *, const char *);
+static void expand_builtin(const char *[], int, int);
+static void expand_macro(const char *[], int);
+static void dump_one_def(const char *, struct macro_definition *);
+
+unsigned long expansion_id;
+
+/*
+ * eval - eval all macros and builtins calls
+ * argc - number of elements in argv.
+ * argv - element vector :
+ * argv[0] = definition of a user
+ * macro or NULL if built-in.
+ * argv[1] = name of the macro or
+ * built-in.
+ * argv[2] = parameters to user-defined
+ * . macro or built-in.
+ * .
+ *
+ * A call in the form of macro-or-builtin() will result in:
+ * argv[0] = nullstr
+ * argv[1] = macro-or-builtin
+ * argv[2] = nullstr
+ *
+ * argc is 3 for macro-or-builtin() and 2 for macro-or-builtin
+ */
+void
+eval(const char *argv[], int argc, int td, int is_traced)
+{
+ size_t mark = SIZE_MAX;
+
+ expansion_id++;
+ if (td & RECDEF)
+ m4errx(1, "expanding recursive definition for %s.", argv[1]);
+ if (is_traced)
+ mark = trace(argv, argc, infile+ilevel);
+ if (td == MACRTYPE)
+ expand_macro(argv, argc);
+ else
+ expand_builtin(argv, argc, td);
+ if (mark != SIZE_MAX)
+ finish_trace(mark);
+}
+
+/*
+ * expand_builtin - evaluate built-in macros.
+ */
+void
+expand_builtin(const char *argv[], int argc, int td)
+{
+ int c, n;
+ int ac;
+ static int sysval = 0;
+
+#ifdef DEBUG
+ printf("argc = %d\n", argc);
+ for (n = 0; n < argc; n++)
+ printf("argv[%d] = %s\n", n, argv[n]);
+ fflush(stdout);
+#endif
+
+ /*
+ * if argc == 3 and argv[2] is null, then we
+ * have macro-or-builtin() type call. We adjust
+ * argc to avoid further checking..
+ */
+ /* we keep the initial value for those built-ins that differentiate
+ * between builtin() and builtin.
+ */
+ ac = argc;
+
+ if (argc == 3 && !*(argv[2]) && !mimic_gnu)
+ argc--;
+
+ switch (td & TYPEMASK) {
+
+ case DEFITYPE:
+ if (argc > 2)
+ dodefine(argv[2], (argc > 3) ? argv[3] : null);
+ break;
+
+ case PUSDTYPE:
+ if (argc > 2)
+ dopushdef(argv[2], (argc > 3) ? argv[3] : null);
+ break;
+
+ case DUMPTYPE:
+ dodump(argv, argc);
+ break;
+
+ case TRACEONTYPE:
+ dotrace(argv, argc, 1);
+ break;
+
+ case TRACEOFFTYPE:
+ dotrace(argv, argc, 0);
+ break;
+
+ case EXPRTYPE:
+ /*
+ * doexpr - evaluate arithmetic
+ * expression
+ */
+ {
+ int base = 10;
+ int maxdigits = 0;
+ int e;
+
+ if (argc > 3) {
+ base = strtoi(argv[3], NULL, 0, 2, 36, &e);
+ if (e) {
+ m4errx(1, "expr: base %s invalid.", argv[3]);
+ }
+ }
+ if (argc > 4) {
+ maxdigits = strtoi(argv[4], NULL, 0, 0, INT_MAX, &e);
+ if (e) {
+ m4errx(1, "expr: maxdigits %s invalid.", argv[4]);
+ }
+ }
+ if (argc > 2)
+ pbnumbase(expr(argv[2]), base, maxdigits);
+ break;
+ }
+
+ case IFELTYPE:
+ if (argc > 4)
+ doifelse(argv, argc);
+ break;
+
+ case IFDFTYPE:
+ /*
+ * doifdef - select one of two
+ * alternatives based on the existence of
+ * another definition
+ */
+ if (argc > 3) {
+ if (lookup_macro_definition(argv[2]) != NULL)
+ pbstr(argv[3]);
+ else if (argc > 4)
+ pbstr(argv[4]);
+ }
+ break;
+
+ case LENGTYPE:
+ /*
+ * dolen - find the length of the
+ * argument
+ */
+ pbnum((argc > 2) ? strlen(argv[2]) : 0);
+ break;
+
+ case INCRTYPE:
+ /*
+ * doincr - increment the value of the
+ * argument
+ */
+ if (argc > 2)
+ pbnum(atoi(argv[2]) + 1);
+ break;
+
+ case DECRTYPE:
+ /*
+ * dodecr - decrement the value of the
+ * argument
+ */
+ if (argc > 2)
+ pbnum(atoi(argv[2]) - 1);
+ break;
+
+ case SYSCTYPE:
+ /*
+ * dosys - execute system command
+ */
+ if (argc > 2) {
+ fflush(stdout);
+ sysval = system(argv[2]);
+ }
+ break;
+
+ case SYSVTYPE:
+ /*
+ * dosysval - return value of the last
+ * system call.
+ *
+ */
+ pbnum(sysval);
+ break;
+
+ case ESYSCMDTYPE:
+ if (argc > 2)
+ doesyscmd(argv[2]);
+ break;
+ case INCLTYPE:
+ if (argc > 2)
+ if (!doincl(argv[2]))
+ err(1, "%s at line %lu: include(%s)",
+ CURRENT_NAME, CURRENT_LINE, argv[2]);
+ break;
+
+ case SINCTYPE:
+ if (argc > 2)
+ (void) doincl(argv[2]);
+ break;
+#ifdef EXTENDED
+ case PASTTYPE:
+ if (argc > 2)
+ if (!dopaste(argv[2]))
+ err(1, "%s at line %lu: paste(%s)",
+ CURRENT_NAME, CURRENT_LINE, argv[2]);
+ break;
+
+ case SPASTYPE:
+ if (argc > 2)
+ (void) dopaste(argv[2]);
+ break;
+ case FORMATTYPE:
+ doformat(argv, argc);
+ break;
+#endif
+ case CHNQTYPE:
+ dochq(argv, ac);
+ break;
+
+ case CHNCTYPE:
+ dochc(argv, argc);
+ break;
+
+ case SUBSTYPE:
+ /*
+ * dosub - select substring
+ *
+ */
+ if (argc > 3)
+ dosub(argv, argc);
+ break;
+
+ case SHIFTYPE:
+ /*
+ * doshift - push back all arguments
+ * except the first one (i.e. skip
+ * argv[2])
+ */
+ if (argc > 3) {
+ for (n = argc - 1; n > 3; n--) {
+ pbstr(rquote);
+ pbstr(argv[n]);
+ pbstr(lquote);
+ pushback(COMMA);
+ }
+ pbstr(rquote);
+ pbstr(argv[3]);
+ pbstr(lquote);
+ }
+ break;
+
+ case DIVRTYPE:
+ if (argc > 2 && (n = atoi(argv[2])) != 0)
+ dodiv(n);
+ else {
+ active = stdout;
+ oindex = 0;
+ }
+ break;
+
+ case UNDVTYPE:
+ doundiv(argv, argc);
+ break;
+
+ case DIVNTYPE:
+ /*
+ * dodivnum - return the number of
+ * current output diversion
+ */
+ pbnum(oindex);
+ break;
+
+ case UNDFTYPE:
+ /*
+ * doundefine - undefine a previously
+ * defined macro(s) or m4 keyword(s).
+ */
+ if (argc > 2)
+ for (n = 2; n < argc; n++)
+ macro_undefine(argv[n]);
+ break;
+
+ case POPDTYPE:
+ /*
+ * dopopdef - remove the topmost
+ * definitions of macro(s) or m4
+ * keyword(s).
+ */
+ if (argc > 2)
+ for (n = 2; n < argc; n++)
+ macro_popdef(argv[n]);
+ break;
+
+ case MKTMTYPE:
+ /*
+ * dotemp - create a temporary file
+ */
+ if (argc > 2) {
+ int fd;
+ char *temp;
+
+ temp = xstrdup(argv[2]);
+
+ fd = mkstemp(temp);
+ if (fd == -1)
+ err(1,
+ "%s at line %lu: couldn't make temp file %s",
+ CURRENT_NAME, CURRENT_LINE, argv[2]);
+ close(fd);
+ pbstr(temp);
+ free(temp);
+ }
+ break;
+
+ case TRNLTYPE:
+ /*
+ * dotranslit - replace all characters in
+ * the source string that appears in the
+ * "from" string with the corresponding
+ * characters in the "to" string.
+ */
+ if (argc > 3) {
+ char *temp;
+
+ temp = xalloc(strlen(argv[2])+1, NULL);
+ if (argc > 4)
+ map(temp, argv[2], argv[3], argv[4]);
+ else
+ map(temp, argv[2], argv[3], null);
+ pbstr(temp);
+ free(temp);
+ } else if (argc > 2)
+ pbstr(argv[2]);
+ break;
+
+ case INDXTYPE:
+ /*
+ * doindex - find the index of the second
+ * argument string in the first argument
+ * string. -1 if not present.
+ */
+ pbnum((argc > 3) ? indx(argv[2], argv[3]) : -1);
+ break;
+
+ case ERRPTYPE:
+ /*
+ * doerrp - print the arguments to stderr
+ * file
+ */
+ if (argc > 2) {
+ for (n = 2; n < argc; n++)
+ fprintf(stderr, "%s%s",
+ mimic_gnu && n == 2 ? "" : " ",
+ argv[n]);
+ if (!mimic_gnu)
+ fprintf(stderr, "\n");
+ }
+ break;
+
+ case DNLNTYPE:
+ /*
+ * dodnl - eat-up-to and including
+ * newline
+ */
+ while ((c = gpbc()) != '\n' && c != EOF)
+ ;
+ break;
+
+ case M4WRTYPE:
+ /*
+ * dom4wrap - set up for
+ * wrap-up/wind-down activity
+ */
+ if (argc > 2)
+ dom4wrap(argv[2]);
+ break;
+
+ case EXITTYPE:
+ /*
+ * doexit - immediate exit from m4.
+ */
+ killdiv();
+ exit((argc > 2) ? atoi(argv[2]) : 0);
+ break;
+
+ case DEFNTYPE:
+ if (argc > 2)
+ for (n = 2; n < argc; n++)
+ dodefn(argv[n]);
+ break;
+
+ case INDIRTYPE: /* Indirect call */
+ if (argc > 2)
+ doindir(argv, argc);
+ break;
+
+ case BUILTINTYPE: /* Builtins only */
+ if (argc > 2)
+ dobuiltin(argv, argc);
+ break;
+
+ case PATSTYPE:
+ if (argc > 2)
+ dopatsubst(argv, argc);
+ break;
+ case REGEXPTYPE:
+ if (argc > 2)
+ doregexp(argv, argc);
+ break;
+ case LINETYPE:
+ doprintlineno(infile+ilevel);
+ break;
+ case FILENAMETYPE:
+ doprintfilename(infile+ilevel);
+ break;
+ case SELFTYPE:
+ pbstr(rquote);
+ pbstr(argv[1]);
+ pbstr(lquote);
+ break;
+ default:
+ m4errx(1, "eval: major botch.");
+ break;
+ }
+}
+
+/*
+ * expand_macro - user-defined macro expansion
+ */
+void
+expand_macro(const char *argv[], int argc)
+{
+ const char *t;
+ const char *p;
+ int n;
+ int argno;
+
+ t = argv[0]; /* defn string as a whole */
+ p = t;
+ while (*p)
+ p++;
+ p--; /* last character of defn */
+ while (p > t) {
+ if (*(p - 1) != ARGFLAG)
+ PUSHBACK(*p);
+ else {
+ switch (*p) {
+
+ case '#':
+ pbnum(argc - 2);
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ argno = *p - '0';
+ if (mimic_gnu) {
+ const unsigned char *q =
+ (const unsigned char *)p;
+ while (isdigit(*++q)) {
+ bp--;
+ argno = argno * 10 + *q - '0';
+ }
+ }
+ if (argno < argc - 1)
+ pbstr(argv[argno + 1]);
+ break;
+ case '*':
+ if (argc > 2) {
+ for (n = argc - 1; n > 2; n--) {
+ pbstr(argv[n]);
+ pushback(COMMA);
+ }
+ pbstr(argv[2]);
+ }
+ break;
+ case '@':
+ if (argc > 2) {
+ for (n = argc - 1; n > 2; n--) {
+ pbstr(rquote);
+ pbstr(argv[n]);
+ pbstr(lquote);
+ pushback(COMMA);
+ }
+ pbstr(rquote);
+ pbstr(argv[2]);
+ pbstr(lquote);
+ }
+ break;
+ default:
+ PUSHBACK(*p);
+ PUSHBACK('$');
+ break;
+ }
+ p--;
+ }
+ p--;
+ }
+ if (p == t) /* do last character */
+ PUSHBACK(*p);
+}
+
+
+/*
+ * dodefine - install definition in the table
+ */
+void
+dodefine(const char *name, const char *defn)
+{
+ if (!*name && !mimic_gnu)
+ m4errx(1, "null definition.");
+ else
+ macro_define(name, defn);
+}
+
+/*
+ * dodefn - push back a quoted definition of
+ * the given name.
+ */
+static void
+dodefn(const char *name)
+{
+ struct macro_definition *p;
+
+ if ((p = lookup_macro_definition(name)) != NULL) {
+ if ((p->type & TYPEMASK) == MACRTYPE) {
+ pbstr(rquote);
+ pbstr(p->defn);
+ pbstr(lquote);
+ } else {
+ pbstr(p->defn);
+ pbstr(BUILTIN_MARKER);
+ }
+ }
+}
+
+/*
+ * dopushdef - install a definition in the hash table
+ * without removing a previous definition. Since
+ * each new entry is entered in *front* of the
+ * hash bucket, it hides a previous definition from
+ * lookup.
+ */
+static void
+dopushdef(const char *name, const char *defn)
+{
+ if (!*name && !mimic_gnu)
+ m4errx(1, "null definition.");
+ else
+ macro_pushdef(name, defn);
+}
+
+/*
+ * dump_one_def - dump the specified definition.
+ */
+static void
+dump_one_def(const char *name, struct macro_definition *p)
+{
+ if (!traceout)
+ traceout = stderr;
+ if (mimic_gnu) {
+ if ((p->type & TYPEMASK) == MACRTYPE)
+ fprintf(traceout, "%s:\t%s\n", name, p->defn);
+ else {
+ fprintf(traceout, "%s:\t<%s>\n", name, p->defn);
+ }
+ } else
+ fprintf(traceout, "`%s'\t`%s'\n", name, p->defn);
+}
+
+/*
+ * dodumpdef - dump the specified definitions in the hash
+ * table to stderr. If nothing is specified, the entire
+ * hash table is dumped.
+ */
+static void
+dodump(const char *argv[], int argc)
+{
+ int n;
+ struct macro_definition *p;
+
+ if (argc > 2) {
+ for (n = 2; n < argc; n++)
+ if ((p = lookup_macro_definition(argv[n])) != NULL)
+ dump_one_def(argv[n], p);
+ } else
+ macro_for_all(dump_one_def);
+}
+
+/*
+ * dotrace - mark some macros as traced/untraced depending upon on.
+ */
+static void
+dotrace(const char *argv[], int argc, int on)
+{
+ int n;
+
+ if (argc > 2) {
+ for (n = 2; n < argc; n++)
+ mark_traced(argv[n], on);
+ } else
+ mark_traced(NULL, on);
+}
+
+/*
+ * doifelse - select one of two alternatives - loop.
+ */
+static void
+doifelse(const char *argv[], int argc)
+{
+ cycle {
+ if (argc < 5)
+ m4errx(1, "wrong number of args for ifelse");
+ if (STREQ(argv[2], argv[3]))
+ pbstr(argv[4]);
+ else if (argc == 6)
+ pbstr(argv[5]);
+ else if (argc > 6) {
+ argv += 3;
+ argc -= 3;
+ continue;
+ }
+ break;
+ }
+}
+
+/*
+ * doinclude - include a given file.
+ */
+static int
+doincl(const char *ifile)
+{
+#ifndef REAL_FREEZE
+ if (thawing)
+ return 1;
+#endif
+ if (ilevel + 1 == MAXINP)
+ m4errx(1, "too many include files.");
+ if (fopen_trypath(infile+ilevel+1, ifile) != NULL) {
+ ilevel++;
+ bbase[ilevel] = bufbase = bp;
+ return (1);
+ } else
+ return (0);
+}
+
+#ifdef EXTENDED
+/*
+ * dopaste - include a given file without any
+ * macro processing.
+ */
+static int
+dopaste(const char *pfile)
+{
+ FILE *pf;
+ int c;
+
+ if ((pf = fopen(pfile, "r")) != NULL) {
+ if (synch_lines)
+ fprintf(active, "#line 1 \"%s\"\n", pfile);
+ while ((c = getc(pf)) != EOF)
+ putc(c, active);
+ (void) fclose(pf);
+ emit_synchline();
+ return (1);
+ } else
+ return (0);
+}
+#endif
+
+/*
+ * dochq - change quote characters
+ */
+static void
+dochq(const char *argv[], int ac)
+{
+ if (ac == 2) {
+ lquote[0] = LQUOTE; lquote[1] = EOS;
+ rquote[0] = RQUOTE; rquote[1] = EOS;
+ } else {
+ strlcpy(lquote, argv[2], sizeof(lquote));
+ if (ac > 3) {
+ strlcpy(rquote, argv[3], sizeof(rquote));
+ } else {
+ rquote[0] = ECOMMT; rquote[1] = EOS;
+ }
+ }
+}
+
+/*
+ * dochc - change comment characters
+ */
+static void
+dochc(const char *argv[], int argc)
+{
+/* XXX Note that there is no difference between no argument and a single
+ * empty argument.
+ */
+ if (argc == 2) {
+ scommt[0] = EOS;
+ ecommt[0] = EOS;
+ } else {
+ strlcpy(scommt, argv[2], sizeof(scommt));
+ if (argc == 3) {
+ ecommt[0] = ECOMMT; ecommt[1] = EOS;
+ } else {
+ strlcpy(ecommt, argv[3], sizeof(ecommt));
+ }
+ }
+}
+
+/*
+ * dom4wrap - expand text at EOF
+ */
+static void
+dom4wrap(const char *text)
+{
+ if (wrapindex >= maxwraps) {
+ if (maxwraps == 0)
+ maxwraps = 16;
+ else
+ maxwraps *= 2;
+ m4wraps = xrealloc(m4wraps, maxwraps * sizeof(*m4wraps),
+ "too many m4wraps");
+ }
+ m4wraps[wrapindex++] = xstrdup(text);
+}
+
+/*
+ * dodivert - divert the output to a temporary file
+ */
+static void
+dodiv(int n)
+{
+ int fd;
+
+ oindex = n;
+ if (n >= maxout) {
+ if (mimic_gnu)
+ resizedivs(n + 10);
+ else
+ n = 0; /* bitbucket */
+ }
+
+ if (n < 0)
+ n = 0; /* bitbucket */
+ if (outfile[n] == NULL) {
+ char fname[] = _PATH_DIVNAME;
+
+ if ((fd = mkstemp(fname)) < 0 ||
+ (outfile[n] = fdopen(fd, "w+")) == NULL)
+ err(1, "%s: cannot divert", fname);
+ if (unlink(fname) == -1)
+ err(1, "%s: cannot unlink", fname);
+ }
+ active = outfile[n];
+}
+
+/*
+ * doundivert - undivert a specified output, or all
+ * other outputs, in numerical order.
+ */
+static void
+doundiv(const char *argv[], int argc)
+{
+ int ind;
+ int n;
+
+ if (argc > 2) {
+ for (ind = 2; ind < argc; ind++) {
+ int e;
+ n = strtoi(argv[ind], NULL, 0, 1, INT_MAX, &e);
+ if (e) {
+ if (errno == EINVAL && mimic_gnu)
+ getdivfile(argv[ind]);
+ } else {
+ if (n < maxout && outfile[n] != NULL)
+ getdiv(n);
+ }
+ }
+ }
+ else
+ for (n = 1; n < maxout; n++)
+ if (outfile[n] != NULL)
+ getdiv(n);
+}
+
+/*
+ * dosub - select substring
+ */
+static void
+dosub(const char *argv[], int argc)
+{
+ const char *ap, *fc, *k;
+ int nc;
+
+ ap = argv[2]; /* target string */
+#ifdef EXPR
+ fc = ap + expr(argv[3]); /* first char */
+#else
+ fc = ap + atoi(argv[3]); /* first char */
+#endif
+ nc = strlen(fc);
+ if (argc >= 5)
+#ifdef EXPR
+ nc = min(nc, expr(argv[4]));
+#else
+ nc = min(nc, atoi(argv[4]));
+#endif
+ if (fc >= ap && fc < ap + strlen(ap))
+ for (k = fc + nc - 1; k >= fc; k--)
+ pushback(*k);
+}
+
+/*
+ * map:
+ * map every character of s1 that is specified in from
+ * into s3 and replace in s. (source s1 remains untouched)
+ *
+ * This is a standard implementation of map(s,from,to) function of ICON
+ * language. Within mapvec, we replace every character of "from" with
+ * the corresponding character in "to". If "to" is shorter than "from",
+ * than the corresponding entries are null, which means that those
+ * characters dissapear altogether. Furthermore, imagine
+ * map(dest, "sourcestring", "srtin", "rn..*") type call. In this case,
+ * `s' maps to `r', `r' maps to `n' and `n' maps to `*'. Thus, `s'
+ * ultimately maps to `*'. In order to achieve this effect in an efficient
+ * manner (i.e. without multiple passes over the destination string), we
+ * loop over mapvec, starting with the initial source character. if the
+ * character value (dch) in this location is different than the source
+ * character (sch), sch becomes dch, once again to index into mapvec, until
+ * the character value stabilizes (i.e. sch = dch, in other words
+ * mapvec[n] == n). Even if the entry in the mapvec is null for an ordinary
+ * character, it will stabilize, since mapvec[0] == 0 at all times. At the
+ * end, we restore mapvec* back to normal where mapvec[n] == n for
+ * 0 <= n <= 127. This strategy, along with the restoration of mapvec, is
+ * about 5 times faster than any algorithm that makes multiple passes over
+ * destination string.
+ */
+static void
+map(char *dest, const char *src, const char *from, const char *to)
+{
+ const char *tmp;
+ unsigned char sch, dch;
+ unsigned char found[256];
+ static char frombis[257];
+ static char tobis[257];
+ static unsigned char mapvec[256] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
+ 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
+ 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
+ 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86,
+ 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102,
+ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115,
+ 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128,
+ 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141,
+ 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154,
+ 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167,
+ 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180,
+ 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193,
+ 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206,
+ 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219,
+ 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232,
+ 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245,
+ 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
+ };
+
+ if (*src) {
+ if (mimic_gnu) {
+ /*
+ * expand character ranges on the fly
+ */
+ from = handledash(frombis, frombis + 256, from);
+ to = handledash(tobis, tobis + 256, to);
+ }
+ tmp = from;
+ /*
+ * create a mapping between "from" and
+ * "to"
+ */
+ memset(found, 0, sizeof(found));
+ for (; (sch = (unsigned char)*from) != '\0'; from++) {
+ if (!mimic_gnu || !found[sch]) {
+ found[sch] = 1;
+ mapvec[sch] = *to;
+ }
+ if (*to)
+ to++;
+ }
+
+ if (mimic_gnu) {
+ for (; (sch = (unsigned char)*src) != '\0'; src++) {
+ if (!found[sch])
+ *dest++ = sch;
+ else if ((dch = mapvec[sch]) != '\0')
+ *dest++ = dch;
+ }
+ } else {
+ while (*src) {
+ sch = (unsigned char)(*src++);
+ dch = mapvec[sch];
+ while (dch != sch) {
+ sch = dch;
+ dch = mapvec[sch];
+ }
+ if ((*dest = (char)dch))
+ dest++;
+ }
+ }
+ /*
+ * restore all the changed characters
+ */
+ while (*tmp) {
+ mapvec[(unsigned char)(*tmp)] = (unsigned char)(*tmp);
+ tmp++;
+ }
+ }
+ *dest = '\0';
+}
+
+
+/*
+ * handledash:
+ * use buffer to copy the src string, expanding character ranges
+ * on the way.
+ */
+static const char *
+handledash(char *buffer, char *end, const char *src)
+{
+ char *p;
+
+ p = buffer;
+ while(*src) {
+ if (src[1] == '-' && src[2]) {
+ unsigned char i;
+ if ((unsigned char)src[0] <= (unsigned char)src[2]) {
+ for (i = (unsigned char)src[0];
+ i <= (unsigned char)src[2]; i++) {
+ *p++ = i;
+ if (p == end) {
+ *p = '\0';
+ return buffer;
+ }
+ }
+ } else {
+ for (i = (unsigned char)src[0];
+ i >= (unsigned char)src[2]; i--) {
+ *p++ = i;
+ if (p == end) {
+ *p = '\0';
+ return buffer;
+ }
+ }
+ }
+ src += 3;
+ } else
+ *p++ = *src++;
+ if (p == end)
+ break;
+ }
+ *p = '\0';
+ return buffer;
+}
diff --git a/usr.bin/m4/expr.c b/usr.bin/m4/expr.c
new file mode 100644
index 0000000..a6c901f
--- /dev/null
+++ b/usr.bin/m4/expr.c
@@ -0,0 +1,50 @@
+/* $NetBSD: expr.c,v 1.19 2009/10/26 21:11:28 christos Exp $ */
+/* $OpenBSD: expr.c,v 1.17 2006/01/20 23:10:19 espie Exp $ */
+/*
+ * Copyright (c) 2004 Marc Espie <espie@cvs.openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: expr.c,v 1.19 2009/10/26 21:11:28 christos Exp $");
+#include <stdint.h>
+#include <stdio.h>
+#include <stddef.h>
+#include "mdef.h"
+#include "extern.h"
+
+int32_t end_result;
+const char *copy_toeval;
+
+extern void yy_scan_string(const char *);
+extern int yyparse(void);
+extern int yyerror(const char *);
+
+int
+yyerror(const char *msg)
+{
+ fprintf(stderr, "m4: %s in expr %s\n", msg, copy_toeval);
+ return(0);
+}
+
+int
+expr(const char *toeval)
+{
+ copy_toeval = toeval;
+ yy_scan_string(toeval);
+ yyparse();
+ return end_result;
+}
diff --git a/usr.bin/m4/extern.h b/usr.bin/m4/extern.h
new file mode 100644
index 0000000..1cb9de1
--- /dev/null
+++ b/usr.bin/m4/extern.h
@@ -0,0 +1,190 @@
+/* $OpenBSD: extern.h,v 1.49 2009/10/14 17:19:47 sthen Exp $ */
+/* $NetBSD: extern.h,v 1.19 2016/01/16 18:30:57 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ */
+
+/* eval.c */
+extern void eval(const char *[], int, int, int);
+extern void dodefine(const char *, const char *);
+extern unsigned long expansion_id;
+
+/* expr.c */
+extern int expr(const char *);
+
+/* gnum4.c */
+extern void addtoincludepath(const char *);
+extern struct input_file *fopen_trypath(struct input_file *, const char *);
+extern void doindir(const char *[], int);
+extern void dobuiltin(const char *[], int);
+extern void dopatsubst(const char *[], int);
+extern void doregexp(const char *[], int);
+
+extern void doprintlineno(struct input_file *);
+extern void doprintfilename(struct input_file *);
+
+extern void doesyscmd(const char *);
+extern void getdivfile(const char *);
+extern void doformat(const char *[], int);
+#ifdef REAL_FREEZE
+extern void freeze_state(const char *);
+extern void thaw_state(const char *);
+#endif
+
+
+/* look.c */
+
+#define FLAG_UNTRACED 0
+#define FLAG_TRACED 1
+#define FLAG_NO_TRACE 2
+
+extern void init_macros(void);
+extern ndptr lookup(const char *);
+extern void mark_traced(const char *, int);
+extern struct ohash macros;
+
+extern struct macro_definition *lookup_macro_definition(const char *);
+extern void macro_define(const char *, const char *);
+extern void macro_pushdef(const char *, const char *);
+extern void macro_popdef(const char *);
+extern void macro_undefine(const char *);
+extern void setup_builtin(const char *, unsigned int);
+extern void macro_for_all(void (*)(const char *, struct macro_definition *));
+#define macro_getdef(p) ((p)->d)
+#define macro_name(p) ((p)->name)
+#define macro_builtin_type(p) ((p)->builtin_type)
+#define is_traced(p) ((p)->trace_flags == FLAG_NO_TRACE ? (trace_flags & TRACE_ALL) : (p)->trace_flags)
+
+extern ndptr macro_getbuiltin(const char *);
+#ifdef REAL_FREEZE
+extern void dump_state(FILE *);
+extern void restore_state(FILE *);
+#endif
+
+/* main.c */
+extern void outputstr(const char *);
+extern void do_emit_synchline(void);
+#define emit_synchline() do { if (synch_lines) do_emit_synchline(); } while(0)
+
+/* misc.c */
+extern void chrsave(int);
+extern char *compute_prevep(void);
+extern void getdiv(int);
+extern ptrdiff_t indx(const char *, const char *);
+extern void initspaces(void);
+extern void killdiv(void);
+extern void pbnum(int);
+extern void pbnumbase(int, int, int);
+extern void pbunsigned(unsigned long);
+extern void pbstr(const char *);
+extern void pushback(int);
+extern void *xalloc(size_t, const char *fmt, ...) __printflike(2, 3);
+extern void *xrealloc(void *, size_t, const char *fmt, ...)
+ __printflike(3, 4);
+extern char *xstrdup(const char *);
+extern void resizedivs(int);
+extern size_t buffer_mark(void);
+extern void dump_buffer(FILE *, size_t);
+extern void __dead m4errx(int, const char *, ...) __printflike(2, 3);
+
+extern int obtain_char(struct input_file *);
+extern void set_input(struct input_file *, FILE *, const char *);
+extern void release_input(struct input_file *);
+
+/* speeded-up versions of chrsave/pushback */
+#define PUSHBACK(c) \
+ do { \
+ if (bp >= endpbb) \
+ enlarge_bufspace(); \
+ *bp++ = (c); \
+ } while(0)
+
+#define CHRSAVE(c) \
+ do { \
+ if (ep >= endest) \
+ enlarge_strspace(); \
+ *ep++ = (c); \
+ } while(0)
+
+/* and corresponding exposure for local symbols */
+extern void enlarge_bufspace(void);
+extern void enlarge_strspace(void);
+extern unsigned char *endpbb;
+extern char *endest;
+
+/* trace.c */
+extern unsigned int trace_flags;
+#define TRACE_ALL 512
+extern void trace_file(const char *);
+extern size_t trace(const char **, int, struct input_file *);
+extern void finish_trace(size_t);
+extern void set_trace_flags(const char *);
+extern FILE *traceout;
+
+extern ndptr hashtab[]; /* hash table for macros etc. */
+extern stae *mstack; /* stack of m4 machine */
+extern char *sstack; /* shadow stack, for string space extension */
+extern FILE *active; /* active output file pointer */
+extern struct input_file infile[];/* input file stack (0=stdin) */
+extern FILE **outfile; /* diversion array(0=bitbucket) */
+extern int maxout; /* maximum number of diversions */
+extern int fp; /* m4 call frame pointer */
+extern int ilevel; /* input file stack pointer */
+extern int oindex; /* diversion index. */
+extern int sp; /* current m4 stack pointer */
+extern unsigned char *bp; /* first available character */
+extern unsigned char *buf; /* push-back buffer */
+extern unsigned char *bufbase; /* buffer base for this ilevel */
+extern unsigned char *bbase[]; /* buffer base per ilevel */
+extern char ecommt[MAXCCHARS+1];/* end character for comment */
+extern char *ep; /* first free char in strspace */
+extern char lquote[MAXCCHARS+1];/* left quote character (`) */
+extern char **m4wraps; /* m4wrap string default. */
+extern int maxwraps; /* size of m4wraps array */
+extern int wrapindex; /* current index in m4wraps */
+extern int fatal_warnings; /* exit on warning */
+extern int quiet; /* no warnings */
+extern int nesting_limit; /* macro expansion nesting limit */
+#ifndef REAL_FREEZE
+extern FILE *freezef; /* copy of input */
+extern int thawing; /* don't process includes during thaw */
+#endif
+
+extern const char *null; /* as it says.. just a null. */
+extern char rquote[MAXCCHARS+1];/* right quote character (') */
+extern char scommt[MAXCCHARS+1];/* start character for comment */
+extern int synch_lines; /* line synchronisation directives */
+
+extern int mimic_gnu; /* behaves like gnu-m4 */
+extern int prefix_builtins; /* prefix builtin macros with m4_ */
diff --git a/usr.bin/m4/gnum4.c b/usr.bin/m4/gnum4.c
new file mode 100644
index 0000000..a450a99
--- /dev/null
+++ b/usr.bin/m4/gnum4.c
@@ -0,0 +1,817 @@
+/* $NetBSD: gnum4.c,v 1.10 2016/01/16 16:59:18 christos Exp $ */
+/* $OpenBSD: gnum4.c,v 1.39 2008/08/21 21:01:04 espie Exp $ */
+
+/*
+ * Copyright (c) 1999 Marc Espie
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * functions needed to support gnu-m4 extensions, including a fake freezing
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: gnum4.c,v 1.10 2016/01/16 16:59:18 christos Exp $");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <err.h>
+#include <paths.h>
+#include <regex.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include "mdef.h"
+#include "stdd.h"
+#include "extern.h"
+
+
+int mimic_gnu = 0;
+#ifndef SIZE_T_MAX
+#define SIZE_T_MAX (size_t)~0ull
+#endif
+
+/*
+ * Support for include path search
+ * First search in the current directory.
+ * If not found, and the path is not absolute, include path kicks in.
+ * First, -I options, in the order found on the command line.
+ * Then M4PATH env variable
+ */
+
+struct path_entry {
+ char *name;
+ struct path_entry *next;
+} *first, *last;
+
+static struct path_entry *new_path_entry(const char *);
+static void ensure_m4path(void);
+static struct input_file *dopath(struct input_file *, const char *);
+
+static struct path_entry *
+new_path_entry(const char *dirname)
+{
+ struct path_entry *n;
+
+ n = malloc(sizeof(struct path_entry));
+ if (!n)
+ errx(1, "out of memory");
+ n->name = strdup(dirname);
+ if (!n->name)
+ errx(1, "out of memory");
+ n->next = 0;
+ return n;
+}
+
+void
+addtoincludepath(const char *dirname)
+{
+ struct path_entry *n;
+
+ n = new_path_entry(dirname);
+
+ if (last) {
+ last->next = n;
+ last = n;
+ }
+ else
+ last = first = n;
+}
+
+static void
+ensure_m4path(void)
+{
+ static int envpathdone = 0;
+ char *envpath;
+ char *sweep;
+ char *path;
+
+ if (envpathdone)
+ return;
+ envpathdone = TRUE;
+ envpath = getenv("M4PATH");
+ if (!envpath)
+ return;
+ /* for portability: getenv result is read-only */
+ envpath = strdup(envpath);
+ if (!envpath)
+ errx(1, "out of memory");
+ for (sweep = envpath;
+ (path = strsep(&sweep, ":")) != NULL;)
+ addtoincludepath(path);
+ free(envpath);
+}
+
+static
+struct input_file *
+dopath(struct input_file *i, const char *filename)
+{
+ char path[MAXPATHLEN];
+ struct path_entry *pe;
+ FILE *f;
+
+ for (pe = first; pe; pe = pe->next) {
+ snprintf(path, sizeof(path), "%s/%s", pe->name, filename);
+ if ((f = fopen(path, "r")) != 0) {
+ set_input(i, f, path);
+ return i;
+ }
+ }
+ return NULL;
+}
+
+struct input_file *
+fopen_trypath(struct input_file *i, const char *filename)
+{
+ FILE *f;
+
+ f = fopen(filename, "r");
+ if (f != NULL) {
+ set_input(i, f, filename);
+ return i;
+ }
+ if (filename[0] == '/')
+ return NULL;
+
+ ensure_m4path();
+
+ return dopath(i, filename);
+}
+
+void
+doindir(const char *argv[], int argc)
+{
+ ndptr n;
+ struct macro_definition *p;
+
+ n = lookup(argv[2]);
+ if (n == NULL || (p = macro_getdef(n)) == NULL)
+ m4errx(1, "indir: undefined macro %s.", argv[2]);
+ argv[1] = p->defn;
+
+ eval(argv+1, argc-1, p->type, is_traced(n));
+}
+
+void
+dobuiltin(const char *argv[], int argc)
+{
+ ndptr p;
+
+ argv[1] = NULL;
+ p = macro_getbuiltin(argv[2]);
+ if (p != NULL)
+ eval(argv+1, argc-1, macro_builtin_type(p), is_traced(p));
+ else
+ m4errx(1, "unknown builtin %s.", argv[2]);
+}
+
+
+/* We need some temporary buffer space, as pb pushes BACK and substitution
+ * proceeds forward... */
+static char *buffer;
+static size_t bufsize = 0;
+static size_t current = 0;
+
+static void addchars(const char *, size_t);
+static void addchar(int);
+static char *twiddle(const char *);
+static char *getstring(void);
+static void exit_regerror(int, const char *, regex_t *) __dead;
+static void do_subst(const char *, const char *, regex_t *, const char *,
+ regmatch_t *);
+static void do_regexpindex(const char *, const char *, regex_t *, regmatch_t *);
+static void do_regexp(const char *, const char *, regex_t *, const char *, regmatch_t *);
+static void add_sub(size_t, const char *, regex_t *, regmatch_t *);
+static void add_replace(const char *, regex_t *, const char *, regmatch_t *);
+#define addconstantstring(s) addchars((s), sizeof(s)-1)
+
+static void
+addchars(const char *c, size_t n)
+{
+ if (n == 0)
+ return;
+ while (current + n > bufsize) {
+ if (bufsize == 0)
+ bufsize = 1024;
+ else
+ bufsize *= 2;
+ buffer = xrealloc(buffer, bufsize, NULL);
+ }
+ memcpy(buffer+current, c, n);
+ current += n;
+}
+
+static void
+addchar(int c)
+{
+ if (current +1 > bufsize) {
+ if (bufsize == 0)
+ bufsize = 1024;
+ else
+ bufsize *= 2;
+ buffer = xrealloc(buffer, bufsize, NULL);
+ }
+ buffer[current++] = c;
+}
+
+static char *
+getstring(void)
+{
+ addchar('\0');
+ current = 0;
+ return buffer;
+}
+
+
+static void
+exit_regerror(int er, const char *pat, regex_t *re)
+{
+ size_t errlen;
+ char *errbuf;
+
+ errlen = regerror(er, re, NULL, 0);
+ errbuf = xalloc(errlen,
+ "malloc in regerror: %lu", (unsigned long)errlen);
+ regerror(er, re, errbuf, errlen);
+ m4errx(1, "regular expression error: %s for: `%s'", errbuf, pat);
+}
+
+static void
+add_sub(size_t n, const char *string, regex_t *re, regmatch_t *pm)
+{
+ if (n > re->re_nsub) {
+ if (!quiet)
+ warnx("No subexpression %zu", n);
+ if (fatal_warnings)
+ exit(EXIT_FAILURE);
+ }
+ /* Subexpressions that did not match are
+ * not an error. */
+ else if (pm[n].rm_so != -1 &&
+ pm[n].rm_eo != -1) {
+ addchars(string + pm[n].rm_so,
+ pm[n].rm_eo - pm[n].rm_so);
+ }
+}
+
+/* Add replacement string to the output buffer, recognizing special
+ * constructs and replacing them with substrings of the original string.
+ */
+static void
+add_replace(const char *string, regex_t *re, const char *replace, regmatch_t *pm)
+{
+ const char *p;
+
+ for (p = replace; *p != '\0'; p++) {
+ if (*p == '&' && !mimic_gnu) {
+ add_sub(0, string, re, pm);
+ continue;
+ }
+ if (*p == '\\') {
+ if (p[1] == '\\') {
+ addchar(p[1]);
+ p++;
+ continue;
+ }
+ if (p[1] == '&') {
+ if (mimic_gnu)
+ add_sub(0, string, re, pm);
+ else
+ addchar(p[1]);
+ p++;
+ continue;
+ }
+ if (isdigit((unsigned char)p[1])) {
+ add_sub(*(++p) - '0', string, re, pm);
+ continue;
+ }
+ }
+ addchar(*p);
+ }
+}
+
+static void
+do_subst(const char *pat, const char *string, regex_t *re, const char *replace,
+ regmatch_t *pm)
+{
+ int error;
+ int flags = 0;
+ const char *last_match = NULL;
+
+ while ((error = regexec(re, string, re->re_nsub+1, pm, flags)) == 0) {
+ if (pm[0].rm_eo != 0) {
+ if (string[pm[0].rm_eo-1] == '\n')
+ flags = 0;
+ else
+ flags = REG_NOTBOL;
+ }
+
+ /* NULL length matches are special... We use the `vi-mode'
+ * rule: don't allow a NULL-match at the last match
+ * position.
+ */
+ if (pm[0].rm_so == pm[0].rm_eo &&
+ string + pm[0].rm_so == last_match) {
+ if (*string == '\0')
+ return;
+ addchar(*string);
+ if (*string++ == '\n')
+ flags = 0;
+ else
+ flags = REG_NOTBOL;
+ continue;
+ }
+ last_match = string + pm[0].rm_so;
+ addchars(string, pm[0].rm_so);
+ add_replace(string, re, replace, pm);
+ string += pm[0].rm_eo;
+ buffer[current] = '\0';
+ }
+ while (*string)
+ addchar(*string++);
+ if (error != REG_NOMATCH)
+ exit_regerror(error, pat, re);
+ pbstr(string);
+}
+
+static void
+do_regexp(const char *pat, const char *string, regex_t *re, const char *replace,
+ regmatch_t *pm)
+{
+ int error;
+
+ switch(error = regexec(re, string, re->re_nsub+1, pm, 0)) {
+ case 0:
+ add_replace(string, re, replace, pm);
+ pbstr(getstring());
+ break;
+ case REG_NOMATCH:
+ break;
+ default:
+ exit_regerror(error, pat, re);
+ }
+}
+
+static void
+do_regexpindex(const char *pat, const char *string, regex_t *re, regmatch_t *pm)
+{
+ int error;
+
+ switch(error = regexec(re, string, re->re_nsub+1, pm, 0)) {
+ case 0:
+ pbunsigned(pm[0].rm_so);
+ break;
+ case REG_NOMATCH:
+ pbnum(-1);
+ break;
+ default:
+ exit_regerror(error, pat, re);
+ }
+}
+
+/* In Gnu m4 mode, parentheses for backmatch don't work like POSIX 1003.2
+ * says. So we twiddle with the regexp before passing it to regcomp.
+ */
+static char *
+twiddle(const char *p)
+{
+ /* + at start of regexp is a normal character for Gnu m4 */
+ if (*p == '^') {
+ addchar(*p);
+ p++;
+ }
+ if (*p == '+') {
+ addchar('\\');
+ }
+ /* This could use strcspn for speed... */
+ while (*p != '\0') {
+ if (*p == '\\') {
+ switch(p[1]) {
+ case '(':
+ case ')':
+ case '|':
+ addchar(p[1]);
+ break;
+ case 'w':
+ addconstantstring("[_a-zA-Z0-9]");
+ break;
+ case 'W':
+ addconstantstring("[^_a-zA-Z0-9]");
+ break;
+ case '<':
+ addconstantstring("[[:<:]]");
+ break;
+ case '>':
+ addconstantstring("[[:>:]]");
+ break;
+ default:
+ addchars(p, 2);
+ break;
+ }
+ p+=2;
+ continue;
+ }
+ if (*p == '(' || *p == ')' || *p == '|')
+ addchar('\\');
+
+ addchar(*p);
+ p++;
+ }
+ return getstring();
+}
+
+static int
+checkempty(const char *argv[], int argc)
+{
+ const char *s;
+ size_t len;
+
+ if (argc != 3 && argv[3][0] != '\0')
+ return 0;
+
+ if (argc == 3) {
+ if (!quiet)
+ warnx("Too few arguments to patsubst");
+ if (fatal_warnings)
+ exit(EXIT_FAILURE);
+ }
+
+ if (argv[4] && argc > 4)
+ len = strlen(argv[4]);
+ else
+ len = 0;
+ for (s = argv[2]; *s != '\0'; s++) {
+ addchars(argv[4], len);
+ addchar(*s);
+ }
+ return 1;
+}
+
+/* patsubst(string, regexp, opt replacement) */
+/* argv[2]: string
+ * argv[3]: regexp
+ * argv[4]: opt rep
+ */
+void
+dopatsubst(const char *argv[], int argc)
+{
+ if (argc < 3) {
+ if (!quiet)
+ warnx("Too few arguments to patsubst");
+ if (fatal_warnings)
+ exit(EXIT_FAILURE);
+ return;
+ }
+ /* special case: empty regexp */
+ if (!checkempty(argv, argc)) {
+
+ const char *pat;
+ int error;
+ regex_t re;
+ regmatch_t *pmatch;
+ int mode = REG_EXTENDED;
+ size_t l = strlen(argv[3]);
+
+ if (!mimic_gnu ||
+ (argv[3][0] == '^') ||
+ (l > 0 && argv[3][l-1] == '$'))
+ mode |= REG_NEWLINE;
+
+ pat = mimic_gnu ? twiddle(argv[3]) : argv[3];
+ error = regcomp(&re, pat, mode);
+ if (error != 0)
+ exit_regerror(error, pat, &re);
+
+ pmatch = xalloc(sizeof(regmatch_t) * (re.re_nsub+1), NULL);
+ do_subst(pat, argv[2], &re,
+ argc > 4 && argv[4] != NULL ? argv[4] : "", pmatch);
+ free(pmatch);
+ regfree(&re);
+ }
+ pbstr(getstring());
+}
+
+void
+doregexp(const char *argv[], int argc)
+{
+ int error;
+ regex_t re;
+ regmatch_t *pmatch;
+ const char *pat;
+
+ if (argc < 3) {
+ if (!quiet)
+ warnx("Too few arguments to regexp");
+ if (fatal_warnings)
+ exit(EXIT_FAILURE);
+ return;
+ }
+ if (checkempty(argv, argc)) {
+ return;
+ }
+
+ pat = mimic_gnu ? twiddle(argv[3]) : argv[3];
+ error = regcomp(&re, pat, REG_EXTENDED);
+ if (error != 0)
+ exit_regerror(error, pat, &re);
+
+ pmatch = xalloc(sizeof(regmatch_t) * (re.re_nsub+1), NULL);
+ if (argv[4] == NULL || argc == 4)
+ do_regexpindex(pat, argv[2], &re, pmatch);
+ else
+ do_regexp(pat, argv[2], &re, argv[4], pmatch);
+ free(pmatch);
+ regfree(&re);
+}
+
+void
+doformat(const char *argv[], int argc)
+{
+ const char *format = argv[2];
+ int pos = 3;
+ int left_padded;
+ long width;
+ size_t l;
+ const char *thisarg;
+ char temp[2];
+ size_t extra;
+
+ while (*format != 0) {
+ if (*format != '%') {
+ addchar(*format++);
+ continue;
+ }
+
+ format++;
+ if (*format == '%') {
+ addchar(*format++);
+ continue;
+ }
+ if (*format == 0) {
+ addchar('%');
+ break;
+ }
+
+ if (*format == '*') {
+ format++;
+ if (pos >= argc)
+ m4errx(1,
+ "Format with too many format specifiers.");
+ width = strtol(argv[pos++], NULL, 10);
+ } else {
+ char *eformat;
+ width = strtol(format, &eformat, 10);
+ format = eformat;
+ }
+ if (width < 0) {
+ left_padded = 1;
+ width = -width;
+ } else {
+ left_padded = 0;
+ }
+ if (*format == '.') {
+ format++;
+ if (*format == '*') {
+ format++;
+ if (pos >= argc)
+ m4errx(1,
+ "Format with too many format specifiers.");
+ extra = strtol(argv[pos++], NULL, 10);
+ } else {
+ char *eformat;
+ extra = strtol(format, &eformat, 10);
+ format = eformat;
+ }
+ } else {
+ extra = SIZE_T_MAX;
+ }
+ if (pos >= argc)
+ m4errx(1, "Format with too many format specifiers.");
+ switch(*format) {
+ case 's':
+ thisarg = argv[pos++];
+ break;
+ case 'c':
+ temp[0] = strtoul(argv[pos++], NULL, 10);
+ temp[1] = 0;
+ thisarg = temp;
+ break;
+ default:
+ m4errx(1, "Unsupported format specification: %s.",
+ argv[2]);
+ }
+ format++;
+ l = strlen(thisarg);
+ if (l > extra)
+ l = extra;
+ if (!left_padded) {
+ while (l < (size_t)width--)
+ addchar(' ');
+ }
+ addchars(thisarg, l);
+ if (left_padded) {
+ while (l < (size_t)width--)
+ addchar(' ');
+ }
+ }
+ pbstr(getstring());
+}
+
+void
+doesyscmd(const char *cmd)
+{
+ int p[2];
+ pid_t pid, cpid;
+ const char *argv[4];
+ int cc;
+ int status;
+
+ /* Follow gnu m4 documentation: first flush buffers. */
+ fflush(NULL);
+
+ argv[0] = "sh";
+ argv[1] = "-c";
+ argv[2] = cmd;
+ argv[3] = NULL;
+
+ /* Just set up standard output, share stderr and stdin with m4 */
+ if (pipe(p) == -1)
+ err(1, "bad pipe");
+ switch(cpid = fork()) {
+ case -1:
+ err(1, "bad fork");
+ /* NOTREACHED */
+ case 0:
+ (void) close(p[0]);
+ (void) dup2(p[1], 1);
+ (void) close(p[1]);
+ execv(_PATH_BSHELL, __UNCONST(argv));
+ exit(1);
+ default:
+ /* Read result in two stages, since m4's buffer is
+ * pushback-only. */
+ (void) close(p[1]);
+ do {
+ char result[BUFSIZE];
+ cc = read(p[0], result, sizeof result);
+ if (cc > 0)
+ addchars(result, cc);
+ } while (cc > 0 || (cc == -1 && errno == EINTR));
+
+ (void) close(p[0]);
+ while ((pid = wait(&status)) != cpid && pid >= 0)
+ continue;
+ pbstr(getstring());
+ }
+}
+
+void
+getdivfile(const char *name)
+{
+ FILE *f;
+ int c;
+
+ f = fopen(name, "r");
+ if (!f)
+ return;
+
+ while ((c = getc(f))!= EOF)
+ putc(c, active);
+ (void) fclose(f);
+}
+
+#ifdef REAL_FREEZE
+void
+freeze_state(const char *fname)
+{
+ FILE *f;
+
+ if ((f = fopen(fname, "wb")) == NULL)
+ m4errx(EXIT_FAILURE, "Can't open output freeze file `%s' (%s)",
+ fname, strerror(errno));
+ fprintf(f, "# This is a frozen state file generated by %s\nV1\n",
+ getprogname());
+ fprintf(f, "Q%zu,%zu\n%s%s\n", strlen(lquote), strlen(rquote),
+ lquote, rquote);
+ fprintf(f, "C%zu,%zu\n%s%s\n", strlen(scommt), strlen(ecommt),
+ scommt, ecommt);
+ dump_state(f);
+ /* XXX: diversions? */
+ fprintf(f, "D-1,0\n");
+ fprintf(f, "# End of frozen state file\n");
+ fclose(f);
+}
+
+void
+thaw_state(const char *fname)
+{
+ char *name = NULL;
+ size_t nl, namelen = 0;
+ char *defn = NULL;
+ size_t dl, defnlen = 0;
+ size_t lineno = 0;
+ char line[1024], *ptr, type;
+ FILE *f;
+
+ if ((f = fopen(fname, "rb")) == NULL)
+ m4errx(EXIT_FAILURE, "Can't open frozen file `%s' (%s)",
+ fname, strerror(errno));
+
+#define GET() if (fgets(line, (int)sizeof(line), f) == NULL) goto out
+#define GETSTR(s, l) if (fread(s, 1, l, f) != l) goto out; else s[l] = '\0'
+
+ GET(); /* comment */
+ GET(); /* version */
+ if ((ptr = strrchr(line, '\n')) != NULL)
+ *ptr = '\0';
+ if (strcmp(line, "V1") != 0)
+ m4errx(EXIT_FAILURE, "Bad frozen version `%s'", line);
+
+ for (;;) {
+ GET();
+ lineno++;
+ switch (*line) {
+ case '\n':
+ continue;
+ case '#':
+ free(name);
+ free(defn);
+ fclose(f);
+ return;
+ default:
+ if (sscanf(line, "%c%zu,%zu\n", &type, &nl, &dl) != 3)
+ m4errx(EXIT_FAILURE, "%s, %zu: Bad line `%s'",
+ fname, lineno, line);
+ break;
+ }
+
+ switch (type) {
+ case 'Q':
+ if (nl >= sizeof(lquote) || dl >= sizeof(rquote))
+ m4errx(EXIT_FAILURE, "%s, %zu: Quote too long",
+ fname, lineno);
+ GETSTR(lquote, nl);
+ GETSTR(rquote, dl);
+ break;
+
+ case 'C':
+ if (nl >= sizeof(scommt) || dl >= sizeof(ecommt))
+ m4errx(EXIT_FAILURE, "%s, %zu: Comment too long",
+ fname, lineno);
+ GETSTR(scommt, nl);
+ GETSTR(ecommt, dl);
+ break;
+
+ case 'T':
+ case 'F':
+ if (nl >= namelen)
+ name = xrealloc(name, namelen = nl + 1,
+ "name grow");
+ if (dl >= defnlen)
+ defn = xrealloc(defn, defnlen = dl + 1,
+ "defn grow");
+ GETSTR(name, nl);
+ GETSTR(defn, dl);
+ macro_pushdef(name, defn);
+ break;
+
+ case 'D':
+ /* XXX: Not implemented */
+ break;
+
+ default:
+ m4errx(EXIT_FAILURE, "%s, %zu: Unknown type %c",
+ fname, lineno,type);
+ }
+ }
+out:
+ m4errx(EXIT_FAILURE, "Unexprected end of file in `%s'", fname);
+}
+#endif
diff --git a/usr.bin/m4/lib/ohash.h b/usr.bin/m4/lib/ohash.h
new file mode 100644
index 0000000..8ccdaf9
--- /dev/null
+++ b/usr.bin/m4/lib/ohash.h
@@ -0,0 +1,73 @@
+#ifndef OHASH_H
+#define OHASH_H
+/* $OpenBSD: ohash.h,v 1.8 2005/12/29 18:54:47 jaredy Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* Open hashing support.
+ * Open hashing was chosen because it is much lighter than other hash
+ * techniques, and more efficient in most cases.
+ */
+
+struct ohash_info {
+ ptrdiff_t key_offset;
+ void *data; /* user data */
+ void *(*halloc)(size_t, void *);
+ void (*hfree)(void *, size_t, void *);
+ void *(*alloc)(size_t, void *);
+};
+
+struct _ohash_record;
+
+struct ohash {
+ struct _ohash_record *t;
+ struct ohash_info info;
+ unsigned int size;
+ unsigned int total;
+ unsigned int deleted;
+};
+
+/* For this to be tweakable, we use small primitives, and leave part of the
+ * logic to the client application. e.g., hashing is left to the client
+ * application. We also provide a simple table entry lookup that yields
+ * a hashing table index (opaque) to be used in find/insert/remove.
+ * The keys are stored at a known position in the client data.
+ */
+__BEGIN_DECLS
+void ohash_init(struct ohash *, unsigned, struct ohash_info *);
+void ohash_delete(struct ohash *);
+
+unsigned int ohash_lookup_interval(struct ohash *, const char *,
+ const char *, u_int32_t);
+unsigned int ohash_lookup_memory(struct ohash *, const char *,
+ size_t, u_int32_t);
+void *ohash_find(struct ohash *, unsigned int);
+void *ohash_remove(struct ohash *, unsigned int);
+void *ohash_insert(struct ohash *, unsigned int, void *);
+void *ohash_first(struct ohash *, unsigned int *);
+void *ohash_next(struct ohash *, unsigned int *);
+unsigned int ohash_entries(struct ohash *);
+
+void *ohash_create_entry(struct ohash_info *, const char *, const char **);
+u_int32_t ohash_interval(const char *, const char **);
+
+unsigned int ohash_qlookupi(struct ohash *, const char *, const char **);
+unsigned int ohash_qlookup(struct ohash *, const char *);
+__END_DECLS
+#endif
+
diff --git a/usr.bin/m4/lib/ohash_create_entry.c b/usr.bin/m4/lib/ohash_create_entry.c
new file mode 100644
index 0000000..3bdc9ab
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_create_entry.c
@@ -0,0 +1,38 @@
+/* $OpenBSD: ohash_create_entry.c,v 1.2 2004/06/22 20:00:16 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+/* This handles the common case of variable length keys, where the
+ * key is stored at the end of the record.
+ */
+void *
+ohash_create_entry(struct ohash_info *i, const char *start, const char **end)
+{
+ char *p;
+
+ if (!*end)
+ *end = start + strlen(start);
+ p = (i->alloc)(i->key_offset + (*end - start) + 1, i->data);
+ if (p) {
+ memcpy(p+i->key_offset, start, *end-start);
+ p[i->key_offset + (*end - start)] = '\0';
+ }
+ return (void *)p;
+}
diff --git a/usr.bin/m4/lib/ohash_delete.c b/usr.bin/m4/lib/ohash_delete.c
new file mode 100644
index 0000000..12a9c0c
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_delete.c
@@ -0,0 +1,31 @@
+/* $OpenBSD: ohash_delete.c,v 1.2 2004/06/22 20:00:16 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+/* hash_delete only frees the hash structure. Use hash_first/hash_next
+ * to free entries as well. */
+void
+ohash_delete(struct ohash *h)
+{
+ (h->info.hfree)(h->t, sizeof(struct _ohash_record) * h->size,
+ h->info.data);
+#ifndef NDEBUG
+ h->t = NULL;
+#endif
+}
diff --git a/usr.bin/m4/lib/ohash_do.c b/usr.bin/m4/lib/ohash_do.c
new file mode 100644
index 0000000..2b8467d
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_do.c
@@ -0,0 +1,111 @@
+/* $OpenBSD: ohash_do.c,v 1.4 2004/06/22 20:00:16 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+static void ohash_resize(struct ohash *);
+
+static void
+ohash_resize(struct ohash *h)
+{
+ struct _ohash_record *n;
+ unsigned int ns, j;
+ unsigned int i, incr;
+
+ if (4 * h->deleted < h->total)
+ ns = h->size << 1;
+ else if (3 * h->deleted > 2 * h->total)
+ ns = h->size >> 1;
+ else
+ ns = h->size;
+ if (ns < MINSIZE)
+ ns = MINSIZE;
+#ifdef STATS_HASH
+ STAT_HASH_EXPAND++;
+ STAT_HASH_SIZE += ns - h->size;
+#endif
+ n = (h->info.halloc)(sizeof(struct _ohash_record) * ns, h->info.data);
+ if (!n)
+ return;
+
+ for (j = 0; j < h->size; j++) {
+ if (h->t[j].p != NULL && h->t[j].p != DELETED) {
+ i = h->t[j].hv % ns;
+ incr = ((h->t[j].hv % (ns - 2)) & ~1) + 1;
+ while (n[i].p != NULL) {
+ i += incr;
+ if (i >= ns)
+ i -= ns;
+ }
+ n[i].hv = h->t[j].hv;
+ n[i].p = h->t[j].p;
+ }
+ }
+ (h->info.hfree)(h->t, sizeof(struct _ohash_record) * h->size,
+ h->info.data);
+ h->t = n;
+ h->size = ns;
+ h->total -= h->deleted;
+ h->deleted = 0;
+}
+
+void *
+ohash_remove(struct ohash *h, unsigned int i)
+{
+ void *result = __UNCONST(h->t[i].p);
+
+ if (result == NULL || result == DELETED)
+ return NULL;
+
+#ifdef STATS_HASH
+ STAT_HASH_ENTRIES--;
+#endif
+ h->t[i].p = DELETED;
+ h->deleted++;
+ if (h->deleted >= MINDELETED && 4 * h->deleted > h->total)
+ ohash_resize(h);
+ return result;
+}
+
+void *
+ohash_find(struct ohash *h, unsigned int i)
+{
+ if (h->t[i].p == DELETED)
+ return NULL;
+ else
+ return __UNCONST(h->t[i].p);
+}
+
+void *
+ohash_insert(struct ohash *h, unsigned int i, void *p)
+{
+#ifdef STATS_HASH
+ STAT_HASH_ENTRIES++;
+#endif
+ if (h->t[i].p == DELETED) {
+ h->deleted--;
+ h->t[i].p = p;
+ } else {
+ h->t[i].p = p;
+ /* Arbitrary resize boundary. Tweak if not efficient enough. */
+ if (++h->total * 4 > h->size * 3)
+ ohash_resize(h);
+ }
+ return p;
+}
diff --git a/usr.bin/m4/lib/ohash_entries.c b/usr.bin/m4/lib/ohash_entries.c
new file mode 100644
index 0000000..683e204
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_entries.c
@@ -0,0 +1,26 @@
+/* $OpenBSD: ohash_entries.c,v 1.2 2004/06/22 20:00:16 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+unsigned int
+ohash_entries(struct ohash *h)
+{
+ return h->total - h->deleted;
+}
diff --git a/usr.bin/m4/lib/ohash_enum.c b/usr.bin/m4/lib/ohash_enum.c
new file mode 100644
index 0000000..966daa1
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_enum.c
@@ -0,0 +1,36 @@
+/* $OpenBSD: ohash_enum.c,v 1.3 2004/06/22 20:00:16 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+void *
+ohash_first(struct ohash *h, unsigned int *pos)
+{
+ *pos = 0;
+ return ohash_next(h, pos);
+}
+
+void *
+ohash_next(struct ohash *h, unsigned int *pos)
+{
+ for (; *pos < h->size; (*pos)++)
+ if (h->t[*pos].p != DELETED && h->t[*pos].p != NULL)
+ return __UNCONST(h->t[(*pos)++].p);
+ return NULL;
+}
diff --git a/usr.bin/m4/lib/ohash_init.3 b/usr.bin/m4/lib/ohash_init.3
new file mode 100644
index 0000000..a0939a8
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_init.3
@@ -0,0 +1,229 @@
+.\" $OpenBSD: ohash_init.3,v 1.14 2007/05/31 19:19:30 jmc Exp $
+.\" Copyright (c) 1999 Marc Espie <espie@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: May 31 2007 $
+.Dt OPEN_HASH 3
+.Os
+.Sh NAME
+.Nm ohash_init ,
+.Nm ohash_delete ,
+.Nm ohash_lookup_interval ,
+.Nm ohash_lookup_memory ,
+.Nm ohash_find ,
+.Nm ohash_remove ,
+.Nm ohash_insert ,
+.Nm ohash_first ,
+.Nm ohash_next ,
+.Nm ohash_entries
+.Nd light-weight open hashing
+.Sh SYNOPSIS
+.Fd #include <stdint.h>
+.Fd #include <stddef.h>
+.Fd #include <ohash.h>
+.Ft void
+.Fn ohash_init "struct ohash *h" "unsigned int size" "struct ohash_info *info"
+.Ft void
+.Fn ohash_delete "struct ohash *h"
+.Ft "unsigned int"
+.Fn ohash_lookup_interval "struct ohash *h" "const char *start" "const char *end" "uint32_t hv"
+.Ft "unsigned int"
+.Fn ohash_lookup_memory "struct ohash *h" "const char *k" "size_t s" "uint32_t hv"
+.Ft void *
+.Fn ohash_find "struct ohash *h" "unsigned int i"
+.Ft void *
+.Fn ohash_remove "struct ohash *h" "unsigned int i"
+.Ft void *
+.Fn ohash_insert "struct ohash *h" "unsigned int i" "void *p"
+.Ft void *
+.Fn ohash_first "struct ohash *h" "unsigned int *i"
+.Ft void *
+.Fn ohash_next "struct ohash *h" "unsigned int *i"
+.Ft "unsigned int"
+.Fn ohash_entries "struct ohash *h"
+.Sh DESCRIPTION
+These functions have been designed as a fast, extensible alternative to
+the usual hash table functions.
+They provide storage and retrieval of records indexed by keys,
+where a key is a contiguous sequence of bytes at a fixed position in
+each record.
+Keys can either be NUL-terminated strings or fixed-size memory areas.
+All functions take a pointer to an ohash structure as the
+.Fa h
+function argument.
+Storage for this structure should be provided by user code.
+.Pp
+.Fn ohash_init
+initializes the table to store roughly 2 to the power
+.Fa size
+elements.
+.Fa info
+holds the position of the key in each record, and two pointers to
+.Xr calloc 3
+and
+.Xr free 3 Ns -like
+functions, to use for managing the table internal storage.
+.Pp
+.Fn ohash_delete
+frees storage internal to
+.Fa h .
+Elements themselves should be freed by the user first, using for instance
+.Fn ohash_first
+and
+.Fn ohash_next .
+.Pp
+.Fn ohash_lookup_interval
+and
+.Fn ohash_lookup_memory
+are the basic look-up element functions.
+The hashing function result is provided by the user as
+.Fa hv .
+These return a
+.Qq slot
+in the ohash table
+.Fa h ,
+to be used with
+.Fn ohash_find ,
+.Fn ohash_insert ,
+or
+.Fn ohash_remove .
+This slot is only valid up to the next call to
+.Fn ohash_insert
+or
+.Fn ohash_remove .
+.Pp
+.Fn ohash_lookup_interval
+handles string-like keys.
+.Fn ohash_lookup_interval
+assumes the key is the interval between
+.Fa start
+and
+.Fa end ,
+exclusive,
+though the actual elements stored in the table should only contain
+NUL-terminated keys.
+.Pp
+.Fn ohash_lookup_memory
+assumes the key is the memory area starting at
+.Fa k
+of size
+.Fa s .
+All bytes are significant in key comparison.
+.Pp
+.Fn ohash_find
+retrieves an element from a slot
+.Fa i
+returned by the
+.Fn ohash_lookup*
+functions.
+It returns
+.Dv NULL
+if the slot is empty.
+.Pp
+.Fn ohash_insert
+inserts a new element
+.Fa p
+at slot
+.Fa i .
+Slot
+.Fa i
+must be empty and element
+.Fa p
+must have a key corresponding to the
+.Fn ohash_lookup*
+call.
+.Pp
+.Fn ohash_remove
+removes the element at slot
+.Fa i .
+It returns the removed element, for user code to dispose of, or
+.Dv NULL
+if the slot was empty.
+.Pp
+.Fn ohash_first
+and
+.Fn ohash_next
+can be used to access all elements in an ohash table, like this:
+.Bd -literal -offset indent
+for (n = ohash_first(h, &i); n != NULL; n = ohash_next(h, &i))
+ do_something_with(n);
+.Ed
+.Pp
+.Fa i
+points to an auxiliary unsigned integer used to record the current position
+in the ohash table.
+Those functions are safe to use even while entries are added to/removed
+from the table, but in such a case they don't guarantee that new entries
+will be returned.
+As a special case, they can safely be used to free elements in the table.
+.Pp
+.Fn ohash_entries
+returns the number of elements in the hash table.
+.Sh STORAGE HANDLING
+Only
+.Fn ohash_init ,
+.Fn ohash_insert ,
+.Fn ohash_remove
+and
+.Fn ohash_delete
+may call the user-supplied memory functions.
+It is the responsibility of the user memory allocation code to verify
+that those calls did not fail.
+.Pp
+If memory allocation fails,
+.Fn ohash_init
+returns a useless hash table.
+.Fn ohash_insert
+and
+.Fn ohash_remove
+still perform the requested operation, but the returned table should be
+considered read-only.
+It can still be accessed by
+.Fn ohash_lookup* ,
+.Fn ohash_find ,
+.Fn ohash_first
+and
+.Fn ohash_next
+to dump relevant information to disk before aborting.
+.Sh THREAD SAFETY
+The open hashing functions are not thread-safe by design.
+In particular, in a threaded environment, there is no guarantee that a
+.Qq slot
+will not move between a
+.Fn ohash_lookup*
+and a
+.Fn ohash_find ,
+.Fn ohash_insert
+or
+.Fn ohash_remove
+call.
+.Pp
+Multi-threaded applications should explicitly protect ohash table access.
+.Sh SEE ALSO
+.Xr ohash_interval 3
+.Rs
+.%A Donald E. Knuth
+.%B The Art of Computer Programming
+.%V Vol. 3
+.%P pp 506-550
+.%D 1973
+.Re
+.Sh STANDARDS
+Those functions are completely non-standard and should be avoided in
+portable programs.
+.Sh HISTORY
+Those functions were designed and written for
+.Ox
+.Xr make 1
+by Marc Espie in 1999.
diff --git a/usr.bin/m4/lib/ohash_init.c b/usr.bin/m4/lib/ohash_init.c
new file mode 100644
index 0000000..4d24fa4
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_init.c
@@ -0,0 +1,41 @@
+/* $OpenBSD: ohash_init.c,v 1.2 2004/06/22 20:00:16 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+void
+ohash_init(struct ohash *h, unsigned int size, struct ohash_info *info)
+{
+ h->size = 1UL << size;
+ if (h->size < MINSIZE)
+ h->size = MINSIZE;
+#ifdef STATS_HASH
+ STAT_HASH_CREATION++;
+ STAT_HASH_SIZE += h->size;
+#endif
+ /* Copy info so that caller may free it. */
+ h->info.key_offset = info->key_offset;
+ h->info.halloc = info->halloc;
+ h->info.hfree = info->hfree;
+ h->info.alloc = info->alloc;
+ h->info.data = info->data;
+ h->t = (h->info.halloc)(sizeof(struct _ohash_record) * h->size,
+ h->info.data);
+ h->total = h->deleted = 0;
+}
diff --git a/usr.bin/m4/lib/ohash_int.h b/usr.bin/m4/lib/ohash_int.h
new file mode 100644
index 0000000..88c818b
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_int.h
@@ -0,0 +1,23 @@
+/* $OpenBSD: ohash_int.h,v 1.3 2006/01/16 15:52:25 espie Exp $ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include "ohash.h"
+
+struct _ohash_record {
+ u_int32_t hv;
+ const char *p;
+};
+
+#define DELETED ((const char *)h)
+#define NONE (h->size)
+
+/* Don't bother changing the hash table if the change is small enough. */
+#define MINSIZE (1UL << 4)
+#define MINDELETED 4
diff --git a/usr.bin/m4/lib/ohash_interval.3 b/usr.bin/m4/lib/ohash_interval.3
new file mode 100644
index 0000000..2e56ca5
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_interval.3
@@ -0,0 +1,90 @@
+.\" $OpenBSD: ohash_interval.3,v 1.11 2007/05/31 19:19:30 jmc Exp $
+.\" Copyright (c) 2001 Marc Espie <espie@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: May 31 2007 $
+.Dt OPEN_HASH_HELPER 3
+.Os
+.Sh NAME
+.Nm ohash_interval ,
+.Nm ohash_create_entry ,
+.Nm ohash_qlookup ,
+.Nm ohash_qlookupi
+.Nd helper functions for open hashing
+.Sh SYNOPSIS
+.Fd #include <stdint.h>
+.Fd #include <stddef.h>
+.Fd #include <ohash.h>
+.Ft u_int32_t
+.Fn ohash_interval "const char *start" "const char **pend"
+.Ft "void *"
+.Fn ohash_create_entry "struct ohash_info *info" "const char *start" "const char **pend"
+.Ft "unsigned int"
+.Fn ohash_qlookupi "struct ohash *h" "const char *start" "const char **pend"
+.Ft "unsigned int"
+.Fn ohash_qlookup "struct ohash *h" "const char *start"
+.Sh DESCRIPTION
+These functions are commonly used to simplify open hashing usage, and use
+similar conventions.
+They operate indifferently on NUL-terminated strings
+.Po
+by setting
+.Fa *pend
+=
+.Dv NULL
+.Pc
+or memory ranges
+.Po
+delimited by
+.Fa start
+and
+.Fa *pend
+.Pc .
+For NUL-terminated strings, as a side effect, those functions
+set
+.Fa *pend
+to the terminating NUL byte.
+.Pp
+.Fn ohash_interval
+is a simple hashing function that yields good results on common data sets.
+.Pp
+.Fn ohash_create_entry
+can be used to create a new record with a given key.
+In that case,
+the alloc field of
+.Fa info
+should point to a
+.Xr malloc 3 Ns -like
+function to allocate the storage.
+.Pp
+.Fn ohash_qlookupi
+is a wrapper function that simply calls
+.Fn ohash_interval
+and
+.Fn ohash_lookup_interval .
+.Pp
+.Fn ohash_qlookup
+is a variation on
+.Fn ohash_qlookupi
+designed for NUL-terminated strings.
+.Sh SEE ALSO
+.Xr ohash_init 3
+.Sh STANDARDS
+Those functions are completely non-standard and should be avoided in
+portable programs.
+.Sh HISTORY
+Those functions were designed and written for
+.Ox
+.Xr make 1
+by Marc Espie in 1999.
diff --git a/usr.bin/m4/lib/ohash_interval.c b/usr.bin/m4/lib/ohash_interval.c
new file mode 100644
index 0000000..2434d3b
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_interval.c
@@ -0,0 +1,36 @@
+/* $OpenBSD: ohash_interval.c,v 1.3 2006/01/16 15:52:25 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+uint32_t
+ohash_interval(const char *s, const char **e)
+{
+ uint32_t k;
+
+ if (!*e)
+ *e = s + strlen(s);
+ if (s == *e)
+ k = 0;
+ else
+ k = *s++;
+ while (s != *e)
+ k = ((k << 2) | (k >> 30)) ^ *s++;
+ return k;
+}
diff --git a/usr.bin/m4/lib/ohash_lookup_interval.c b/usr.bin/m4/lib/ohash_lookup_interval.c
new file mode 100644
index 0000000..e497801
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_lookup_interval.c
@@ -0,0 +1,68 @@
+/* $OpenBSD: ohash_lookup_interval.c,v 1.3 2006/01/16 15:52:25 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+unsigned int
+ohash_lookup_interval(struct ohash *h, const char *start, const char *end,
+ uint32_t hv)
+{
+ unsigned int i, incr;
+ unsigned int empty;
+
+#ifdef STATS_HASH
+ STAT_HASH_LOOKUP++;
+#endif
+ empty = NONE;
+ i = hv % h->size;
+ incr = ((hv % (h->size-2)) & ~1) + 1;
+ while (h->t[i].p != NULL) {
+#ifdef STATS_HASH
+ STAT_HASH_LENGTH++;
+#endif
+ if (h->t[i].p == DELETED) {
+ if (empty == NONE)
+ empty = i;
+ } else if (h->t[i].hv == hv &&
+ strncmp(h->t[i].p+h->info.key_offset, start,
+ end - start) == 0 &&
+ (h->t[i].p+h->info.key_offset)[end-start] == '\0') {
+ if (empty != NONE) {
+ h->t[empty].hv = hv;
+ h->t[empty].p = h->t[i].p;
+ h->t[i].p = DELETED;
+ return empty;
+ } else {
+#ifdef STATS_HASH
+ STAT_HASH_POSITIVE++;
+#endif
+ return i;
+ }
+ }
+ i += incr;
+ if (i >= h->size)
+ i -= h->size;
+ }
+
+ /* Found an empty position. */
+ if (empty != NONE)
+ i = empty;
+ h->t[i].hv = hv;
+ return i;
+}
diff --git a/usr.bin/m4/lib/ohash_lookup_memory.c b/usr.bin/m4/lib/ohash_lookup_memory.c
new file mode 100644
index 0000000..93ded5a
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_lookup_memory.c
@@ -0,0 +1,64 @@
+/* $OpenBSD: ohash_lookup_memory.c,v 1.3 2006/01/16 15:52:25 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+unsigned int
+ohash_lookup_memory(struct ohash *h, const char *k, size_t size, uint32_t hv)
+{
+ unsigned int i, incr;
+ unsigned int empty;
+
+#ifdef STATS_HASH
+ STAT_HASH_LOOKUP++;
+#endif
+ empty = NONE;
+ i = hv % h->size;
+ incr = ((hv % (h->size-2)) & ~1) + 1;
+ while (h->t[i].p != NULL) {
+#ifdef STATS_HASH
+ STAT_HASH_LENGTH++;
+#endif
+ if (h->t[i].p == DELETED) {
+ if (empty == NONE)
+ empty = i;
+ } else if (h->t[i].hv == hv &&
+ memcmp(h->t[i].p+h->info.key_offset, k, size) == 0) {
+ if (empty != NONE) {
+ h->t[empty].hv = hv;
+ h->t[empty].p = h->t[i].p;
+ h->t[i].p = DELETED;
+ return empty;
+ } else {
+#ifdef STATS_HASH
+ STAT_HASH_POSITIVE++;
+#endif
+ } return i;
+ }
+ i += incr;
+ if (i >= h->size)
+ i -= h->size;
+ }
+
+ /* Found an empty position. */
+ if (empty != NONE)
+ i = empty;
+ h->t[i].hv = hv;
+ return i;
+}
diff --git a/usr.bin/m4/lib/ohash_qlookup.c b/usr.bin/m4/lib/ohash_qlookup.c
new file mode 100644
index 0000000..58b3454
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_qlookup.c
@@ -0,0 +1,27 @@
+/* $OpenBSD: ohash_qlookup.c,v 1.2 2004/06/22 20:00:17 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+unsigned int
+ohash_qlookup(struct ohash *h, const char *s)
+{
+ const char *e = NULL;
+ return ohash_qlookupi(h, s, &e);
+}
diff --git a/usr.bin/m4/lib/ohash_qlookupi.c b/usr.bin/m4/lib/ohash_qlookupi.c
new file mode 100644
index 0000000..2acec4e
--- /dev/null
+++ b/usr.bin/m4/lib/ohash_qlookupi.c
@@ -0,0 +1,29 @@
+/* $OpenBSD: ohash_qlookupi.c,v 1.2 2004/06/22 20:00:17 espie Exp $ */
+/* ex:ts=8 sw=4:
+ */
+
+/* Copyright (c) 1999, 2004 Marc Espie <espie@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "ohash_int.h"
+
+unsigned int
+ohash_qlookupi(struct ohash *h, const char *s, const char **e)
+{
+ u_int32_t hv;
+
+ hv = ohash_interval(s, e);
+ return ohash_lookup_interval(h, s, *e, hv);
+}
diff --git a/usr.bin/m4/lib/strtonum.c b/usr.bin/m4/lib/strtonum.c
new file mode 100644
index 0000000..8d11712
--- /dev/null
+++ b/usr.bin/m4/lib/strtonum.c
@@ -0,0 +1,73 @@
+/* $OpenBSD: strtonum.c,v 1.6 2004/08/03 19:38:01 millert Exp $ */
+
+/*
+ * Copyright (c) 2004 Ted Unangst and Todd Miller
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: strtonum.c,v 1.2 2009/10/26 21:14:18 christos Exp $");
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#define INVALID 1
+#define TOOSMALL 2
+#define TOOLARGE 3
+
+long long
+strtonum(const char *numstr, long long minval, long long maxval,
+ const char **errstrp);
+long long
+strtonum(const char *numstr, long long minval, long long maxval,
+ const char **errstrp)
+{
+ long long ll = 0;
+ char *ep;
+ int error = 0;
+ struct errval {
+ const char *errstr;
+ int err;
+ } ev[4] = {
+ { NULL, 0 },
+ { "invalid", EINVAL },
+ { "too small", ERANGE },
+ { "too large", ERANGE },
+ };
+
+ ev[0].err = errno;
+ errno = 0;
+ if (minval > maxval)
+ error = INVALID;
+ else {
+ ll = strtoll(numstr, &ep, 10);
+ if (numstr == ep || *ep != '\0')
+ error = INVALID;
+ else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)
+ error = TOOSMALL;
+ else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)
+ error = TOOLARGE;
+ }
+ if (errstrp != NULL)
+ *errstrp = ev[error].errstr;
+ errno = ev[error].err;
+ if (error)
+ ll = 0;
+
+ return (ll);
+}
+
diff --git a/usr.bin/m4/look.c b/usr.bin/m4/look.c
new file mode 100644
index 0000000..de2c282
--- /dev/null
+++ b/usr.bin/m4/look.c
@@ -0,0 +1,315 @@
+/* $NetBSD: look.c,v 1.13 2016/01/16 17:00:07 christos Exp $ */
+/* $OpenBSD: look.c,v 1.21 2009/10/14 17:23:17 sthen Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * look.c
+ * Facility: m4 macro processor
+ * by: oz
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: look.c,v 1.13 2016/01/16 17:00:07 christos Exp $");
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <ohash.h>
+#include "mdef.h"
+#include "stdd.h"
+#include "extern.h"
+
+static void *hash_alloc(size_t, void *);
+static void hash_free(void *, size_t, void *);
+static void *element_alloc(size_t, void *);
+static void setup_definition(struct macro_definition *, const char *,
+ const char *);
+
+static struct ohash_info macro_info = {
+ offsetof(struct ndblock, name),
+ NULL, hash_alloc, hash_free, element_alloc };
+
+struct ohash macros;
+
+/* Support routines for hash tables. */
+void *
+hash_alloc(size_t s, void *u UNUSED)
+{
+ void *storage = xalloc(s, "hash alloc");
+ if (storage)
+ memset(storage, 0, s);
+ return storage;
+}
+
+void
+hash_free(void *p, size_t s UNUSED, void *u UNUSED)
+{
+ free(p);
+}
+
+void *
+element_alloc(size_t s, void *u UNUSED)
+{
+ return xalloc(s, "element alloc");
+}
+
+void
+init_macros(void)
+{
+ ohash_init(&macros, 10, &macro_info);
+}
+
+/*
+ * find name in the hash table
+ */
+ndptr
+lookup(const char *name)
+{
+ return ohash_find(&macros, ohash_qlookup(&macros, name));
+}
+
+struct macro_definition *
+lookup_macro_definition(const char *name)
+{
+ ndptr p;
+
+ p = ohash_find(&macros, ohash_qlookup(&macros, name));
+ if (p)
+ return p->d;
+ else
+ return NULL;
+}
+
+static void
+setup_definition(struct macro_definition *d, const char *defn, const char *name)
+{
+ ndptr p;
+
+ if (strncmp(defn, BUILTIN_MARKER, sizeof(BUILTIN_MARKER)-1) == 0 &&
+ (p = macro_getbuiltin(defn+sizeof(BUILTIN_MARKER)-1)) != NULL) {
+ d->type = macro_builtin_type(p);
+ d->defn = xstrdup(defn+sizeof(BUILTIN_MARKER)-1);
+ } else {
+ if (!*defn)
+ d->defn = xstrdup(null);
+ else
+ d->defn = xstrdup(defn);
+ d->type = MACRTYPE;
+ }
+ if (STREQ(name, defn))
+ d->type |= RECDEF;
+}
+
+static ndptr
+create_entry(const char *name)
+{
+ const char *end = NULL;
+ unsigned int i;
+ ndptr n;
+
+ i = ohash_qlookupi(&macros, name, &end);
+ n = ohash_find(&macros, i);
+ if (n == NULL) {
+ n = ohash_create_entry(&macro_info, name, &end);
+ ohash_insert(&macros, i, n);
+ n->trace_flags = FLAG_NO_TRACE;
+ n->builtin_type = MACRTYPE;
+ n->d = NULL;
+ }
+ return n;
+}
+
+void
+macro_define(const char *name, const char *defn)
+{
+ ndptr n = create_entry(name);
+ if (n->d != NULL) {
+ if (n->d->defn != null)
+ free(n->d->defn);
+ } else {
+ n->d = xalloc(sizeof(struct macro_definition), NULL);
+ n->d->next = NULL;
+ }
+ setup_definition(n->d, defn, name);
+}
+
+void
+macro_pushdef(const char *name, const char *defn)
+{
+ ndptr n;
+ struct macro_definition *d;
+
+ n = create_entry(name);
+ d = xalloc(sizeof(struct macro_definition), NULL);
+ d->next = n->d;
+ n->d = d;
+ setup_definition(n->d, defn, name);
+}
+
+void
+macro_undefine(const char *name)
+{
+ ndptr n = lookup(name);
+ if (n != NULL) {
+ struct macro_definition *r, *r2;
+
+ for (r = n->d; r != NULL; r = r2) {
+ r2 = r->next;
+ if (r->defn != null)
+ free(r->defn);
+ free(r);
+ }
+ n->d = NULL;
+ }
+}
+
+void
+macro_popdef(const char *name)
+{
+ ndptr n = lookup(name);
+
+ if (n != NULL) {
+ struct macro_definition *r = n->d;
+ if (r != NULL) {
+ n->d = r->next;
+ if (r->defn != null)
+ free(r->defn);
+ free(r);
+ }
+ }
+}
+
+void
+macro_for_all(void (*f)(const char *, struct macro_definition *))
+{
+ ndptr n;
+ unsigned int i;
+
+ for (n = ohash_first(&macros, &i); n != NULL;
+ n = ohash_next(&macros, &i))
+ if (n->d != NULL)
+ f(n->name, n->d);
+}
+
+void
+setup_builtin(const char *name, unsigned int type)
+{
+ ndptr n;
+ char *name2;
+
+ if (prefix_builtins) {
+ name2 = xalloc(strlen(name)+3+1, NULL);
+ memcpy(name2, "m4_", 3);
+ memcpy(name2 + 3, name, strlen(name)+1);
+ } else
+ name2 = xstrdup(name);
+
+ n = create_entry(name2);
+ n->builtin_type = type;
+ n->d = xalloc(sizeof(struct macro_definition), NULL);
+ n->d->defn = name2;
+ n->d->type = type;
+ n->d->next = NULL;
+}
+
+void
+mark_traced(const char *name, int on)
+{
+ ndptr p;
+ unsigned int i;
+
+ if (name == NULL) {
+ if (on)
+ trace_flags |= TRACE_ALL;
+ else
+ trace_flags &= ~TRACE_ALL;
+ for (p = ohash_first(&macros, &i); p != NULL;
+ p = ohash_next(&macros, &i))
+ p->trace_flags = FLAG_NO_TRACE;
+ } else {
+ p = create_entry(name);
+ p->trace_flags = on;
+ }
+}
+
+ndptr
+macro_getbuiltin(const char *name)
+{
+ ndptr p;
+
+ p = lookup(name);
+ if (p == NULL || p->builtin_type == MACRTYPE)
+ return NULL;
+ else
+ return p;
+}
+
+#ifdef REAL_FREEZE
+static void
+recurse(FILE *f, ndptr n, struct macro_definition *d)
+{
+ if (d->next != NULL)
+ recurse(f, n, d->next);
+
+ // skip built-ins, because it is cheaper to do so
+ // and initialize them manually
+ if (d->type & (NOARGS|NEEDARGS))
+ return;
+ fprintf(f, "%c%zu,%zu\n%s%s\n",
+ (d->type & (NOARGS|NEEDARGS)) ? 'F' : 'T',
+ strlen(n->name), strlen(d->defn),
+ n->name, d->defn);
+}
+
+static void
+dump_entry(FILE *f, ndptr n)
+{
+ if (n->d == NULL)
+ return;
+ recurse(f, n, n->d);
+}
+
+void
+dump_state(FILE *f)
+{
+ ndptr n;
+ unsigned int i;
+ for (n = ohash_first(&macros, &i); n != NULL;
+ n = ohash_next(&macros, &i))
+ dump_entry(f, n);
+}
+#endif
diff --git a/usr.bin/m4/m4.1 b/usr.bin/m4/m4.1
new file mode 100644
index 0000000..f033cee
--- /dev/null
+++ b/usr.bin/m4/m4.1
@@ -0,0 +1,522 @@
+.\" $NetBSD: m4.1,v 1.27 2016/01/17 11:24:28 wiz Exp $
+.\" @(#) $OpenBSD: m4.1,v 1.56 2009/10/14 17:19:47 sthen Exp $
+.\"
+.\" Copyright (c) 1989, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Ozan Yigit at York University.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd January 16, 2015
+.Dt M4 1
+.Os
+.Sh NAME
+.Nm m4
+.Nd macro language processor
+.Sh SYNOPSIS
+.Nm m4
+.Op Fl EGgiPQsv
+.Oo
+.Sm off
+.Fl D Ar name Op No = Ar value
+.Sm on
+.Oc
+.Op Fl d Ar flags
+.Op Fl e Ar filename
+.Op Fl F Ar filename
+.Op Fl I Ar dirname
+.Op Fl L Ar number
+.Op Fl o Ar filename
+.Op Fl R Ar filename
+.Op Fl t Ar macro
+.Op Fl U Ns Ar name
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm m4
+utility is a macro processor that can be used as a front end to any
+language (e.g., C, ratfor, fortran, lex, and yacc).
+If no input files are given,
+.Nm m4
+reads from the standard input,
+otherwise files specified on the command line are
+processed in the given order.
+Input files can be regular files, files in the m4 include paths, or a
+single dash
+.Pq Sq - ,
+denoting standard input.
+.Nm m4
+writes
+the processed text to the standard output, unless told otherwise.
+.Pp
+Macro calls have the form name(argument1[, argument2, ..., argumentN]).
+.Pp
+There cannot be any space following the macro name and the open
+parenthesis
+.Sq \&( .
+If the macro name is not followed by an open
+parenthesis it is processed with no arguments.
+.Pp
+Macro names consist of a leading alphabetic or underscore
+possibly followed by alphanumeric or underscore characters, e.g.,
+valid macro names match the pattern
+.Dq [a-zA-Z_][a-zA-Z0-9_]* .
+.Pp
+In arguments to macros, leading unquoted space, tab, and newline
+.Pq Sq \en
+characters are ignored.
+To quote strings, use left and right single quotes
+.Po e.g.,\ \&
+.Sq "\ this is a string with a leading space"
+.Pc .
+You can change the quote characters with the
+.Ic changequote
+built-in macro.
+.Pp
+Most built-ins don't make any sense without arguments, and hence are not
+recognized as special when not followed by an open parenthesis.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl D , Fl Fl define Ar name Ns Op Pf = Ns Ar value
+Define the symbol
+.Ar name
+to have some value (or
+.Dv NULL ) .
+.It Fl d , Fl Fl debug Ar "flags"
+Set trace flags.
+.Ar flags
+may hold the following:
+.Bl -tag -width Ds
+.It Ar a
+print macro arguments.
+.It Ar c
+print macro expansion over several lines.
+.It Ar e
+print result of macro expansion.
+.It Ar f
+print filename location.
+.It Ar l
+print line number.
+.It Ar q
+quote arguments and expansion with the current quotes.
+.It Ar t
+start with all macros traced.
+.It Ar x
+number macro expansions.
+.It Ar V
+turn on all options.
+.El
+.Pp
+By default, trace is set to
+.Qq eq .
+.It Fl E , Fl Fl fatal-warnings
+Warnings make
+.Nm
+exit.
+.It Fl e , Fl Fl error-output Ar filename
+Redirect error output to filename.
+.It Fl F , Fl Fl freeze-state Ar filename
+Save the input state to
+.Ar filename .
+.It Fl G , Fl Fl traditional
+Disable GNU-m4 extensions.
+.It Fl g , Fl Fl gnu
+Activate GNU-m4 compatibility mode.
+In this mode, translit handles simple character
+ranges (e.g., a-z), regular expressions mimic emacs behavior,
+multiple m4wrap calls are handled as a stack,
+the number of diversions is unlimited,
+empty names for macro definitions are allowed,
+and eval understands
+.Sq 0rbase:value
+numbers.
+.It Fl Fl help
+Print help message and exit.
+.It Fl I , Fl Fl include Ar "dirname"
+Add directory
+.Ar dirname
+to the include path.
+.It Fl i , Fl Fl interactive
+Set unbuffered output, disable tty signals.
+.It Fl L , Fl Fl nesting-limit
+Set the nesting limit in macro expansions.
+This is unimplemented and unlimited.
+.It Fl o Ar filename
+Send trace output to
+.Ar filename .
+.It Fl P , Fl Fl prefix-builtins
+Prefix all built-in macros with
+.Sq m4_ .
+For example, instead of writing
+.Ic define ,
+use
+.Ic m4_define .
+.It Fl Q , Fl Fl quiet , Fl Fl silent
+Don't print warnings.
+.It Fl R , Fl Fl reload-state Ar filename
+Reload a previously saved state from
+.Ar filename .
+.It Fl s , Fl Fl synclines
+Output line synchronization directives, suitable for
+.Xr cpp 1 .
+.It Fl t , Fl Fl trace Ar macro
+Turn tracing on for
+.Ar macro .
+.It Fl U , Fl Fl undefine Ar "name"
+Undefine the symbol
+.Ar name .
+.It Fl v , Fl Fl version
+Print the version and exit.
+.El
+.Sh SYNTAX
+.Nm m4
+provides the following built-in macros.
+They may be redefined, losing their original meaning.
+Return values are null unless otherwise stated.
+.Bl -tag -width changequote
+.It Fn builtin name
+Calls a built-in by its
+.Fa name ,
+overriding possible redefinitions.
+.It Fn changecom startcomment endcomment
+Changes the start comment and end comment sequences.
+Comment sequences may be up to five characters long.
+The default values are the hash sign
+and the newline character.
+.Bd -literal -offset indent
+# This is a comment
+.Ed
+.Pp
+With no arguments, comments are turned off.
+With one single argument, the end comment sequence is set
+to the newline character.
+.It Fn changequote beginquote endquote
+Defines the open quote and close quote sequences.
+Quote sequences may be up to five characters long.
+The default values are the backquote character and the quote
+character.
+.Bd -literal -offset indent
+`Here is a quoted string'
+.Ed
+.Pp
+With no arguments, the default quotes are restored.
+With one single argument, the close quote sequence is set
+to the newline character.
+.It Fn decr arg
+Decrements the argument
+.Fa arg
+by 1.
+The argument
+.Fa arg
+must be a valid numeric string.
+.It Fn define name value
+Define a new macro named by the first argument
+.Fa name
+to have the
+value of the second argument
+.Fa value .
+Each occurrence of
+.Sq $n
+(where
+.Ar n
+is 0 through 9) is replaced by the
+.Ar n Ns 'th
+argument.
+.Sq $0
+is the name of the calling macro.
+Undefined arguments are replaced by a null string.
+.Sq $#
+is replaced by the number of arguments;
+.Sq $*
+is replaced by all arguments comma separated;
+.Sq $@
+is the same as
+.Sq $*
+but all arguments are quoted against further expansion.
+.It Fn defn name ...
+Returns the quoted definition for each argument.
+This can be used to rename
+macro definitions (even for built-in macros).
+.It Fn divert num
+There are 10 output queues (numbered 0-9).
+At the end of processing
+.Nm m4
+concatenates all the queues in numerical order to produce the
+final output.
+Initially the output queue is 0.
+The divert
+macro allows you to select a new output queue (an invalid argument
+passed to divert causes output to be discarded).
+.It Ic divnum
+Returns the current output queue number.
+.It Ic dnl
+Discard input characters up to and including the next newline.
+.It Fn dumpdef name ...
+Prints the names and definitions for the named items, or for everything
+if no arguments are passed.
+.It Fn errprint msg
+Prints the first argument on the standard error output stream.
+.It Fn esyscmd cmd
+Passes its first argument to a shell and returns the shell's standard output.
+Note that the shell shares its standard input and standard error with
+.Nm m4 .
+.It Fn eval expr[,radix[,minimum]]
+Computes the first argument as an arithmetic expression using 32-bit
+arithmetic.
+Operators are the standard C ternary, arithmetic, logical,
+shift, relational, bitwise, and parentheses operators.
+You can specify
+octal, decimal, and hexadecimal numbers as in C.
+The optional second argument
+.Fa radix
+specifies the radix for the result and the optional third argument
+.Fa minimum
+specifies the minimum number of digits in the result.
+.It Fn expr expr
+This is an alias for
+.Ic eval .
+.It Fn format formatstring arg1 ...
+Returns
+.Fa formatstring
+with escape sequences substituted with
+.Fa arg1
+and following arguments, in a way similar to
+.Xr printf 3 .
+This built-in is only available in GNU-m4 compatibility mode, and the only
+parameters implemented are there for autoconf compatibility:
+left-padding flag, an optional field width, a maximum field width,
+*-specified field widths, and the %s and %c data type.
+.It Fn ifdef name yes no
+If the macro named by the first argument is defined then return the second
+argument, otherwise the third.
+If there is no third argument, the value is
+.Dv NULL .
+The word
+.Qq unix
+is predefined.
+.It Fn ifelse a b yes ...
+If the first argument
+.Fa a
+matches the second argument
+.Fa b
+then
+.Fn ifelse
+returns
+the third argument
+.Fa yes .
+If the match fails the three arguments are
+discarded and the next three arguments are used until there is
+zero or one arguments left, either this last argument or
+.Dv NULL
+is returned if no other matches were found.
+.It Fn include name
+Returns the contents of the file specified in the first argument.
+If the file is not found as is, look through the include path:
+first the directories specified with
+.Fl I
+on the command line, then the environment variable
+.Ev M4PATH ,
+as a colon-separated list of directories.
+Include aborts with an error message if the file cannot be included.
+.It Fn incr arg
+Increments the argument by 1.
+The argument must be a valid numeric string.
+.It Fn index string substring
+Returns the index of the second argument in the first argument (e.g.,
+.Ic index(the quick brown fox jumped, fox)
+returns 16).
+If the second
+argument is not found index returns \-1.
+.It Fn indir macro arg1 ...
+Indirectly calls the macro whose name is passed as the first argument,
+with the remaining arguments passed as first, ... arguments.
+.It Fn len arg
+Returns the number of characters in the first argument.
+Extra arguments
+are ignored.
+.It Fn m4exit code
+Immediately exits with the return value specified by the first argument,
+0 if none.
+.It Fn m4wrap todo
+Allows you to define what happens at the final
+.Dv EOF ,
+usually for cleanup purposes (e.g.,
+.Ic m4wrap("cleanup(tempfile)")
+causes the macro cleanup to be
+invoked after all other processing is done).
+.Pp
+Multiple calls to
+.Fn m4wrap
+get inserted in sequence at the final
+.Dv EOF .
+.It Fn maketemp template
+Invokes
+.Xr mkstemp 3
+on the first argument, and returns the modified string.
+This can be used to create unique
+temporary file names.
+.It Fn paste file
+Includes the contents of the file specified by the first argument without
+any macro processing.
+Aborts with an error message if the file cannot be
+included.
+.It Fn patsubst string regexp replacement
+Substitutes a regular expression in a string with a replacement string.
+Usual substitution patterns apply: an ampersand
+.Pq Sq \&&
+is replaced by the string matching the regular expression.
+The string
+.Sq \e# ,
+where
+.Sq #
+is a digit, is replaced by the corresponding back-reference.
+.It Fn popdef arg ...
+Restores the
+.Ic pushdef Ns ed
+definition for each argument.
+.It Fn pushdef macro def
+Takes the same arguments as
+.Ic define ,
+but it saves the definition on a
+stack for later retrieval by
+.Fn popdef .
+.It Fn regexp string regexp replacement
+Finds a regular expression in a string.
+If no further arguments are given,
+it returns the first match position or \-1 if no match.
+If a third argument
+is provided, it returns the replacement string, with sub-patterns replaced.
+.It Fn shift arg1 ...
+Returns all but the first argument, the remaining arguments are
+quoted and pushed back with commas in between.
+The quoting
+nullifies the effect of the extra scan that will subsequently be
+performed.
+.It Fn sinclude file
+Similar to
+.Ic include ,
+except it ignores any errors.
+.It Fn spaste file
+Similar to
+.Fn paste ,
+except it ignores any errors.
+.It Fn substr string offset length
+Returns a substring of the first argument starting at the offset specified
+by the second argument and the length specified by the third argument.
+If no third argument is present it returns the rest of the string.
+.It Fn syscmd cmd
+Passes the first argument to the shell.
+Nothing is returned.
+.It Ic sysval
+Returns the return value from the last
+.Ic syscmd .
+.It Fn traceon arg ...
+Enables tracing of macro expansions for the given arguments, or for all
+macros if no argument is given.
+.It Fn traceoff arg ...
+Disables tracing of macro expansions for the given arguments, or for all
+macros if no argument is given.
+.It Fn translit string mapfrom mapto
+Transliterate the characters in the first argument from the set
+given by the second argument to the set given by the third.
+You cannot use
+.Xr tr 1
+style abbreviations.
+.It Fn undefine name1 ...
+Removes the definition for the macros specified by its arguments.
+.It Fn undivert arg ...
+Flushes the named output queues (or all queues if no arguments).
+.It Ic unix
+A pre-defined macro for testing the OS platform.
+.It Ic __line__
+Returns the current file's line number.
+.It Ic __file__
+Returns the current file's name.
+.El
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
+.Pp
+The flags
+.Op Fl dgIot
+and the macros
+.Ic builtin ,
+.Ic esyscmd ,
+.Ic expr ,
+.Ic format ,
+.Ic indir ,
+.Ic paste ,
+.Ic patsubst ,
+.Ic regexp ,
+.Ic spaste ,
+.Ic unix ,
+.Ic __line__ ,
+and
+.Ic __file__
+are extensions to that specification.
+.Pp
+The output format of tracing and of
+.Ic dumpdef
+are not specified in any standard,
+are likely to change and should not be relied upon.
+The current format of tracing is closely modelled on
+.Nm gnu-m4 ,
+to allow
+.Nm autoconf
+to work.
+.Pp
+The built-ins
+.Ic pushdef
+and
+.Ic popdef
+handle macro definitions as a stack.
+However,
+.Ic define
+interacts with the stack in an undefined way.
+In this implementation,
+.Ic define
+replaces the top-most definition only.
+Other implementations may erase all definitions on the stack instead.
+.Pp
+All built-ins do expand without arguments in many other
+.Nm m4 .
+.Pp
+Many other
+.Nm
+have dire size limitations with respect to buffer sizes.
+.Sh AUTHORS
+.An -nosplit
+.An Ozan Yigit Aq Mt oz@sis.yorku.ca
+and
+.An Richard A. O'Keefe Aq Mt ok@goanna.cs.rmit.OZ.AU .
+.Pp
+GNU-m4 compatibility extensions by
+.An Marc Espie Aq Mt espie@cvs.openbsd.org .
diff --git a/usr.bin/m4/main.c b/usr.bin/m4/main.c
new file mode 100644
index 0000000..cba65ad
--- /dev/null
+++ b/usr.bin/m4/main.c
@@ -0,0 +1,811 @@
+/* $OpenBSD: main.c,v 1.77 2009/10/14 17:19:47 sthen Exp $ */
+/* $NetBSD: main.c,v 1.46 2016/01/23 14:24:43 christos Exp $ */
+
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * main.c
+ * Facility: m4 macro processor
+ * by: oz
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: main.c,v 1.46 2016/01/23 14:24:43 christos Exp $");
+#include <assert.h>
+#include <signal.h>
+#include <getopt.h>
+#include <err.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <ohash.h>
+#include "mdef.h"
+#include "stdd.h"
+#include "extern.h"
+#include "pathnames.h"
+
+ndptr hashtab[HASHSIZE]; /* hash table for macros etc. */
+stae *mstack; /* stack of m4 machine */
+char *sstack; /* shadow stack, for string space extension */
+static size_t STACKMAX; /* current maximum size of stack */
+int sp; /* current m4 stack pointer */
+int fp; /* m4 call frame pointer */
+struct input_file infile[MAXINP];/* input file stack (0=stdin) */
+FILE **outfile; /* diversion array(0=bitbucket)*/
+int maxout;
+FILE *active; /* active output file pointer */
+int ilevel = 0; /* input file stack pointer */
+int oindex = 0; /* diversion index.. */
+const char *null = ""; /* as it says.. just a null.. */
+char **m4wraps = NULL; /* m4wraps array. */
+int maxwraps = 0; /* size of m4wraps array */
+int wrapindex = 0; /* current offset in m4wraps */
+char lquote[MAXCCHARS+1] = {LQUOTE}; /* left quote character (`) */
+char rquote[MAXCCHARS+1] = {RQUOTE}; /* right quote character (') */
+char scommt[MAXCCHARS+1] = {SCOMMT}; /* start character for comment */
+char ecommt[MAXCCHARS+1] = {ECOMMT}; /* end character for comment */
+int synch_lines = 0; /* line synchronisation for C preprocessor */
+int prefix_builtins = 0; /* -P option to prefix builtin keywords */
+int fatal_warnings = 0; /* -E option to exit on warnings */
+int quiet = 0; /* -Q option to silence warnings */
+int nesting_limit = -1; /* -L for nesting limit */
+const char *freeze = NULL; /* -F to freeze state */
+const char *reload = NULL; /* -R to reload state */
+#ifndef REAL_FREEZE
+FILE *freezef = NULL;
+int thawing = 0;
+#endif
+
+struct keyblk {
+ const char *knam; /* keyword name */
+ int ktyp; /* keyword type */
+};
+
+struct keyblk keywrds[] = { /* m4 keywords to be installed */
+ { "include", INCLTYPE },
+ { "sinclude", SINCTYPE },
+ { "define", DEFITYPE },
+ { "defn", DEFNTYPE },
+ { "divert", DIVRTYPE | NOARGS },
+ { "expr", EXPRTYPE },
+ { "eval", EXPRTYPE },
+ { "substr", SUBSTYPE },
+ { "ifelse", IFELTYPE },
+ { "ifdef", IFDFTYPE },
+ { "len", LENGTYPE },
+ { "incr", INCRTYPE },
+ { "decr", DECRTYPE },
+ { "dnl", DNLNTYPE | NOARGS },
+ { "changequote", CHNQTYPE | NOARGS },
+ { "changecom", CHNCTYPE | NOARGS },
+ { "index", INDXTYPE },
+#ifdef EXTENDED
+ { "paste", PASTTYPE },
+ { "spaste", SPASTYPE },
+ /* Newer extensions, needed to handle gnu-m4 scripts */
+ { "indir", INDIRTYPE},
+ { "builtin", BUILTINTYPE},
+ { "patsubst", PATSTYPE},
+ { "regexp", REGEXPTYPE},
+ { "esyscmd", ESYSCMDTYPE},
+ { "__file__", FILENAMETYPE | NOARGS},
+ { "__line__", LINETYPE | NOARGS},
+#endif
+ { "popdef", POPDTYPE },
+ { "pushdef", PUSDTYPE },
+ { "dumpdef", DUMPTYPE | NOARGS },
+ { "shift", SHIFTYPE | NOARGS },
+ { "translit", TRNLTYPE },
+ { "undefine", UNDFTYPE },
+ { "undivert", UNDVTYPE | NOARGS },
+ { "divnum", DIVNTYPE | NOARGS },
+ { "maketemp", MKTMTYPE },
+ { "errprint", ERRPTYPE | NOARGS },
+ { "m4wrap", M4WRTYPE | NOARGS },
+ { "m4exit", EXITTYPE | NOARGS },
+ { "syscmd", SYSCTYPE },
+ { "sysval", SYSVTYPE | NOARGS },
+ { "traceon", TRACEONTYPE | NOARGS },
+ { "traceoff", TRACEOFFTYPE | NOARGS },
+
+#if defined(unix) || defined(__unix__)
+ { "unix", SELFTYPE | NOARGS },
+#else
+#ifdef vms
+ { "vms", SELFTYPE | NOARGS },
+#endif
+#endif
+};
+
+#define MAXKEYS (sizeof(keywrds)/sizeof(struct keyblk))
+
+#define MAXRECORD 50
+static struct position {
+ char *name;
+ unsigned long line;
+} quotes[MAXRECORD], paren[MAXRECORD];
+
+static void record(struct position *, int);
+static void dump_stack(struct position *, int);
+
+static void macro(void);
+static void initkwds(void);
+static ndptr inspect(int, char *);
+static int do_look_ahead(int, const char *);
+static void reallyoutputstr(const char *);
+static void reallyputchar(int);
+
+static void enlarge_stack(void);
+static void help(void);
+
+static void
+usage(FILE *f)
+{
+ fprintf(f, "Usage: %s [-EGgiPQsv] [-Dname[=value]] [-d flags] "
+ "[-I dirname] [-o filename] [-L limit]\n"
+ "\t[-t macro] [-Uname] [file ...]\n", getprogname());
+}
+
+__dead static void
+onintr(int signo)
+{
+ char intrmessage[] = "m4: interrupted.\n";
+ write(STDERR_FILENO, intrmessage, sizeof(intrmessage)-1);
+ _exit(1);
+}
+
+#define OPT_HELP 1
+
+struct option longopts[] = {
+ { "debug", optional_argument, 0, 'd' },
+ { "define", required_argument, 0, 'D' },
+ { "error-output", required_argument, 0, 'e' },
+ { "fatal-warnings", no_argument, 0, 'E' },
+ { "freeze-state", required_argument, 0, 'F' },
+ { "gnu", no_argument, 0, 'g' },
+ { "help", no_argument, 0, OPT_HELP },
+ { "include", required_argument, 0, 'I' },
+ { "interactive", no_argument, 0, 'i' },
+ { "nesting-limit", required_argument, 0, 'L' },
+ { "prefix-builtins", no_argument, 0, 'P' },
+ { "quiet", no_argument, 0, 'Q' },
+ { "reload-state", required_argument, 0, 'R' },
+ { "silent", no_argument, 0, 'Q' },
+ { "synclines", no_argument, 0, 's' },
+ { "trace", required_argument, 0, 't' },
+ { "traditional", no_argument, 0, 'G' },
+ { "undefine", required_argument, 0, 'U' },
+ { "version", no_argument, 0, 'v' },
+#ifdef notyet
+ { "arglength", required_argument, 0, 'l' },
+ { "debugfile", optional_argument, 0, OPT_DEBUGFILE },
+ { "hashsize", required_argument, 0, 'H' },
+ { "warn-macro-sequence",optional_argument, 0, OPT_WARN_SEQUENCE },
+#endif
+ { 0, 0, 0, 0 },
+};
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ int n;
+ char *p;
+
+ setprogname(argv[0]);
+
+ if (signal(SIGINT, SIG_IGN) != SIG_IGN)
+ signal(SIGINT, onintr);
+
+ init_macros();
+ initspaces();
+ STACKMAX = INITSTACKMAX;
+
+ mstack = (stae *)xalloc(sizeof(stae) * STACKMAX, NULL);
+ sstack = (char *)xalloc(STACKMAX, NULL);
+
+ maxout = 0;
+ outfile = NULL;
+ resizedivs(MAXOUT);
+
+ while ((c = getopt_long(argc, argv, "D:d:e:EF:GgI:iL:o:PR:Qst:U:v",
+ longopts, NULL)) != -1)
+ switch(c) {
+ case 'D': /* define something..*/
+ for (p = optarg; *p; p++)
+ if (*p == '=')
+ break;
+ if (*p)
+ *p++ = EOS;
+ dodefine(optarg, p);
+ break;
+ case 'd':
+ set_trace_flags(optarg);
+ break;
+ case 'E':
+ fatal_warnings++;
+ break;
+ case 'e':
+ if (freopen(optarg, "w+", stderr) == NULL)
+ err(EXIT_FAILURE, "Can't redirect errors to `%s'",
+ optarg);
+ break;
+ case 'F':
+ freeze = optarg;
+#ifndef REAL_FREEZE
+ if ((freezef = fopen(freeze, "w")) == NULL)
+ err(EXIT_FAILURE, "Can't open `%s'", freeze);
+#endif
+ break;
+ case 'I':
+ addtoincludepath(optarg);
+ break;
+ case 'i':
+ setvbuf(stdout, NULL, _IONBF, 0);
+ signal(SIGINT, SIG_IGN);
+ break;
+ case 'G':
+ mimic_gnu = 0;
+ break;
+ case 'g':
+ mimic_gnu = 1;
+ break;
+ case 'L':
+ nesting_limit = atoi(optarg);
+ break;
+ case 'o':
+ trace_file(optarg);
+ break;
+ case 'P':
+ prefix_builtins = 1;
+ break;
+ case 'Q':
+ quiet++;
+ break;
+ case 'R':
+ reload = optarg;
+ break;
+ case 's':
+ synch_lines = 1;
+ break;
+ case 't':
+ mark_traced(optarg, 1);
+ break;
+ case 'U': /* undefine... */
+ macro_popdef(optarg);
+ break;
+ case 'v':
+ fprintf(stderr, "%s version %d\n", getprogname(),
+ VERSION);
+ return EXIT_SUCCESS;
+ case OPT_HELP:
+ help();
+ return EXIT_SUCCESS;
+ case '?':
+ default:
+ usage(stderr);
+ return EXIT_FAILURE;
+ }
+
+#ifdef REDIRECT
+ /*
+ * This is meant only for debugging; it makes all output
+ * go to a known file, even if the command line options
+ * send it elsewhere. It should not be turned of in production code.
+ */
+ if (freopen("/tmp/m4", "w+", stderr) == NULL)
+ err(EXIT_FAILURE, "Can't redirect errors to `%s'",
+ "/tmp/m4");
+#endif
+ argc -= optind;
+ argv += optind;
+
+
+ initkwds();
+ if (mimic_gnu)
+ setup_builtin("format", FORMATTYPE);
+
+ active = stdout; /* default active output */
+ bbase[0] = bufbase;
+
+ if (reload) {
+#ifdef REAL_FREEZE
+ thaw_state(reload);
+#else
+ if (fopen_trypath(infile, reload) == NULL)
+ err(1, "Can't open `%s'", reload);
+ sp = -1;
+ fp = 0;
+ thawing = 1;
+ macro();
+ thawing = 0;
+ release_input(infile);
+#endif
+ }
+
+ if (!argc) {
+ sp = -1; /* stack pointer initialized */
+ fp = 0; /* frame pointer initialized */
+ set_input(infile+0, stdin, "stdin");
+ /* default input (naturally) */
+ macro();
+ } else
+ for (; argc--; ++argv) {
+ p = *argv;
+ if (p[0] == '-' && p[1] == EOS)
+ set_input(infile, stdin, "stdin");
+ else if (fopen_trypath(infile, p) == NULL)
+ err(1, "%s", p);
+ sp = -1;
+ fp = 0;
+ macro();
+ release_input(infile);
+ }
+
+ if (wrapindex) {
+ int i;
+
+ ilevel = 0; /* in case m4wrap includes.. */
+ bufbase = bp = buf; /* use the entire buffer */
+ if (mimic_gnu) {
+ while (wrapindex != 0) {
+ for (i = 0; i < wrapindex; i++)
+ pbstr(m4wraps[i]);
+ wrapindex =0;
+ macro();
+ }
+ } else {
+ for (i = 0; i < wrapindex; i++) {
+ pbstr(m4wraps[i]);
+ macro();
+ }
+ }
+ }
+
+ if (active != stdout)
+ active = stdout; /* reset output just in case */
+ for (n = 1; n < maxout; n++) /* default wrap-up: undivert */
+ if (outfile[n] != NULL)
+ getdiv(n);
+ /* remove bitbucket if used */
+ if (outfile[0] != NULL) {
+ (void) fclose(outfile[0]);
+ }
+
+#ifdef REAL_FREEZE
+ if (freeze)
+ freeze_state(freeze);
+#else
+ if (freezef)
+ fclose(freezef);
+#endif
+
+ return 0;
+}
+
+/*
+ * Look ahead for `token'.
+ * (on input `t == token[0]')
+ * Used for comment and quoting delimiters.
+ * Returns 1 if `token' present; copied to output.
+ * 0 if `token' not found; all characters pushed back
+ */
+static int
+do_look_ahead(int t, const char *token)
+{
+ int i;
+
+ assert((unsigned char)t == (unsigned char)token[0]);
+
+ for (i = 1; *++token; i++) {
+ t = gpbc();
+ if (t == EOF || (unsigned char)t != (unsigned char)*token) {
+ pushback(t);
+ while (--i)
+ pushback(*--token);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+#define LOOK_AHEAD(t, token) (t != EOF && \
+ (unsigned char)(t)==(unsigned char)(token)[0] && \
+ do_look_ahead(t,token))
+
+/*
+ * macro - the work horse..
+ */
+static void
+macro(void)
+{
+ char token[MAXTOK+1];
+ int t, l;
+ ndptr p;
+ int nlpar;
+
+ cycle {
+ t = gpbc();
+
+ if (LOOK_AHEAD(t,lquote)) { /* strip quotes */
+ nlpar = 0;
+ record(quotes, nlpar++);
+ /*
+ * Opening quote: scan forward until matching
+ * closing quote has been found.
+ */
+ do {
+
+ l = gpbc();
+ if (LOOK_AHEAD(l,rquote)) {
+ if (--nlpar > 0)
+ outputstr(rquote);
+ } else if (LOOK_AHEAD(l,lquote)) {
+ record(quotes, nlpar++);
+ outputstr(lquote);
+ } else if (l == EOF) {
+ if (!quiet) {
+ if (nlpar == 1)
+ warnx("unclosed quote:");
+ else
+ warnx(
+ "%d unclosed quotes:",
+ nlpar);
+ dump_stack(quotes, nlpar);
+ }
+ exit(EXIT_FAILURE);
+ } else {
+ if (nlpar > 0) {
+ if (sp < 0)
+ reallyputchar(l);
+ else
+ CHRSAVE(l);
+ }
+ }
+ }
+ while (nlpar != 0);
+ } else if (sp < 0 && LOOK_AHEAD(t, scommt)) {
+ reallyoutputstr(scommt);
+
+ for(;;) {
+ t = gpbc();
+ if (LOOK_AHEAD(t, ecommt)) {
+ reallyoutputstr(ecommt);
+ break;
+ }
+ if (t == EOF)
+ break;
+ reallyputchar(t);
+ }
+ } else if (t == '_' || isalpha(t)) {
+ p = inspect(t, token);
+ if (p != NULL)
+ pushback(l = gpbc());
+ if (p == NULL || (l != LPAREN &&
+ (macro_getdef(p)->type & NEEDARGS) != 0))
+ outputstr(token);
+ else {
+ /*
+ * real thing.. First build a call frame:
+ */
+ pushf(fp); /* previous call frm */
+ pushf(macro_getdef(p)->type); /* type of the call */
+ pushf(is_traced(p));
+ pushf(0); /* parenthesis level */
+ fp = sp; /* new frame pointer */
+ /*
+ * now push the string arguments:
+ */
+ pushs1(macro_getdef(p)->defn); /* defn string */
+ pushs1((char *)macro_name(p)); /* macro name */
+ pushs(ep); /* start next..*/
+
+ if (l != LPAREN && PARLEV == 0) {
+ /* no bracks */
+ chrsave(EOS);
+
+ if ((size_t)sp == STACKMAX)
+ errx(1, "internal stack overflow");
+ eval((const char **) mstack+fp+1, 2,
+ CALTYP, TRACESTATUS);
+
+ ep = PREVEP; /* flush strspace */
+ sp = PREVSP; /* previous sp.. */
+ fp = PREVFP; /* rewind stack...*/
+ }
+ }
+ } else if (t == EOF) {
+ if (sp > -1 && ilevel <= 0) {
+ if (!quiet) {
+ warnx("unexpected end of input, "
+ "unclosed parenthesis:");
+ dump_stack(paren, PARLEV);
+ }
+ exit(EXIT_FAILURE);
+ }
+ if (ilevel <= 0)
+ break; /* all done thanks.. */
+ release_input(infile+ilevel--);
+ emit_synchline();
+ bufbase = bbase[ilevel];
+ continue;
+ } else if (sp < 0) { /* not in a macro at all */
+ reallyputchar(t); /* output directly.. */
+ }
+
+ else switch(t) {
+
+ case LPAREN:
+ if (PARLEV > 0)
+ chrsave(t);
+ while (isspace(l = gpbc())) /* skip blank, tab, nl.. */
+ if (PARLEV > 0)
+ chrsave(l);
+ pushback(l);
+ record(paren, PARLEV++);
+ break;
+
+ case RPAREN:
+ if (--PARLEV > 0)
+ chrsave(t);
+ else { /* end of argument list */
+ chrsave(EOS);
+
+ if ((size_t)sp == STACKMAX)
+ errx(1, "internal stack overflow");
+
+ eval((const char **) mstack+fp+1, sp-fp,
+ CALTYP, TRACESTATUS);
+
+ ep = PREVEP; /* flush strspace */
+ sp = PREVSP; /* previous sp.. */
+ fp = PREVFP; /* rewind stack...*/
+ }
+ break;
+
+ case COMMA:
+ if (PARLEV == 1) {
+ chrsave(EOS); /* new argument */
+ while (isspace(l = gpbc()))
+ ;
+ pushback(l);
+ pushs(ep);
+ } else
+ chrsave(t);
+ break;
+
+ default:
+ if (LOOK_AHEAD(t, scommt)) {
+ char *q;
+ for (q = scommt; *q; q++)
+ chrsave(*q);
+ for(;;) {
+ t = gpbc();
+ if (LOOK_AHEAD(t, ecommt)) {
+ for (q = ecommt; *q; q++)
+ chrsave(*q);
+ break;
+ }
+ if (t == EOF)
+ break;
+ CHRSAVE(t);
+ }
+ } else
+ CHRSAVE(t); /* stack the char */
+ break;
+ }
+ }
+}
+
+/*
+ * output string directly, without pushing it for reparses.
+ */
+void
+outputstr(const char *s)
+{
+ if (sp < 0)
+ reallyoutputstr(s);
+ else
+ while (*s)
+ CHRSAVE(*s++);
+}
+
+void
+reallyoutputstr(const char *s)
+{
+ if (synch_lines) {
+ while (*s) {
+ fputc(*s, active);
+ if (*s++ == '\n') {
+ infile[ilevel].synch_lineno++;
+ if (infile[ilevel].synch_lineno !=
+ infile[ilevel].lineno)
+ do_emit_synchline();
+ }
+ }
+ } else
+ fputs(s, active);
+}
+
+void
+reallyputchar(int c)
+{
+ putc(c, active);
+ if (synch_lines && c == '\n') {
+ infile[ilevel].synch_lineno++;
+ if (infile[ilevel].synch_lineno != infile[ilevel].lineno)
+ do_emit_synchline();
+ }
+}
+
+/*
+ * build an input token..
+ * consider only those starting with _ or A-Za-z.
+ */
+static ndptr
+inspect(int c, char *tp)
+{
+ char *name = tp;
+ char *etp = tp+MAXTOK;
+ ndptr p;
+
+ *tp++ = c;
+
+ while ((isalnum(c = gpbc()) || c == '_') && tp < etp)
+ *tp++ = c;
+ if (c != EOF)
+ PUSHBACK(c);
+ *tp = EOS;
+ /* token is too long, it won't match anything, but it can still
+ * be output. */
+ if (tp == ep) {
+ outputstr(name);
+ while (isalnum(c = gpbc()) || c == '_') {
+ if (sp < 0)
+ reallyputchar(c);
+ else
+ CHRSAVE(c);
+ }
+ *name = EOS;
+ return NULL;
+ }
+
+ p = ohash_find(&macros, ohash_qlookupi(&macros, name, (void *)&tp));
+ if (p == NULL)
+ return NULL;
+ if (macro_getdef(p) == NULL)
+ return NULL;
+ return p;
+}
+
+/*
+ * initkwds - initialise m4 keywords as fast as possible.
+ * This very similar to install, but without certain overheads,
+ * such as calling lookup. Malloc is not used for storing the
+ * keyword strings, since we simply use the static pointers
+ * within keywrds block.
+ */
+static void
+initkwds(void)
+{
+ unsigned int type;
+ size_t i;
+
+ for (i = 0; i < MAXKEYS; i++) {
+ type = keywrds[i].ktyp;
+ if ((keywrds[i].ktyp & NOARGS) == 0)
+ type |= NEEDARGS;
+ setup_builtin(keywrds[i].knam, type);
+ }
+}
+
+static void
+record(struct position *t, int lev)
+{
+ if (lev < MAXRECORD) {
+ t[lev].name = CURRENT_NAME;
+ t[lev].line = CURRENT_LINE;
+ }
+}
+
+static void
+dump_stack(struct position *t, int lev)
+{
+ int i;
+
+ for (i = 0; i < lev; i++) {
+ if (i == MAXRECORD) {
+ fprintf(stderr, " ...\n");
+ break;
+ }
+ fprintf(stderr, " %s at line %lu\n",
+ t[i].name, t[i].line);
+ }
+}
+
+
+static void
+enlarge_stack(void)
+{
+ STACKMAX += STACKMAX/2;
+ mstack = xrealloc(mstack, sizeof(stae) * STACKMAX,
+ "Evaluation stack overflow (%lu)",
+ (unsigned long)STACKMAX);
+ sstack = xrealloc(sstack, STACKMAX,
+ "Evaluation stack overflow (%lu)",
+ (unsigned long)STACKMAX);
+}
+
+static const struct {
+ const char *n;
+ const char *d;
+} nd [] = {
+{ "-d, --debug[=flags]", "set debug flags" },
+{ "-D, --define=name[=value]", "define macro" },
+{ "-e, --error-output=file", "send error output to file" },
+{ "-E, --fatal-warnings", "exit on warnings" },
+{ "-F, --freeze-state=file", "save state to file" },
+{ "-g, --gnu", "enable gnu extensions" },
+{ " --help", "print this message and exit" },
+{ "-I, --include=file", "include file" },
+{ "-i, --interactive", "unbuffer output, ignore tty signals" },
+{ "-L, --nesting-limit=num", "macro expansion nesting limit (unimpl)" },
+{ "-P, --prefix-builtins", "prefix builtins with m4_" },
+{ "-Q, --quiet", "don't print warnings" },
+{ "-R, --reload-state=file", "restore state from file" },
+{ "-Q, --silent", "don't print warnings" },
+{ "-s, --synclines", "output line directives for cpp(1)" },
+{ "-t, --trace=macro", "trace the named macro" },
+{ "-G, --traditional", "disable gnu extensions" },
+{ "-U, --undefine=name", "undefine the named macro" },
+{ "-v, --version", "print the version number and exit" },
+};
+
+static void
+help(void)
+{
+ size_t i;
+ fprintf(stdout, "%s version %d\n\n", getprogname(), VERSION);
+ usage(stdout);
+
+ fprintf(stdout, "\nThe long options are:\n");
+ for (i = 0; i < __arraycount(nd); i++)
+ fprintf(stdout, "\t%-25.25s\t%s\n", nd[i].n, nd[i].d);
+}
diff --git a/usr.bin/m4/mdef.h b/usr.bin/m4/mdef.h
new file mode 100644
index 0000000..f80c208
--- /dev/null
+++ b/usr.bin/m4/mdef.h
@@ -0,0 +1,237 @@
+/* $OpenBSD: mdef.h,v 1.29 2006/03/20 20:27:45 espie Exp $ */
+/* $NetBSD: mdef.h,v 1.17 2016/01/16 18:31:29 christos Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)mdef.h 8.1 (Berkeley) 6/6/93
+ */
+
+#ifdef __GNUC__
+# define UNUSED __attribute__((__unused__))
+#else
+# define UNUSED
+#endif
+
+#define MACRTYPE 1
+#define DEFITYPE 2
+#define EXPRTYPE 3
+#define SUBSTYPE 4
+#define IFELTYPE 5
+#define LENGTYPE 6
+#define CHNQTYPE 7
+#define SYSCTYPE 8
+#define UNDFTYPE 9
+#define INCLTYPE 10
+#define SINCTYPE 11
+#define PASTTYPE 12
+#define SPASTYPE 13
+#define INCRTYPE 14
+#define IFDFTYPE 15
+#define PUSDTYPE 16
+#define POPDTYPE 17
+#define SHIFTYPE 18
+#define DECRTYPE 19
+#define DIVRTYPE 20
+#define UNDVTYPE 21
+#define DIVNTYPE 22
+#define MKTMTYPE 23
+#define ERRPTYPE 24
+#define M4WRTYPE 25
+#define TRNLTYPE 26
+#define DNLNTYPE 27
+#define DUMPTYPE 28
+#define CHNCTYPE 29
+#define INDXTYPE 30
+#define SYSVTYPE 31
+#define EXITTYPE 32
+#define DEFNTYPE 33
+#define SELFTYPE 34
+#define INDIRTYPE 35
+#define BUILTINTYPE 36
+#define PATSTYPE 37
+#define FILENAMETYPE 38
+#define LINETYPE 39
+#define REGEXPTYPE 40
+#define ESYSCMDTYPE 41
+#define TRACEONTYPE 42
+#define TRACEOFFTYPE 43
+#define FORMATTYPE 44
+
+#define BUILTIN_MARKER "__builtin_"
+
+#define TYPEMASK 0xff /* Keep bits really corresponding to a type. */
+#define RECDEF 0x100 /* Pure recursive def, don't expand it */
+#define NOARGS 0x200 /* builtin needs no args */
+#define NEEDARGS 0x400 /* mark builtin that need args with this */
+
+/*
+ * m4 special characters
+ */
+
+#define ARGFLAG '$'
+#define LPAREN '('
+#define RPAREN ')'
+#define LQUOTE '`'
+#define RQUOTE '\''
+#define COMMA ','
+#define SCOMMT '#'
+#define ECOMMT '\n'
+
+#ifdef msdos
+#define system(str) (-1)
+#endif
+
+/*
+ * other important constants
+ */
+
+#define EOS '\0'
+#define MAXINP 10 /* maximum include files */
+#define MAXOUT 10 /* maximum # of diversions */
+#define BUFSIZE 4096 /* starting size of pushback buffer */
+#define INITSTACKMAX 4096 /* starting size of call stack */
+#define STRSPMAX 4096 /* starting size of string space */
+#define MAXTOK 512 /* maximum chars in a tokn */
+#define HASHSIZE 199 /* maximum size of hashtab */
+#define MAXCCHARS 5 /* max size of comment/quote delim */
+
+#define ALL 1
+#define TOP 0
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+#define cycle for(;;)
+
+/*
+ * m4 data structures
+ */
+
+typedef struct ndblock *ndptr;
+
+struct macro_definition {
+ struct macro_definition *next;
+ char *defn; /* definition.. */
+ unsigned int type; /* type of the entry.. */
+};
+
+
+struct ndblock { /* hashtable structure */
+ unsigned int builtin_type;
+ unsigned int trace_flags;
+ struct macro_definition *d;
+ char name[1]; /* entry name.. */
+};
+
+typedef union { /* stack structure */
+ int sfra; /* frame entry */
+ char *sstr; /* string entry */
+} stae;
+
+struct input_file {
+ FILE *file;
+ char *name;
+ unsigned long lineno;
+ unsigned long synch_lineno; /* used for -s */
+ int c;
+};
+
+#define CURRENT_NAME (infile[ilevel].name)
+#define CURRENT_LINE (infile[ilevel].lineno)
+#define TOKEN_LINE(f) (f->lineno - (f->c == '\n' ? 1 : 0))
+
+/*
+ * macros for readibility and/or speed
+ *
+ * gpbc() - get a possibly pushed-back character
+ * pushf() - push a call frame entry onto stack
+ * pushs() - push a string pointer onto stack
+ */
+#define gpbc() (bp > bufbase) ? *--bp : obtain_char(infile+ilevel)
+#define pushf(x) \
+ do { \
+ if ((size_t)++sp == STACKMAX) \
+ enlarge_stack();\
+ mstack[sp].sfra = (x); \
+ sstack[sp] = 0; \
+ } while (0)
+
+#define pushs(x) \
+ do { \
+ if ((size_t)++sp == STACKMAX) \
+ enlarge_stack();\
+ mstack[sp].sstr = (x); \
+ sstack[sp] = 1; \
+ } while (0)
+
+#define pushs1(x) \
+ do { \
+ if ((size_t)++sp == STACKMAX) \
+ enlarge_stack();\
+ mstack[sp].sstr = (x); \
+ sstack[sp] = 0; \
+ } while (0)
+
+/*
+ * . .
+ * | . | <-- sp | . |
+ * +-------+ +-----+
+ * | arg 3 ----------------------->| str |
+ * +-------+ | . |
+ * | arg 2 ---PREVEP-----+ .
+ * +-------+ |
+ * . | | |
+ * +-------+ | +-----+
+ * | plev | PARLEV +-------->| str |
+ * +-------+ | . |
+ * | type | CALTYP .
+ * +-------+
+ * | prcf ---PREVFP--+
+ * +-------+ |
+ * | . | PREVSP |
+ * . |
+ * +-------+ |
+ * | <----------+
+ * +-------+
+ *
+ */
+#define PARLEV (mstack[fp].sfra)
+#define CALTYP (mstack[fp-2].sfra)
+#define TRACESTATUS (mstack[fp-1].sfra)
+#define PREVEP (mstack[fp+3].sstr)
+#define PREVSP (fp-4)
+#define PREVFP (mstack[fp-3].sfra)
+
+#define VERSION 20150116
diff --git a/usr.bin/m4/misc.c b/usr.bin/m4/misc.c
new file mode 100644
index 0000000..d7bc8cf
--- /dev/null
+++ b/usr.bin/m4/misc.c
@@ -0,0 +1,414 @@
+/* $OpenBSD: misc.c,v 1.41 2009/10/14 17:19:47 sthen Exp $ */
+/* $NetBSD: misc.c,v 1.24 2016/01/16 17:01:22 christos Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: misc.c,v 1.24 2016/01/16 17:01:22 christos Exp $");
+#include <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <err.h>
+#include "mdef.h"
+#include "stdd.h"
+#include "extern.h"
+#include "pathnames.h"
+
+
+char *ep; /* first free char in strspace */
+static char *strspace; /* string space for evaluation */
+char *endest; /* end of string space */
+static size_t strsize = STRSPMAX;
+static size_t bufsize = BUFSIZE;
+
+unsigned char *buf; /* push-back buffer */
+unsigned char *bufbase; /* the base for current ilevel */
+unsigned char *bbase[MAXINP]; /* the base for each ilevel */
+unsigned char *bp; /* first available character */
+unsigned char *endpbb; /* end of push-back buffer */
+
+
+/*
+ * find the index of second str in the first str.
+ */
+ptrdiff_t
+indx(const char *s1, const char *s2)
+{
+ char *t;
+
+ t = strstr(s1, s2);
+ if (t == NULL)
+ return (-1);
+ else
+ return (t - s1);
+}
+/*
+ * pushback - push character back onto input
+ */
+void
+pushback(int c)
+{
+ if (c == EOF)
+ return;
+ if (bp >= endpbb)
+ enlarge_bufspace();
+ *bp++ = c;
+}
+
+/*
+ * pbstr - push string back onto input
+ * pushback is replicated to improve
+ * performance.
+ */
+void
+pbstr(const char *s)
+{
+ size_t n;
+
+ n = strlen(s);
+ while ((size_t)(endpbb - bp) <= n)
+ enlarge_bufspace();
+ while (n > 0)
+ *bp++ = s[--n];
+}
+
+/*
+ * pbnum - convert number to string, push back on input.
+ */
+void
+pbnum(int n)
+{
+ pbnumbase(n, 10, 0);
+}
+
+void
+pbnumbase(int n, int base, int d)
+{
+ static char digits[36] = "0123456789abcdefghijklmnopqrstuvwxyz";
+ int num;
+ int printed = 0;
+
+ if (base > 36)
+ m4errx(1, "base %d > 36: not supported.", base);
+
+ if (base < 2)
+ m4errx(1, "bad base %d for conversion.", base);
+
+ num = (n < 0) ? -n : n;
+ do {
+ pushback(digits[num % base]);
+ printed++;
+ }
+ while ((num /= base) > 0);
+
+ if (n < 0)
+ printed++;
+ while (printed++ < d)
+ pushback('0');
+
+ if (n < 0)
+ pushback('-');
+}
+
+/*
+ * pbunsigned - convert unsigned long to string, push back on input.
+ */
+void
+pbunsigned(unsigned long n)
+{
+ do {
+ pushback(n % 10 + '0');
+ }
+ while ((n /= 10) > 0);
+}
+
+void
+initspaces(void)
+{
+ int i;
+
+ strspace = xalloc(strsize+1, NULL);
+ ep = strspace;
+ endest = strspace+strsize;
+ buf = (unsigned char *)xalloc(bufsize, NULL);
+ bufbase = buf;
+ bp = buf;
+ endpbb = buf + bufsize;
+ for (i = 0; i < MAXINP; i++)
+ bbase[i] = buf;
+}
+
+void
+enlarge_strspace(void)
+{
+ char *newstrspace;
+ int i;
+
+ strsize *= 2;
+ newstrspace = malloc(strsize + 1);
+ if (!newstrspace)
+ errx(1, "string space overflow");
+ memcpy(newstrspace, strspace, strsize/2);
+ for (i = 0; i <= sp; i++)
+ if (sstack[i])
+ mstack[i].sstr = (mstack[i].sstr - strspace)
+ + newstrspace;
+ ep = (ep-strspace) + newstrspace;
+ free(strspace);
+ strspace = newstrspace;
+ endest = strspace + strsize;
+}
+
+void
+enlarge_bufspace(void)
+{
+ unsigned char *newbuf;
+ int i;
+
+ bufsize += bufsize/2;
+ newbuf = xrealloc(buf, bufsize, "too many characters pushed back");
+ for (i = 0; i < MAXINP; i++)
+ bbase[i] = (bbase[i]-buf)+newbuf;
+ bp = (bp-buf)+newbuf;
+ bufbase = (bufbase-buf)+newbuf;
+ buf = newbuf;
+ endpbb = buf+bufsize;
+}
+
+/*
+ * chrsave - put single char on string space
+ */
+void
+chrsave(int c)
+{
+ if (ep >= endest)
+ enlarge_strspace();
+ *ep++ = c;
+}
+
+/*
+ * read in a diversion file, and dispose it.
+ */
+void
+getdiv(int n)
+{
+ int c;
+
+ if (active == outfile[n])
+ m4errx(1, "undivert: diversion still active.");
+ rewind(outfile[n]);
+ while ((c = getc(outfile[n])) != EOF)
+ putc(c, active);
+ (void) fclose(outfile[n]);
+ outfile[n] = NULL;
+}
+
+/*
+ * killdiv - get rid of the diversion files
+ */
+void
+killdiv(void)
+{
+ int n;
+
+ for (n = 0; n < maxout; n++)
+ if (outfile[n] != NULL) {
+ (void) fclose(outfile[n]);
+ }
+}
+
+void
+m4errx(int exval, const char *fmt, ...)
+{
+ fprintf(stderr, "%s: ", getprogname());
+ fprintf(stderr, "%s at line %lu: ", CURRENT_NAME, CURRENT_LINE);
+ if (fmt != NULL) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ }
+ fprintf(stderr, "\n");
+ exit(exval);
+}
+
+/*
+ * resizedivs: allocate more diversion files */
+void
+resizedivs(int n)
+{
+ int i;
+
+ outfile = (FILE **)xrealloc(outfile, sizeof(FILE *) * n,
+ "too many diverts %d", n);
+ for (i = maxout; i < n; i++)
+ outfile[i] = NULL;
+ maxout = n;
+}
+
+void *
+xalloc(size_t n, const char *fmt, ...)
+{
+ void *p = malloc(n);
+
+ if (p == NULL) {
+ if (fmt == NULL)
+ err(1, "malloc");
+ else {
+ va_list va;
+
+ va_start(va, fmt);
+ verr(1, fmt, va);
+ va_end(va);
+ }
+ }
+ return p;
+}
+
+void *
+xrealloc(void *old, size_t n, const char *fmt, ...)
+{
+ char *p = realloc(old, n);
+
+ if (p == NULL) {
+ free(old);
+ if (fmt == NULL)
+ err(1, "realloc");
+ else {
+ va_list va;
+
+ va_start(va, fmt);
+ verr(1, fmt, va);
+ va_end(va);
+ }
+ }
+ return p;
+}
+
+char *
+xstrdup(const char *s)
+{
+ char *p = strdup(s);
+ if (p == NULL)
+ err(1, "strdup");
+ return p;
+}
+
+int
+obtain_char(struct input_file *f)
+{
+ if (f->c == EOF)
+ return EOF;
+
+ f->c = fgetc(f->file);
+#ifndef REAL_FREEZE
+ if (freezef)
+ fputc(f->c, freezef);
+#endif
+ if (f->c == '\n')
+ f->lineno++;
+
+ return f->c;
+}
+
+void
+set_input(struct input_file *f, FILE *real, const char *name)
+{
+ f->file = real;
+ f->lineno = 1;
+ f->c = 0;
+ f->name = xstrdup(name);
+ emit_synchline();
+}
+
+void
+do_emit_synchline(void)
+{
+ fprintf(active, "#line %lu \"%s\"\n",
+ infile[ilevel].lineno, infile[ilevel].name);
+ infile[ilevel].synch_lineno = infile[ilevel].lineno;
+}
+
+void
+release_input(struct input_file *f)
+{
+ if (f->file != stdin)
+ fclose(f->file);
+ f->c = EOF;
+ /*
+ * XXX can't free filename, as there might still be
+ * error information pointing to it.
+ */
+}
+
+void
+doprintlineno(struct input_file *f)
+{
+ pbunsigned(TOKEN_LINE(f));
+}
+
+void
+doprintfilename(struct input_file *f)
+{
+ pbstr(rquote);
+ pbstr(f->name);
+ pbstr(lquote);
+}
+
+/*
+ * buffer_mark/dump_buffer: allows one to save a mark in a buffer,
+ * and later dump everything that was added since then to a file.
+ */
+size_t
+buffer_mark(void)
+{
+ return bp - buf;
+}
+
+
+void
+dump_buffer(FILE *f, size_t m)
+{
+ unsigned char *s;
+
+ for (s = bp; (size_t)(s - buf) > m;)
+ fputc(*--s, f);
+}
diff --git a/usr.bin/m4/parser.y b/usr.bin/m4/parser.y
new file mode 100644
index 0000000..f8256ef
--- /dev/null
+++ b/usr.bin/m4/parser.y
@@ -0,0 +1,86 @@
+%{
+/* $NetBSD: parser.y,v 1.3 2015/01/04 18:31:09 joerg Exp $ */
+/* $OpenBSD: parser.y,v 1.6 2008/08/21 21:00:14 espie Exp $ */
+/*
+ * Copyright (c) 2004 Marc Espie <espie@cvs.openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: parser.y,v 1.3 2015/01/04 18:31:09 joerg Exp $");
+#include <stdint.h>
+#define YYSTYPE int32_t
+extern int32_t end_result;
+extern int yylex(void);
+extern int yyerror(const char *);
+%}
+%token NUMBER
+%token ERROR
+%left LOR
+%left LAND
+%left '|'
+%left '^'
+%left '&'
+%left EQ NE
+%left '<' LE '>' GE
+%left LSHIFT RSHIFT
+%left '+' '-'
+%left '*' '/' '%'
+%right UMINUS UPLUS '!' '~'
+
+%%
+
+top : expr { end_result = $1; }
+ ;
+expr : expr '+' expr { $$ = $1 + $3; }
+ | expr '-' expr { $$ = $1 - $3; }
+ | expr '*' expr { $$ = $1 * $3; }
+ | expr '/' expr {
+ if ($3 == 0) {
+ yyerror("division by zero");
+ exit(1);
+ }
+ $$ = $1 / $3;
+ }
+ | expr '%' expr {
+ if ($3 == 0) {
+ yyerror("modulo zero");
+ exit(1);
+ }
+ $$ = $1 % $3;
+ }
+ | expr LSHIFT expr { $$ = $1 << $3; }
+ | expr RSHIFT expr { $$ = $1 >> $3; }
+ | expr '<' expr { $$ = $1 < $3; }
+ | expr '>' expr { $$ = $1 > $3; }
+ | expr LE expr { $$ = $1 <= $3; }
+ | expr GE expr { $$ = $1 >= $3; }
+ | expr EQ expr { $$ = $1 == $3; }
+ | expr NE expr { $$ = $1 != $3; }
+ | expr '&' expr { $$ = $1 & $3; }
+ | expr '^' expr { $$ = $1 ^ $3; }
+ | expr '|' expr { $$ = $1 | $3; }
+ | expr LAND expr { $$ = $1 && $3; }
+ | expr LOR expr { $$ = $1 || $3; }
+ | '(' expr ')' { $$ = $2; }
+ | '-' expr %prec UMINUS { $$ = -$2; }
+ | '+' expr %prec UPLUS { $$ = $2; }
+ | '!' expr { $$ = !$2; }
+ | '~' expr { $$ = ~$2; }
+ | NUMBER
+ ;
+%%
+
diff --git a/usr.bin/m4/pathnames.h b/usr.bin/m4/pathnames.h
new file mode 100644
index 0000000..ef788e0
--- /dev/null
+++ b/usr.bin/m4/pathnames.h
@@ -0,0 +1,40 @@
+/* $NetBSD: pathnames.h,v 1.15 2011/03/08 23:55:19 riz Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Definitions of diversion files.
+ */
+#define _PATH_DIVNAME "/tmp/m4.0XXXXXXXXXX" /* unix diversion files */
diff --git a/usr.bin/m4/stdd.h b/usr.bin/m4/stdd.h
new file mode 100644
index 0000000..0cbd681
--- /dev/null
+++ b/usr.bin/m4/stdd.h
@@ -0,0 +1,54 @@
+/* $NetBSD: stdd.h,v 1.4 2003/08/07 11:14:34 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit at York University.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)stdd.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * standard defines
+ */
+
+#define max(a,b) ((a) > (b)? (a): (b))
+#define min(a,b) ((a) < (b)? (a): (b))
+
+#define iswhite(c) ((c) == ' ' || (c) == '\t')
+
+/*
+ * STREQ is an optimised strcmp(a,b)==0
+ * STREQN is an optimised strncmp(a,b,n)==0; assumes n > 0
+ */
+#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp(a, b) == 0)
+#define STREQN(a, b, n) ((a)[0] == (b)[0] && strncmp(a, b, n) == 0)
+
+#define YES 1
+#define NO 0
diff --git a/usr.bin/m4/tokenizer.l b/usr.bin/m4/tokenizer.l
new file mode 100644
index 0000000..fc4ebf1
--- /dev/null
+++ b/usr.bin/m4/tokenizer.l
@@ -0,0 +1,108 @@
+%{
+/* $NetBSD: tokenizer.l,v 1.6 2012/03/20 20:34:58 matt Exp $ */
+/* $OpenBSD: tokenizer.l,v 1.6 2008/08/21 21:00:14 espie Exp $ */
+/*
+ * Copyright (c) 2004 Marc Espie <espie@cvs.openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include "parser.h"
+__RCSID("$NetBSD: tokenizer.l,v 1.6 2012/03/20 20:34:58 matt Exp $");
+#include <stdlib.h>
+#include <errno.h>
+#include <stdint.h>
+#include <limits.h>
+
+extern int mimic_gnu;
+extern int32_t yylval;
+extern int yylex(void);
+extern int yywrap(void);
+
+int32_t number(void);
+int32_t parse_radix(void);
+
+%}
+
+%option nounput
+%option noinput
+
+delim [ \t\n]
+ws {delim}+
+hex 0[xX][0-9a-fA-F]+
+oct 0[0-7]*
+dec [1-9][0-9]*
+radix 0[rR][0-9]+:[0-9a-zA-Z]+
+
+%%
+{ws} {/* just skip it */}
+{hex}|{oct}|{dec} { yylval = number(); return(NUMBER); }
+{radix} { if (mimic_gnu) {
+ yylval = parse_radix(); return(NUMBER);
+ } else {
+ return(ERROR);
+ }
+ }
+"<=" { return(LE); }
+">=" { return(GE); }
+"<<" { return(LSHIFT); }
+">>" { return(RSHIFT); }
+"==" { return(EQ); }
+"!=" { return(NE); }
+"&&" { return(LAND); }
+"||" { return(LOR); }
+. { return yytext[0]; }
+%%
+
+int32_t
+number(void)
+{
+ long l;
+
+ errno = 0;
+ l = strtol(yytext, NULL, 0);
+ if (((l == LONG_MAX || l == LONG_MIN) && errno == ERANGE) ||
+ l > INT32_MAX || l < INT32_MIN) {
+ fprintf(stderr, "m4: numeric overflow in expr: %s\n", yytext);
+ }
+ return l;
+}
+
+int32_t
+parse_radix(void)
+{
+ long base;
+ char *next;
+ long l;
+
+ l = 0;
+ base = strtol(yytext+2, &next, 0);
+ if (base > 36 || next == NULL) {
+ fprintf(stderr, "m4: error in number %s\n", yytext);
+ } else {
+ next++;
+ while (*next != 0) {
+ if (*next >= '0' && *next <= '9')
+ l = base * l + *next - '0';
+ else if (*next >= 'a' && *next <= 'z')
+ l = base * l + *next - 'a' + 10;
+ else if (*next >= 'A' && *next <= 'Z')
+ l = base * l + *next - 'A' + 10;
+ next++;
+ }
+ }
+ return l;
+}
+
diff --git a/usr.bin/m4/trace.c b/usr.bin/m4/trace.c
new file mode 100644
index 0000000..bb9397f
--- /dev/null
+++ b/usr.bin/m4/trace.c
@@ -0,0 +1,203 @@
+/* $NetBSD: trace.c,v 1.8 2012/03/20 20:34:58 matt Exp $ */
+/* $OpenBSD: trace.c,v 1.15 2006/03/24 08:03:44 espie Exp $ */
+/*
+ * Copyright (c) 2001 Marc Espie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBSD
+ * PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: trace.c,v 1.8 2012/03/20 20:34:58 matt Exp $");
+
+#include <sys/types.h>
+#include <err.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "mdef.h"
+#include "stdd.h"
+#include "extern.h"
+
+FILE *traceout;
+
+#define TRACE_ARGS 1
+#define TRACE_EXPANSION 2
+#define TRACE_QUOTE 4
+#define TRACE_FILENAME 8
+#define TRACE_LINENO 16
+#define TRACE_CONT 32
+#define TRACE_ID 64
+#define TRACE_NEWFILE 128 /* not implemented yet */
+#define TRACE_INPUT 256 /* not implemented yet */
+
+static unsigned int letter_to_flag(int);
+static void print_header(struct input_file *);
+static int frame_level(void);
+
+
+unsigned int trace_flags = TRACE_QUOTE | TRACE_EXPANSION;
+
+void
+trace_file(const char *name)
+{
+
+ if (traceout && traceout != stderr)
+ fclose(traceout);
+ traceout = fopen(name, "w");
+ if (!traceout)
+ err(1, "can't open %s", name);
+}
+
+static unsigned int
+letter_to_flag(int c)
+{
+ switch(c) {
+ case 'a':
+ return TRACE_ARGS;
+ case 'e':
+ return TRACE_EXPANSION;
+ case 'q':
+ return TRACE_QUOTE;
+ case 'c':
+ return TRACE_CONT;
+ case 'x':
+ return TRACE_ID;
+ case 'f':
+ return TRACE_FILENAME;
+ case 'l':
+ return TRACE_LINENO;
+ case 'p':
+ return TRACE_NEWFILE;
+ case 'i':
+ return TRACE_INPUT;
+ case 't':
+ return TRACE_ALL;
+ case 'V':
+ return ~0;
+ default:
+ return 0;
+ }
+}
+
+void
+set_trace_flags(const char *s)
+{
+ char mode = 0;
+ unsigned int f = 0;
+
+ if (*s == '+' || *s == '-')
+ mode = *s++;
+ while (*s)
+ f |= letter_to_flag(*s++);
+ switch(mode) {
+ case 0:
+ trace_flags = f;
+ break;
+ case '+':
+ trace_flags |= f;
+ break;
+ case '-':
+ trace_flags &= ~f;
+ break;
+ }
+}
+
+static int
+frame_level(void)
+{
+ int level;
+ int framep;
+
+ for (framep = fp, level = 0; framep != 0;
+ level++,framep = mstack[framep-3].sfra)
+ ;
+ return level;
+}
+
+static void
+print_header(struct input_file *inp)
+{
+ fprintf(traceout, "m4trace:");
+ if (trace_flags & TRACE_FILENAME)
+ fprintf(traceout, "%s:", inp->name);
+ if (trace_flags & TRACE_LINENO)
+ fprintf(traceout, "%lu:", TOKEN_LINE(inp));
+ fprintf(traceout, " -%d- ", frame_level());
+ if (trace_flags & TRACE_ID)
+ fprintf(traceout, "id %lu: ", expansion_id);
+}
+
+size_t
+trace(const char *argv[], int argc, struct input_file *inp)
+{
+ if (!traceout)
+ traceout = stderr;
+ print_header(inp);
+ if (trace_flags & TRACE_CONT) {
+ fprintf(traceout, "%s ...\n", argv[1]);
+ print_header(inp);
+ }
+ fprintf(traceout, "%s", argv[1]);
+ if ((trace_flags & TRACE_ARGS) && argc > 2) {
+ char delim[3];
+ int i;
+
+ delim[0] = LPAREN;
+ delim[1] = EOS;
+ for (i = 2; i < argc; i++) {
+ fprintf(traceout, "%s%s%s%s", delim,
+ (trace_flags & TRACE_QUOTE) ? lquote : "",
+ argv[i],
+ (trace_flags & TRACE_QUOTE) ? rquote : "");
+ delim[0] = COMMA;
+ delim[1] = ' ';
+ delim[2] = EOS;
+ }
+ fprintf(traceout, "%c", RPAREN);
+ }
+ if (trace_flags & TRACE_CONT) {
+ fprintf(traceout, " -> ???\n");
+ print_header(inp);
+ fprintf(traceout, argc > 2 ? "%s(...)" : "%s", argv[1]);
+ }
+ if (trace_flags & TRACE_EXPANSION)
+ return buffer_mark();
+ else {
+ fprintf(traceout, "\n");
+ return SIZE_MAX;
+ }
+}
+
+void
+finish_trace(size_t mark)
+{
+ fprintf(traceout, " -> ");
+ if (trace_flags & TRACE_QUOTE)
+ fprintf(traceout, "%s", lquote);
+ dump_buffer(traceout, mark);
+ if (trace_flags & TRACE_QUOTE)
+ fprintf(traceout, "%s", rquote);
+ fprintf(traceout, "\n");
+}
diff --git a/usr.bin/make/Makefile.boot b/usr.bin/make/Makefile.boot
new file mode 100644
index 0000000..f5be09c
--- /dev/null
+++ b/usr.bin/make/Makefile.boot
@@ -0,0 +1,45 @@
+# $NetBSD: Makefile.boot,v 1.21 2014/02/24 07:23:44 skrll Exp $
+#
+# a very simple makefile...
+#
+# You only want to use this if you aren't running NetBSD.
+#
+# modify MACHINE and MACHINE_ARCH as appropriate for your target architecture
+#
+CC=gcc -O -g
+
+.c.o:
+ ${CC} ${CFLAGS} -c $< -o $@
+
+MACHINE=i386
+MACHINE_ARCH=i386
+# tested on HP-UX 10.20
+#MAKE_MACHINE=hppa
+#MAKE_MACHINE_ARCH=hppa
+CFLAGS= -DTARGET_MACHINE=\"${MACHINE}\" \
+ -DTARGET_MACHINE_ARCH=\"${MACHINE_ARCH}\" \
+ -DMAKE_MACHINE=\"${MACHINE}\"
+LIBS=
+
+OBJ=arch.o buf.o compat.o cond.o dir.o for.o hash.o job.o main.o make.o \
+ make_malloc.o parse.o str.o strlist.o suff.o targ.o trace.o var.o util.o
+
+LIBOBJ= lst.lib/lstAppend.o lst.lib/lstAtEnd.o lst.lib/lstAtFront.o \
+ lst.lib/lstClose.o lst.lib/lstConcat.o lst.lib/lstDatum.o \
+ lst.lib/lstDeQueue.o lst.lib/lstDestroy.o lst.lib/lstDupl.o \
+ lst.lib/lstEnQueue.o lst.lib/lstFind.o lst.lib/lstFindFrom.o \
+ lst.lib/lstFirst.o lst.lib/lstForEach.o lst.lib/lstForEachFrom.o \
+ lst.lib/lstInit.o lst.lib/lstInsert.o lst.lib/lstIsAtEnd.o \
+ lst.lib/lstIsEmpty.o lst.lib/lstLast.o lst.lib/lstMember.o \
+ lst.lib/lstNext.o lst.lib/lstOpen.o lst.lib/lstRemove.o \
+ lst.lib/lstReplace.o lst.lib/lstSucc.o lst.lib/lstPrev.o
+
+bmake: ${OBJ} ${LIBOBJ}
+# @echo 'make of make and make.0 started.'
+ ${CC} ${CFLAGS} ${OBJ} ${LIBOBJ} -o bmake ${LIBS}
+ @ls -l $@
+# nroff -h -man make.1 > make.0
+# @echo 'make of make and make.0 completed.'
+
+clean:
+ rm -f ${OBJ} ${LIBOBJ} ${PORTOBJ} bmake
diff --git a/usr.bin/make/PSD.doc/Makefile b/usr.bin/make/PSD.doc/Makefile
new file mode 100644
index 0000000..67702b8
--- /dev/null
+++ b/usr.bin/make/PSD.doc/Makefile
@@ -0,0 +1,10 @@
+# $NetBSD: Makefile,v 1.4 2014/07/05 19:22:43 dholland Exp $
+# @(#)Makefile 8.1 (Berkeley) 8/14/93
+
+SECTION=reference/ref1
+ARTICLE=make
+SRCS= tutorial.ms
+MACROS= -ms
+EXTRAHTMLFILES=make1.png make2.png
+
+.include <bsd.doc.mk>
diff --git a/usr.bin/make/PSD.doc/tutorial.ms b/usr.bin/make/PSD.doc/tutorial.ms
new file mode 100644
index 0000000..814a09a
--- /dev/null
+++ b/usr.bin/make/PSD.doc/tutorial.ms
@@ -0,0 +1,3794 @@
+.\" $NetBSD: tutorial.ms,v 1.13 2017/03/01 13:05:11 kre Exp $
+.\" Copyright (c) 1988, 1989, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Adam de Boor.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" Copyright (c) 1988, 1989 by Adam de Boor
+.\" Copyright (c) 1989 by Berkeley Softworks
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Adam de Boor.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tutorial.ms 8.1 (Berkeley) 8/18/93
+.\"
+.EH 'PSD:12-%''PMake \*- A Tutorial'
+.OH 'PMake \*- A Tutorial''PSD:12-%'
+.\" Ix is an indexing macro similar to .IX but I've disabled it for now
+.\" Since that would require 2 passes and I am not in the mood for that.
+.de Ix
+..
+.\" Rd is section (region) define and Rm is region mention? Again disable for
+.\" now.
+.de Rd
+..
+.de Rm
+..
+.\" xH is a macro to provide numbered headers that are automatically stuffed
+.\" into a table-of-contents, properly indented, etc. If the first argument
+.\" is numeric, it is taken as the depth for numbering (as for .NH), else
+.\" the default (1) is assumed.
+.\"
+.\" @P The initial paragraph distance.
+.\" @Q The piece of section number to increment (or 0 if none given)
+.\" @R Section header.
+.\" @S Indent for toc entry
+.\" @T Argument to NH (can't use @Q b/c giving 0 to NH resets the counter)
+.de xH
+.NH \\$1
+\\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
+.nr PD .1v
+.XS \\n%
+.ta 0.6i
+\\*(SN \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
+.XE
+.nr PD .3v
+..
+.\" CW is used to place a string in fixed-width or switch to a
+.\" fixed-width font.
+.\" C is a typewriter font for a laserwriter. Use something else if
+.\" you don't have one...
+.de CW
+.ie !\\n(.$ .ft C
+.el \&\\$3\fC\\$1\fP\\$2
+..
+.\" Anything I put in a display I want to be in fixed-width
+.am DS
+.CW
+..
+.\" The stuff in .No produces a little stop sign in the left margin
+.\" that says NOTE in it. Unfortunately, it does cause a break, but
+.\" hey. Can't have everything. In case you're wondering how I came
+.\" up with such weird commands, they came from running grn on a
+.\" gremlin file...
+.de No
+.br
+.ne 0.5i
+.ie n \{\
+.nr g3 \w'NOTE '
+.po -\\n(g3u
+.br
+NOTE
+.br
+.po +\\n(g3u
+.\}
+.el \{\
+.po -0.5i
+.br
+.mk
+.nr g3 \\n(.f
+.nr g4 \\n(.s
+.sp -1
+.\" .st cf
+\D't 5u'
+.sp -1
+\h'50u'
+.sp -1
+\D't 3u'
+.sp -1
+.sp 7u
+\h'53u'
+\d\D'p -0.19i 0.0i 0.0i -0.13i 0.30i 0.0i 0.0i 0.13i'
+.sp -1
+.ft R
+.ps 6
+.nr g8 \\n(.d
+.ds g9 "NOTE
+.sp 74u
+\h'85u'\v'0.85n'\h-\w\\*(g9u/2u\&\\*(g9
+.sp |\\n(g8u
+.sp 166u
+\D't 3u'
+.br
+.po
+.rt
+.ft \\n(g3
+.ps \\n(g4
+.\}
+..
+.de Bp
+.ie !\\n(.$ .IP \(bu 2
+.el .IP "\&" 2
+..
+.ie n .po +\w'NOTE 'u
+.el .po +.3i
+.TL
+PMake \*- A Tutorial
+.AU
+Adam de Boor
+.AI
+Berkeley Softworks
+2150 Shattuck Ave, Penthouse
+Berkeley, CA 94704
+adam@bsw.uu.net
+\&...!uunet!bsw!adam
+.FS
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appears in all copies.
+The University of California, Berkeley Softworks, and Adam de Boor make no
+representations about the suitability of this software for any
+purpose. It is provided "as is" without express or implied warranty.
+.FE
+.PP
+.xH 1 Introduction
+.LP
+PMake is a program for creating other programs, or anything else you
+can think of for it to do. The basic idea behind PMake is that, for
+any given system, be it a program or a document or whatever, there
+will be some files that depend on the state of other files (on when
+they were last modified). PMake takes these dependencies, which you
+must specify, and uses them to build whatever it is you want it to
+build.
+.LP
+PMake is almost fully-compatible with Make, with which you may already
+be familiar. PMake's most important feature is its ability to run
+several different jobs at once, making the creation of systems
+considerably faster. It also has a great deal more functionality than
+Make. Throughout the text, whenever something is mentioned that is an
+important difference between PMake and Make (i.e. something that will
+cause a makefile to fail if you don't do something about it), or is
+simply important, it will be flagged with a little sign in the left
+margin, like this:
+.No
+.LP
+This tutorial is divided into three main sections corresponding to basic,
+intermediate and advanced PMake usage. If you already know Make well,
+you will only need to skim chapter 2 (there are some aspects of
+PMake that I consider basic to its use that didn't exist in Make).
+Things in chapter 3 make life much easier, while those in chapter 4
+are strictly for those who know what they are doing. Chapter 5 has
+definitions for the jargon I use and chapter 6 contains possible
+solutions to the problems presented throughout the tutorial.
+.xH 1 The Basics of PMake
+.LP
+PMake takes as input a file that tells a) which files depend on which
+other files to be complete and b) what to do about files that are
+``out-of-date.'' This file is known as a ``makefile'' and is usually
+.Ix 0 def makefile
+kept in the top-most directory of the system to be built. While you
+can call the makefile anything you want, PMake will look for
+.CW Makefile
+and
+.CW makefile
+(in that order) in the current directory if you don't tell it
+otherwise.
+.Ix 0 def makefile default
+To specify a different makefile, use the
+.B \-f
+flag (e.g.
+.CW "pmake -f program.mk" ''). ``
+.Ix 0 ref flags -f
+.Ix 0 ref makefile other
+.LP
+A makefile has four different types of lines in it:
+.RS
+.IP \(bu 2
+File dependency specifications
+.IP \(bu 2
+Creation commands
+.IP \(bu 2
+Variable assignments
+.IP \(bu 2
+Comments, include statements and conditional directives
+.RE
+.LP
+Any line may be continued over multiple lines by ending it with a
+backslash.
+.Ix 0 def "continuation line"
+The backslash, following newline and any initial whitespace
+on the following line are compressed into a single space before the
+input line is examined by PMake.
+.xH 2 Dependency Lines
+.LP
+As mentioned in the introduction, in any system, there are
+dependencies between the files that make up the system. For instance,
+in a program made up of several C source files and one header file,
+the C files will need to be re-compiled should the header file be
+changed. For a document of several chapters and one macro file, the
+chapters will need to be reprocessed if any of the macros changes.
+.Ix 0 def "dependency"
+These are dependencies and are specified by means of dependency lines in
+the makefile.
+.LP
+.Ix 0 def "dependency line"
+On a dependency line, there are targets and sources, separated by a
+one- or two-character operator.
+The targets ``depend'' on the sources and are usually created from
+them.
+.Ix 0 def target
+.Ix 0 def source
+.Ix 0 ref operator
+Any number of targets and sources may be specified on a dependency line.
+All the targets in the line are made to depend on all the sources.
+Targets and sources need not be actual files, but every source must be
+either an actual file or another target in the makefile.
+If you run out of room, use a backslash at the end of the line to continue onto
+the next one.
+.LP
+Any file may be a target and any file may be a source, but the
+relationship between the two (or however many) is determined by the
+``operator'' that separates them.
+.Ix 0 def operator
+Three types of operators exist: one specifies that the datedness of a
+target is determined by the state of its sources, while another
+specifies other files (the sources) that need to be dealt with before
+the target can be re-created. The third operator is very similar to
+the first, with the additional condition that the target is
+out-of-date if it has no sources. These operations are represented by
+the colon, the exclamation point and the double-colon, respectively, and are
+mutually exclusive. Their exact semantics are as follows:
+.IP ":"
+.Ix 0 def operator colon
+.Ix 0 def :
+If a colon is used, a target on the line is considered to be
+``out-of-date'' (and in need of creation) if
+.RS
+.IP \(bu 2
+any of the sources has been modified more recently than the target, or
+.IP \(bu 2
+the target doesn't exist.
+.RE
+.Ix 0 def out-of-date
+.IP "\&"
+Under this operation, steps will be taken to re-create the target only
+if it is found to be out-of-date by using these two rules.
+.IP "!"
+.Ix 0 def operator force
+.Ix 0 def !
+If an exclamation point is used, the target will always be re-created,
+but this will not happen until all of its sources have been examined
+and re-created, if necessary.
+.IP "::"
+.Ix 0 def operator double-colon
+.Ix 0 def ::
+If a double-colon is used, a target is out-of-date if:
+.RS
+.IP \(bu 2
+any of the sources has been modified more recently than the target, or
+.IP \(bu 2
+the target doesn't exist, or
+.IP \(bu 2
+the target has no sources.
+.RE
+.IP "\&"
+If the target is out-of-date according to these rules, it will be re-created.
+This operator also does something else to the targets, but I'll go
+into that in the next section (``Shell Commands'').
+.LP
+Enough words, now for an example. Take that C program I mentioned
+earlier. Say there are three C files
+.CW a.c , (
+.CW b.c
+and
+.CW c.c )
+each of which
+includes the file
+.CW defs.h .
+The dependencies between the files could then be expressed as follows:
+.DS
+program : a.o b.o c.o
+a.o b.o c.o : defs.h
+a.o : a.c
+b.o : b.c
+c.o : c.c
+.DE
+.LP
+You may be wondering at this point, where
+.CW a.o ,
+.CW b.o
+and
+.CW c.o
+came in and why
+.I they
+depend on
+.CW defs.h
+and the C files don't. The reason is quite simple:
+.CW program
+cannot be made by linking together .c files \*- it must be
+made from .o files. Likewise, if you change
+.CW defs.h ,
+it isn't the .c files that need to be re-created, it's the .o files.
+If you think of dependencies in these terms \*- which files (targets)
+need to be created from which files (sources) \*- you should have no problems.
+.LP
+An important thing to notice about the above example, is that all the
+\&.o files appear as targets on more than one line. This is perfectly
+all right: the target is made to depend on all the sources mentioned
+on all the dependency lines. E.g.
+.CW a.o
+depends on both
+.CW defs.h
+and
+.CW a.c .
+.Ix 0 ref dependency
+.No
+.LP
+The order of the dependency lines in the makefile is
+important: the first target on the first dependency line in the
+makefile will be the one that gets made if you don't say otherwise.
+That's why
+.CW program
+comes first in the example makefile, above.
+.LP
+Both targets and sources may contain the standard C-Shell wildcard
+characters
+.CW { , (
+.CW } ,
+.CW * ,
+.CW ? ,
+.CW [ ,
+and
+.CW ] ),
+but the non-curly-brace ones may only appear in the final component
+(the file portion) of the target or source. The characters mean the
+following things:
+.IP \fB{}\fP
+These enclose a comma-separated list of options and cause the pattern
+to be expanded once for each element of the list. Each expansion
+contains a different element. For example,
+.CW src/{whiffle,beep,fish}.c
+expands to the three words
+.CW src/whiffle.c ,
+.CW src/beep.c ,
+and
+.CW src/fish.c .
+These braces may be nested and, unlike the other wildcard characters,
+the resulting words need not be actual files. All other wildcard
+characters are expanded using the files that exist when PMake is
+started.
+.IP \fB*\fP
+This matches zero or more characters of any sort.
+.CW src/*.c
+will expand to the same three words as above as long as
+.CW src
+contains those three files (and no other files that end in
+.CW .c ).
+.IP \fB?\fP
+Matches any single character.
+.IP \fB[]\fP
+This is known as a character class and contains either a list of
+single characters, or a series of character ranges
+.CW a-z , (
+for example means all characters between a and z), or both. It matches
+any single character contained in the list. E.g.
+.CW [A-Za-z]
+will match all letters, while
+.CW [0123456789]
+will match all numbers.
+.xH 2 Shell Commands
+.LP
+``Isn't that nice,'' you say to yourself, ``but how are files
+actually `re-created,' as he likes to spell it?''
+The re-creation is accomplished by commands you place in the makefile.
+These commands are passed to the Bourne shell (better known as
+``/bin/sh'') to be executed and are
+.Ix 0 ref shell
+.Ix 0 ref re-creation
+.Ix 0 ref update
+expected to do what's necessary to update the target file (PMake
+doesn't actually check to see if the target was created. It just
+assumes it's there).
+.Ix 0 ref target
+.LP
+Shell commands in a makefile look a lot like shell commands you would
+type at a terminal, with one important exception: each command in a
+makefile
+.I must
+be preceded by at least one tab.
+.LP
+Each target has associated with it a shell script made up of
+one or more of these shell commands. The creation script for a target
+should immediately follow the dependency line for that target. While
+any given target may appear on more than one dependency line, only one
+of these dependency lines may be followed by a creation script, unless
+the `::' operator was used on the dependency line.
+.Ix 0 ref operator double-colon
+.Ix 0 ref ::
+.No
+.LP
+If the double-colon was used, each dependency line for the target
+may be followed by a shell script. That script will only be executed
+if the target on the associated dependency line is out-of-date with
+respect to the sources on that line, according to the rules I gave
+earlier.
+I'll give you a good example of this later on.
+.LP
+To expand on the earlier makefile, you might add commands as follows:
+.DS
+program : a.o b.o c.o
+ cc a.o b.o c.o \-o program
+a.o b.o c.o : defs.h
+a.o : a.c
+ cc \-c a.c
+b.o : b.c
+ cc \-c b.c
+c.o : c.c
+ cc \-c c.c
+.DE
+.LP
+Something you should remember when writing a makefile is, the
+commands will be executed if the
+.I target
+on the dependency line is out-of-date, not the sources.
+.Ix 0 ref target
+.Ix 0 ref source
+.Ix 0 ref out-of-date
+In this example, the command
+.CW "cc \-c a.c" '' ``
+will be executed if
+.CW a.o
+is out-of-date. Because of the `:' operator,
+.Ix 0 ref :
+.Ix 0 ref operator colon
+this means that should
+.CW a.c
+.I or
+.CW defs.h
+have been modified more recently than
+.CW a.o ,
+the command will be executed
+.CW a.o "\&" (
+will be considered out-of-date).
+.Ix 0 ref out-of-date
+.LP
+Remember how I said the only difference between a makefile shell
+command and a regular shell command was the leading tab? I lied. There
+is another way in which makefile commands differ from regular ones.
+The first two characters after the initial whitespace are treated
+specially.
+If they are any combination of `@' and `\-', they cause PMake to do
+different things.
+.LP
+In most cases, shell commands are printed before they're
+actually executed. This is to keep you informed of what's going on. If
+an `@' appears, however, this echoing is suppressed. In the case of an
+.CW echo
+command, say
+.CW "echo Linking index" ,'' ``
+it would be
+rather silly to see
+.DS
+echo Linking index
+Linking index
+.DE
+.LP
+so PMake allows you to place an `@' before the command
+.CW "@echo Linking index" '') (``
+to prevent the command from being printed.
+.LP
+The other special character is the `\-'. In case you didn't know,
+shell commands finish with a certain ``exit status.'' This status is
+made available by the operating system to whatever program invoked the
+command. Normally this status will be 0 if everything went ok and
+non-zero if something went wrong. For this reason, PMake will consider
+an error to have occurred if one of the shells it invokes returns a non-zero
+status. When it detects an error, PMake's usual action is to abort
+whatever it's doing and exit with a non-zero status itself (any other
+targets that were being created will continue being made, but nothing
+new will be started. PMake will exit after the last job finishes).
+This behavior can be altered, however, by placing a `\-' at the front
+of a command
+.CW "\-mv index index.old" ''), (``
+certain command-line arguments,
+or doing other things, to be detailed later. In such
+a case, the non-zero status is simply ignored and PMake keeps chugging
+along.
+.No
+.LP
+Because all the commands are given to a single shell to execute, such
+things as setting shell variables, changing directories, etc., last
+beyond the command in which they are found. This also allows shell
+compound commands (like
+.CW for
+loops) to be entered in a natural manner.
+Since this could cause problems for some makefiles that depend on
+each command being executed by a single shell, PMake has a
+.B \-B
+.Ix 0 ref compatibility
+.Ix 0 ref flags -B
+flag (it stands for backwards-compatible) that forces each command to
+be given to a separate shell. It also does several other things, all
+of which I discourage since they are now old-fashioned.\|.\|.\|.
+.No
+.LP
+A target's shell script is fed to the shell on its (the shell's) input stream.
+This means that any commands, such as
+.CW ci
+that need to get input from the terminal won't work right \*- they'll
+get the shell's input, something they probably won't find to their
+liking. A simple way around this is to give a command like this:
+.DS
+ci $(SRCS) < /dev/tty
+.DE
+This would force the program's input to come from the terminal. If you
+can't do this for some reason, your only other alternative is to use
+PMake in its fullest compatibility mode. See
+.B Compatibility
+in chapter 4.
+.Ix 0 ref compatibility
+.LP
+.xH 2 Variables
+.LP
+PMake, like Make before it, has the ability to save text in variables
+to be recalled later at your convenience. Variables in PMake are used
+much like variables in the shell and, by tradition, consist of
+all upper-case letters (you don't
+.I have
+to use all upper-case letters.
+In fact there's nothing to stop you from calling a variable
+.CW @^&$%$ .
+Just tradition). Variables are assigned-to using lines of the form
+.Ix 0 def variable assignment
+.DS
+VARIABLE = value
+.DE
+.Ix 0 def variable assignment
+appended-to by
+.DS
+VARIABLE += value
+.DE
+.Ix 0 def variable appending
+.Ix 0 def variable assignment appended
+.Ix 0 def +=
+conditionally assigned-to (if the variable isn't already defined) by
+.DS
+VARIABLE ?= value
+.DE
+.Ix 0 def variable assignment conditional
+.Ix 0 def ?=
+and assigned-to with expansion (i.e. the value is expanded (see below)
+before being assigned to the variable\*-useful for placing a value at
+the beginning of a variable, or other things) by
+.DS
+VARIABLE := value
+.DE
+.Ix 0 def variable assignment expanded
+.Ix 0 def :=
+.LP
+Any whitespace before
+.I value
+is stripped off. When appending, a space is placed between the old
+value and the stuff being appended.
+.LP
+The final way a variable may be assigned to is using
+.DS
+VARIABLE != shell-command
+.DE
+.Ix 0 def variable assignment shell-output
+.Ix 0 def !=
+In this case,
+.I shell-command
+has all its variables expanded (see below) and is passed off to a
+shell to execute. The output of the shell is then placed in the
+variable. Any newlines (other than the final one) are replaced by
+spaces before the assignment is made. This is typically used to find
+the current directory via a line like:
+.DS
+CWD != pwd
+.DE
+.LP
+.B Note:
+this is intended to be used to execute commands that produce small amounts
+of output (e.g. ``pwd''). The implementation is less than intelligent and will
+likely freeze if you execute something that produces thousands of
+bytes of output (8 Kb is the limit on many UNIX systems).
+.LP
+The value of a variable may be retrieved by enclosing the variable
+name in parentheses or curly braces and preceding the whole thing
+with a dollar sign.
+.LP
+For example, to set the variable CFLAGS to the string
+.CW "\-I/sprite/src/lib/libc \-O" ,'' ``
+you would place a line
+.DS
+CFLAGS = \-I/sprite/src/lib/libc \-O
+.DE
+in the makefile and use the word
+.CW "$(CFLAGS)"
+wherever you would like the string
+.CW "\-I/sprite/src/lib/libc \-O"
+to appear. This is called variable expansion.
+.Ix 0 def variable expansion
+.No
+.LP
+Unlike Make, PMake will not expand a variable unless it knows
+the variable exists. E.g. if you have a
+.CW "${i}"
+in a shell command and you have not assigned a value to the variable
+.CW i
+(the empty string is considered a value, by the way), where Make would have
+substituted the empty string, PMake will leave the
+.CW "${i}"
+alone.
+To keep PMake from substituting for a variable it knows, precede the
+dollar sign with another dollar sign.
+(e.g. to pass
+.CW "${HOME}"
+to the shell, use
+.CW "$${HOME}" ).
+This causes PMake, in effect, to expand the
+.CW $
+macro, which expands to a single
+.CW $ .
+For compatibility, Make's style of variable expansion will be used
+if you invoke PMake with any of the compatibility flags (\c
+.B \-V ,
+.B \-B
+or
+.B \-M .
+The
+.B \-V
+flag alters just the variable expansion).
+.Ix 0 ref flags -V
+.Ix 0 ref flags -B
+.Ix 0 ref flags -M
+.Ix 0 ref compatibility
+.LP
+.Ix 0 ref variable expansion
+There are two different times at which variable expansion occurs:
+When parsing a dependency line, the expansion occurs immediately
+upon reading the line. If any variable used on a dependency line is
+undefined, PMake will print a message and exit.
+Variables in shell commands are expanded when the command is
+executed.
+Variables used inside another variable are expanded whenever the outer
+variable is expanded (the expansion of an inner variable has no effect
+on the outer variable. I.e. if the outer variable is used on a dependency
+line and in a shell command, and the inner variable changes value
+between when the dependency line is read and the shell command is
+executed, two different values will be substituted for the outer
+variable).
+.Ix 0 def variable types
+.LP
+Variables come in four flavors, though they are all expanded the same
+and all look about the same. They are (in order of expanding scope):
+.RS
+.IP \(bu 2
+Local variables.
+.Ix 0 ref variable local
+.IP \(bu 2
+Command-line variables.
+.Ix 0 ref variable command-line
+.IP \(bu 2
+Global variables.
+.Ix 0 ref variable global
+.IP \(bu 2
+Environment variables.
+.Ix 0 ref variable environment
+.RE
+.LP
+The classification of variables doesn't matter much, except that the
+classes are searched from the top (local) to the bottom (environment)
+when looking up a variable. The first one found wins.
+.xH 3 Local Variables
+.LP
+.Ix 0 def variable local
+Each target can have as many as seven local variables. These are
+variables that are only ``visible'' within that target's shell script
+and contain such things as the target's name, all of its sources (from
+all its dependency lines), those sources that were out-of-date, etc.
+Four local variables are defined for all targets. They are:
+.RS
+.IP ".TARGET"
+.Ix 0 def variable local .TARGET
+.Ix 0 def .TARGET
+The name of the target.
+.IP ".OODATE"
+.Ix 0 def variable local .OODATE
+.Ix 0 def .OODATE
+The list of the sources for the target that were considered out-of-date.
+The order in the list is not guaranteed to be the same as the order in
+which the dependencies were given.
+.IP ".ALLSRC"
+.Ix 0 def variable local .ALLSRC
+.Ix 0 def .ALLSRC
+The list of all sources for this target in the order in which they
+were given.
+.IP ".PREFIX"
+.Ix 0 def variable local .PREFIX
+.Ix 0 def .PREFIX
+The target without its suffix and without any leading path. E.g. for
+the target
+.CW ../../lib/compat/fsRead.c ,
+this variable would contain
+.CW fsRead .
+.RE
+.LP
+Three other local variables are set only for certain targets under
+special circumstances. These are the ``.IMPSRC,''
+.Ix 0 ref variable local .IMPSRC
+.Ix 0 ref .IMPSRC
+``.ARCHIVE,''
+.Ix 0 ref variable local .ARCHIVE
+.Ix 0 ref .ARCHIVE
+and ``.MEMBER''
+.Ix 0 ref variable local .MEMBER
+.Ix 0 ref .MEMBER
+variables. When they are set and how they are used is described later.
+.LP
+Four of these variables may be used in sources as well as in shell
+scripts.
+.Ix 0 def "dynamic source"
+.Ix 0 def source dynamic
+These are ``.TARGET'', ``.PREFIX'', ``.ARCHIVE'' and ``.MEMBER''. The
+variables in the sources are expanded once for each target on the
+dependency line, providing what is known as a ``dynamic source,''
+.Rd 0
+allowing you to specify several dependency lines at once. For example,
+.DS
+$(OBJS) : $(.PREFIX).c
+.DE
+will create a dependency between each object file and its
+corresponding C source file.
+.xH 3 Command-line Variables
+.LP
+.Ix 0 def variable command-line
+Command-line variables are set when PMake is first invoked by giving a
+variable assignment as one of the arguments. For example,
+.DS
+pmake "CFLAGS = -I/sprite/src/lib/libc -O"
+.DE
+would make
+.CW CFLAGS
+be a command-line variable with the given value. Any assignments to
+.CW CFLAGS
+in the makefile will have no effect, because once it
+is set, there is (almost) nothing you can do to change a command-line
+variable (the search order, you see). Command-line variables may be
+set using any of the four assignment operators, though only
+.CW =
+and
+.CW ?=
+behave as you would expect them to, mostly because assignments to
+command-line variables are performed before the makefile is read, thus
+the values set in the makefile are unavailable at the time.
+.CW +=
+.Ix 0 ref +=
+.Ix 0 ref variable assignment appended
+is the same as
+.CW = ,
+because the old value of the variable is sought only in the scope in
+which the assignment is taking place (for reasons of efficiency that I
+won't get into here).
+.CW :=
+and
+.CW ?=
+.Ix 0 ref :=
+.Ix 0 ref ?=
+.Ix 0 ref variable assignment expanded
+.Ix 0 ref variable assignment conditional
+will work if the only variables used are in the environment.
+.CW !=
+is sort of pointless to use from the command line, since the same
+effect can no doubt be accomplished using the shell's own command
+substitution mechanisms (backquotes and all that).
+.xH 3 Global Variables
+.LP
+.Ix 0 def variable global
+Global variables are those set or appended-to in the makefile.
+There are two classes of global variables: those you set and those PMake sets.
+As I said before, the ones you set can have any name you want them to have,
+except they may not contain a colon or an exclamation point.
+The variables PMake sets (almost) always begin with a
+period and always contain upper-case letters, only. The variables are
+as follows:
+.RS
+.IP .PMAKE
+.Ix 0 def variable global .PMAKE
+.Ix 0 def .PMAKE
+.Ix 0 def variable global MAKE
+.Ix 0 def MAKE
+The name by which PMake was invoked is stored in this variable. For
+compatibility, the name is also stored in the MAKE variable.
+.IP .MAKEFLAGS
+.Ix 0 def variable global .MAKEFLAGS
+.Ix 0 def .MAKEFLAGS variable
+.Ix 0 def variable global MFLAGS
+.Ix 0 def MFLAGS
+All the relevant flags with which PMake was invoked. This does not
+include such things as
+.B \-f
+or variable assignments. Again for compatibility, this value is stored
+in the MFLAGS variable as well.
+.RE
+.LP
+Two other variables, ``.INCLUDES'' and ``.LIBS,'' are covered in the
+section on special targets in chapter 3.
+.Ix 0 ref variable global .INCLUDES
+.Ix 0 ref variable global .LIBS
+.LP
+Global variables may be deleted using lines of the form:
+.Ix 0 def #undef
+.Ix 0 def variable deletion
+.DS
+#undef \fIvariable\fP
+.DE
+The
+.CW # ' `
+must be the first character on the line. Note that this may only be
+done on global variables.
+.xH 3 Environment Variables
+.LP
+.Ix 0 def variable environment
+Environment variables are passed by the shell that invoked PMake and
+are given by PMake to each shell it invokes. They are expanded like
+any other variable, but they cannot be altered in any way.
+.LP
+One special environment variable,
+.CW PMAKE ,
+.Ix 0 def variable environment PMAKE
+is examined by PMake for command-line flags, variable assignments,
+etc., it should always use. This variable is examined before the
+actual arguments to PMake are. In addition, all flags given to PMake,
+either through the
+.CW PMAKE
+variable or on the command line, are placed in this environment
+variable and exported to each shell PMake executes. Thus recursive
+invocations of PMake automatically receive the same flags as the
+top-most one.
+.LP
+Using all these variables, you can compress the sample makefile even more:
+.DS
+OBJS = a.o b.o c.o
+program : $(OBJS)
+ cc $(.ALLSRC) \-o $(.TARGET)
+$(OBJS) : defs.h
+a.o : a.c
+ cc \-c a.c
+b.o : b.c
+ cc \-c b.c
+c.o : c.c
+ cc \-c c.c
+.DE
+.Ix 0 ref variable local .ALLSRC
+.Ix 0 ref .ALLSRC
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref .TARGET
+.Rd 3
+.xH 2 Comments
+.LP
+.Ix 0 def comments
+Comments in a makefile start with a `#' character and extend to the
+end of the line. They may appear
+anywhere you want them, except in a shell command (though the shell
+will treat it as a comment, too). If, for some reason, you need to use the `#'
+in a variable or on a dependency line, put a backslash in front of it.
+PMake will compress the two into a single `#' (Note: this isn't true
+if PMake is operating in full-compatibility mode).
+.Ix 0 ref flags -M
+.Ix 0 ref compatibility
+.xH 2 Parallelism
+.No
+.LP
+PMake was specifically designed to re-create several targets at once,
+when possible. You do not have to do anything special to cause this to
+happen (unless PMake was configured to not act in parallel, in which
+case you will have to make use of the
+.B \-L
+and
+.B \-J
+flags (see below)),
+.Ix 0 ref flags -L
+.Ix 0 ref flags -J
+but you do have to be careful at times.
+.LP
+There are several problems you are likely to encounter. One is
+that some makefiles (and programs) are written in such a way that it is
+impossible for two targets to be made at once. The program
+.CW xstr ,
+for example,
+always modifies the files
+.CW strings
+and
+.CW x.c .
+There is no way to change it. Thus you cannot run two of them at once
+without something being trashed. Similarly, if you have commands
+in the makefile that always send output to the same file, you will not
+be able to make more than one target at once unless you change the
+file you use. You can, for instance, add a
+.CW $$$$
+to the end of the file name to tack on the process ID of the shell
+executing the command (each
+.CW $$
+expands to a single
+.CW $ ,
+thus giving you the shell variable
+.CW $$ ).
+Since only one shell is used for all the
+commands, you'll get the same file name for each command in the
+script.
+.LP
+The other problem comes from improperly-specified dependencies that
+worked in Make because of its sequential, depth-first way of examining
+them. While I don't want to go into depth on how PMake
+works (look in chapter 4 if you're interested), I will warn you that
+files in two different ``levels'' of the dependency tree may be
+examined in a different order in PMake than they were in Make. For
+example, given the makefile
+.DS
+a : b c
+b : d
+.DE
+PMake will examine the targets in the order
+.CW c ,
+.CW d ,
+.CW b ,
+.CW a .
+If the makefile's author expected PMake to abort before making
+.CW c
+if an error occurred while making
+.CW b ,
+or if
+.CW b
+needed to exist before
+.CW c
+was made,
+s/he will be sorely disappointed. The dependencies are
+incomplete, since in both these cases,
+.CW c
+would depend on
+.CW b .
+So watch out.
+.LP
+Another problem you may face is that, while PMake is set up to handle the
+output from multiple jobs in a graceful fashion, the same is not so for input.
+It has no way to regulate input to different jobs,
+so if you use the redirection from
+.CW /dev/tty
+I mentioned earlier, you must be careful not to run two of the jobs at once.
+.xH 2 Writing and Debugging a Makefile
+.LP
+Now you know most of what's in a makefile, what do you do next? There
+are two choices: (1) use one of the uncommonly-available makefile
+generators or (2) write your own makefile (I leave out the third choice of
+ignoring PMake and doing everything by hand as being beyond the bounds
+of common sense).
+.LP
+When faced with the writing of a makefile, it is usually best to start
+from first principles: just what
+.I are
+you trying to do? What do you want the makefile finally to produce?
+.LP
+To begin with a somewhat traditional example, let's say you need to
+write a makefile to create a program,
+.CW expr ,
+that takes standard infix expressions and converts them to prefix form (for
+no readily apparent reason). You've got three source files, in C, that
+make up the program:
+.CW main.c ,
+.CW parse.c ,
+and
+.CW output.c .
+Harking back to my pithy advice about dependency lines, you write the
+first line of the file:
+.DS
+expr : main.o parse.o output.o
+.DE
+because you remember
+.CW expr
+is made from
+.CW .o
+files, not
+.CW .c
+files. Similarly for the
+.CW .o
+files you produce the lines:
+.DS
+main.o : main.c
+parse.o : parse.c
+output.o : output.c
+main.o parse.o output.o : defs.h
+.DE
+.LP
+Great. You've now got the dependencies specified. What you need now is
+commands. These commands, remember, must produce the target on the
+dependency line, usually by using the sources you've listed.
+You remember about local variables? Good, so it should come
+to you as no surprise when you write
+.DS
+expr : main.o parse.o output.o
+ cc -o $(.TARGET) $(.ALLSRC)
+.DE
+Why use the variables? If your program grows to produce postfix
+expressions too (which, of course, requires a name change or two), it
+is one fewer place you have to change the file. You cannot do this for
+the object files, however, because they depend on their corresponding
+source files
+.I and
+.CW defs.h ,
+thus if you said
+.DS
+ cc -c $(.ALLSRC)
+.DE
+you'd get (for
+.CW main.o ):
+.DS
+ cc -c main.c defs.h
+.DE
+which is wrong. So you round out the makefile with these lines:
+.DS
+main.o : main.c
+ cc -c main.c
+parse.o : parse.c
+ cc -c parse.c
+output.o : output.c
+ cc -c output.c
+.DE
+.LP
+The makefile is now complete and will, in fact, create the program you
+want it to without unnecessary compilations or excessive typing on
+your part. There are two things wrong with it, however (aside from it
+being altogether too long, something I'll address in chapter 3):
+.IP 1)
+The string
+.CW "main.o parse.o output.o" '' ``
+is repeated twice, necessitating two changes when you add postfix
+(you were planning on that, weren't you?). This is in direct violation
+of de Boor's First Rule of writing makefiles:
+.QP
+.I
+Anything that needs to be written more than once
+should be placed in a variable.
+.IP "\&"
+I cannot emphasize this enough as being very important to the
+maintenance of a makefile and its program.
+.IP 2)
+There is no way to alter the way compilations are performed short of
+editing the makefile and making the change in all places. This is evil
+and violates de Boor's Second Rule, which follows directly from the
+first:
+.QP
+.I
+Any flags or programs used inside a makefile should be placed in a variable so
+they may be changed, temporarily or permanently, with the greatest ease.
+.LP
+The makefile should more properly read:
+.DS
+OBJS = main.o parse.o output.o
+expr : $(OBJS)
+ $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC)
+main.o : main.c
+ $(CC) $(CFLAGS) -c main.c
+parse.o : parse.c
+ $(CC) $(CFLAGS) -c parse.c
+output.o : output.c
+ $(CC) $(CFLAGS) -c output.c
+$(OBJS) : defs.h
+.DE
+Alternatively, if you like the idea of dynamic sources mentioned in
+section 2.3.1,
+.Rm 0 2.3.1
+.Rd 4
+.Ix 0 ref "dynamic source"
+.Ix 0 ref source dynamic
+you could write it like this:
+.DS
+OBJS = main.o parse.o output.o
+expr : $(OBJS)
+ $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC)
+$(OBJS) : $(.PREFIX).c defs.h
+ $(CC) $(CFLAGS) -c $(.PREFIX).c
+.DE
+These two rules and examples lead to de Boor's First Corollary:
+.QP
+.I
+Variables are your friends.
+.LP
+Once you've written the makefile comes the sometimes-difficult task of
+.Ix 0 ref debugging
+making sure the darn thing works. Your most helpful tool to make sure
+the makefile is at least syntactically correct is the
+.B \-n
+.Ix 0 ref flags -n
+flag, which allows you to see if PMake will choke on the makefile. The
+second thing the
+.B \-n
+flag lets you do is see what PMake would do without it actually doing
+it, thus you can make sure the right commands would be executed were
+you to give PMake its head.
+.LP
+When you find your makefile isn't behaving as you hoped, the first
+question that comes to mind (after ``What time is it, anyway?'') is
+``Why not?'' In answering this, two flags will serve you well:
+.CW "-d m" '' ``
+.Ix 0 ref flags -d
+and
+.CW "-p 2" .'' ``
+.Ix 0 ref flags -p
+The first causes PMake to tell you as it examines each target in the
+makefile and indicate why it is deciding whatever it is deciding. You
+can then use the information printed for other targets to see where
+you went wrong. The
+.CW "-p 2" '' ``
+flag makes PMake print out its internal state when it is done,
+allowing you to see that you forgot to make that one chapter depend on
+that file of macros you just got a new version of. The output from
+.CW "-p 2" '' ``
+is intended to resemble closely a real makefile, but with additional
+information provided and with variables expanded in those commands
+PMake actually printed or executed.
+.LP
+Something to be especially careful about is circular dependencies.
+.Ix 0 def dependency circular
+E.g.
+.DS
+a : b
+b : c d
+d : a
+.DE
+In this case, because of how PMake works,
+.CW c
+is the only thing PMake will examine, because
+.CW d
+and
+.CW a
+will effectively fall off the edge of the universe, making it
+impossible to examine
+.CW b
+(or them, for that matter).
+PMake will tell you (if run in its normal mode) all the targets
+involved in any cycle it looked at (i.e. if you have two cycles in the
+graph (naughty, naughty), but only try to make a target in one of
+them, PMake will only tell you about that one. You'll have to try to
+make the other to find the second cycle). When run as Make, it will
+only print the first target in the cycle.
+.xH 2 Invoking PMake
+.LP
+.Ix 0 ref flags
+.Ix 0 ref arguments
+.Ix 0 ref usage
+PMake comes with a wide variety of flags to choose from.
+They may appear in any order, interspersed with command-line variable
+assignments and targets to create.
+The flags are as follows:
+.IP "\fB\-d\fP \fIwhat\fP"
+.Ix 0 def flags -d
+.Ix 0 ref debugging
+This causes PMake to spew out debugging information that
+may prove useful to you. If you can't
+figure out why PMake is doing what it's doing, you might try using
+this flag. The
+.I what
+parameter is a string of single characters that tell PMake what
+aspects you are interested in. Most of what I describe will make
+little sense to you, unless you've dealt with Make before. Just
+remember where this table is and come back to it as you read on.
+The characters and the information they produce are as follows:
+.RS
+.IP a
+Archive searching and caching.
+.IP c
+Conditional evaluation.
+.IP d
+The searching and caching of directories.
+.IP j
+Various snippets of information related to the running of the multiple
+shells. Not particularly interesting.
+.IP m
+The making of each target: what target is being examined; when it was
+last modified; whether it is out-of-date; etc.
+.IP p
+Makefile parsing.
+.IP r
+Remote execution.
+.IP s
+The application of suffix-transformation rules. (See chapter 3)
+.IP t
+The maintenance of the list of targets.
+.IP v
+Variable assignment.
+.RE
+.IP "\&"
+Of these all, the
+.CW m
+and
+.CW s
+letters will be most useful to you.
+If the
+.B \-d
+is the final argument or the argument from which it would get these
+key letters (see below for a note about which argument would be used)
+begins with a
+.B \- ,
+all of these debugging flags will be set, resulting in massive amounts
+of output.
+.IP "\fB\-f\fP \fImakefile\fP"
+.Ix 0 def flags -f
+Specify a makefile to read different from the standard makefiles
+.CW Makefile "\&" (
+or
+.CW makefile ).
+.Ix 0 ref makefile default
+.Ix 0 ref makefile other
+If
+.I makefile
+is ``\-'', PMake uses the standard input. This is useful for making
+quick and dirty makefiles.\|.\|.
+.Ix 0 ref makefile "quick and dirty"
+.IP \fB\-h\fP
+.Ix 0 def flags -h
+Prints out a summary of the various flags PMake accepts. It can also
+be used to find out what level of concurrency was compiled into the
+version of PMake you are using (look at
+.B \-J
+and
+.B \-L )
+and various other information on how PMake was configured.
+.Ix 0 ref configuration
+.Ix 0 ref makefile system
+.IP \fB\-i\fP
+.Ix 0 def flags -i
+If you give this flag, PMake will ignore non-zero status returned
+by any of its shells. It's like placing a `\-' before all the commands
+in the makefile.
+.IP \fB\-k\fP
+.Ix 0 def flags -k
+This is similar to
+.B \-i
+in that it allows PMake to continue when it sees an error, but unlike
+.B \-i ,
+where PMake continues blithely as if nothing went wrong,
+.B \-k
+causes it to recognize the error and only continue work on those
+things that don't depend on the target, either directly or indirectly (through
+depending on something that depends on it), whose creation returned the error.
+The `k' is for ``keep going''.\|.\|.
+.Ix 0 ref target
+.IP \fB\-l\fP
+.Ix 0 def flags -l
+PMake has the ability to lock a directory against other
+people executing it in the same directory (by means of a file called
+``LOCK.make'' that it creates and checks for in the directory). This
+is a Good Thing because two people doing the same thing in the same place
+can be disastrous for the final product (too many cooks and all that).
+Whether this locking is the default is up to your system
+administrator. If locking is on,
+.B \-l
+will turn it off, and vice versa. Note that this locking will not
+prevent \fIyou\fP from invoking PMake twice in the same place \*- if
+you own the lock file, PMake will warn you about it but continue to execute.
+.IP "\fB\-m\fP \fIdirectory\fP"
+.Ix 0 def flags -m
+Tells PMake another place to search for included makefiles via the <...>
+style. Several
+.B \-m
+options can be given to form a search path. If this construct is used the
+default system makefile search path is completely overridden.
+To be explained in chapter 3, section 3.2.
+.Rm 2 3.2
+.IP \fB\-n\fP
+.Ix 0 def flags -n
+This flag tells PMake not to execute the commands needed to update the
+out-of-date targets in the makefile. Rather, PMake will simply print
+the commands it would have executed and exit. This is particularly
+useful for checking the correctness of a makefile. If PMake doesn't do
+what you expect it to, it's a good chance the makefile is wrong.
+.IP "\fB\-p\fP \fInumber\fP"
+.Ix 0 def flags -p
+.Ix 0 ref debugging
+This causes PMake to print its input in a reasonable form, though
+not necessarily one that would make immediate sense to anyone but me. The
+.I number
+is a bitwise-or of 1 and 2 where 1 means it should print the input
+before doing any processing and 2 says it should print it after
+everything has been re-created. Thus
+.CW "\-p 3"
+would print it twice\*-once before processing and once after (you
+might find the difference between the two interesting). This is mostly
+useful to me, but you may find it informative in some bizarre circumstances.
+.IP \fB\-q\fP
+.Ix 0 def flags -q
+If you give PMake this flag, it will not try to re-create anything. It
+will just see if anything is out-of-date and exit non-zero if so.
+.IP \fB\-r\fP
+.Ix 0 def flags -r
+When PMake starts up, it reads a default makefile that tells it what
+sort of system it's on and gives it some idea of what to do if you
+don't tell it anything. I'll tell you about it in chapter 3. If you
+give this flag, PMake won't read the default makefile.
+.IP \fB\-s\fP
+.Ix 0 def flags -s
+This causes PMake to not print commands before they're executed. It
+is the equivalent of putting an `@' before every command in the
+makefile.
+.IP \fB\-t\fP
+.Ix 0 def flags -t
+Rather than try to re-create a target, PMake will simply ``touch'' it
+so as to make it appear up-to-date. If the target didn't exist before,
+it will when PMake finishes, but if the target did exist, it will
+appear to have been updated.
+.IP \fB\-v\fP
+.Ix 0 def flags -v
+This is a mixed-compatibility flag intended to mimic the System V
+version of Make. It is the same as giving
+.B \-B ,
+and
+.B \-V
+as well as turning off directory locking. Targets can still be created
+in parallel, however. This is the mode PMake will enter if it is
+invoked either as
+.CW smake '' ``
+or
+.CW vmake ''. ``
+.IP \fB\-x\fP
+.Ix 0 def flags -x
+This tells PMake it's ok to export jobs to other machines, if they're
+available. It is used when running in Make mode, as exporting in this
+mode tends to make things run slower than if the commands were just
+executed locally.
+.IP \fB\-B\fP
+.Ix 0 ref compatibility
+.Ix 0 def flags -B
+Forces PMake to be as backwards-compatible with Make as possible while
+still being itself.
+This includes:
+.RS
+.IP \(bu 2
+Executing one shell per shell command
+.IP \(bu 2
+Expanding anything that looks even vaguely like a variable, with the
+empty string replacing any variable PMake doesn't know.
+.IP \(bu 2
+Refusing to allow you to escape a `#' with a backslash.
+.IP \(bu 2
+Permitting undefined variables on dependency lines and conditionals
+(see below). Normally this causes PMake to abort.
+.RE
+.IP \fB\-C\fP
+.Ix 0 def flags -C
+This nullifies any and all compatibility mode flags you may have given
+or implied up to the time the
+.B \-C
+is encountered. It is useful mostly in a makefile that you wrote for PMake
+to avoid bad things happening when someone runs PMake as
+.CW make '' ``
+or has things set in the environment that tell it to be compatible.
+.B \-C
+is
+.I not
+placed in the
+.CW PMAKE
+environment variable or the
+.CW .MAKEFLAGS
+or
+.CW MFLAGS
+global variables.
+.Ix 0 ref variable environment PMAKE
+.Ix 0 ref variable global .MAKEFLAGS
+.Ix 0 ref variable global MFLAGS
+.Ix 0 ref .MAKEFLAGS variable
+.Ix 0 ref MFLAGS
+.IP "\fB\-D\fP \fIvariable\fP"
+.Ix 0 def flags -D
+Allows you to define a variable to have
+.CW 1 '' ``
+as its value. The variable is a global variable, not a command-line
+variable. This is useful mostly for people who are used to the C
+compiler arguments and those using conditionals, which I'll get into
+in section 4.3
+.Rm 1 4.3
+.IP "\fB\-I\fP \fIdirectory\fP"
+.Ix 0 def flags -I
+Tells PMake another place to search for included makefiles. Yet
+another thing to be explained in chapter 3 (section 3.2, to be
+precise).
+.Rm 2 3.2
+.IP "\fB\-J\fP \fInumber\fP"
+.Ix 0 def flags -J
+Gives the absolute maximum number of targets to create at once on both
+local and remote machines.
+.IP "\fB\-L\fP \fInumber\fP"
+.Ix 0 def flags -L
+This specifies the maximum number of targets to create on the local
+machine at once. This may be 0, though you should be wary of doing
+this, as PMake may hang until a remote machine becomes available, if
+one is not available when it is started.
+.IP \fB\-M\fP
+.Ix 0 ref compatibility
+.Ix 0 def flags -M
+This is the flag that provides absolute, complete, full compatibility
+with Make. It still allows you to use all but a few of the features of
+PMake, but it is non-parallel. This is the mode PMake enters if you
+call it
+.CW make .'' ``
+.IP \fB\-P\fP
+.Ix 0 def flags -P
+.Ix 0 ref "output control"
+When creating targets in parallel, several shells are executing at
+once, each wanting to write its own two cent's-worth to the screen.
+This output must be captured by PMake in some way in order to prevent
+the screen from being filled with garbage even more indecipherable
+than you usually see. PMake has two ways of doing this, one of which
+provides for much cleaner output and a clear separation between the
+output of different jobs, the other of which provides a more immediate
+response so one can tell what is really happening. The former is done
+by notifying you when the creation of a target starts, capturing the
+output and transferring it to the screen all at once when the job
+finishes. The latter is done by catching the output of the shell (and
+its children) and buffering it until an entire line is received, then
+printing that line preceded by an indication of which job produced
+the output. Since I prefer this second method, it is the one used by
+default. The first method will be used if you give the
+.B \-P
+flag to PMake.
+.IP \fB\-V\fP
+.Ix 0 def flags -V
+As mentioned before, the
+.B \-V
+flag tells PMake to use Make's style of expanding variables,
+substituting the empty string for any variable it doesn't know.
+.IP \fB\-W\fP
+.Ix 0 def flags -W
+There are several times when PMake will print a message at you that is
+only a warning, i.e. it can continue to work in spite of your having
+done something silly (such as forgotten a leading tab for a shell
+command). Sometimes you are well aware of silly things you have done
+and would like PMake to stop bothering you. This flag tells it to shut
+up about anything non-fatal.
+.IP \fB\-X\fP
+.Ix 0 def flags -X
+This flag causes PMake to not attempt to export any jobs to another
+machine.
+.LP
+Several flags may follow a single `\-'. Those flags that require
+arguments take them from successive parameters. E.g.
+.DS
+pmake -fDnI server.mk DEBUG /chip2/X/server/include
+.DE
+will cause PMake to read
+.CW server.mk
+as the input makefile, define the variable
+.CW DEBUG
+as a global variable and look for included makefiles in the directory
+.CW /chip2/X/server/include .
+.xH 2 Summary
+.LP
+A makefile is made of four types of lines:
+.RS
+.IP \(bu 2
+Dependency lines
+.IP \(bu 2
+Creation commands
+.IP \(bu 2
+Variable assignments
+.IP \(bu 2
+Comments, include statements and conditional directives
+.RE
+.LP
+A dependency line is a list of one or more targets, an operator
+.CW : ', (`
+.CW :: ', `
+or
+.CW ! '), `
+and a list of zero or more sources. Sources may contain wildcards and
+certain local variables.
+.LP
+A creation command is a regular shell command preceded by a tab. In
+addition, if the first two characters after the tab (and other
+whitespace) are a combination of
+.CW @ ' `
+or
+.CW - ', `
+PMake will cause the command to not be printed (if the character is
+.CW @ ') `
+or errors from it to be ignored (if
+.CW - '). `
+A blank line, dependency line or variable assignment terminates a
+creation script. There may be only one creation script for each target
+with a
+.CW : ' `
+or
+.CW ! ' `
+operator.
+.LP
+Variables are places to store text. They may be unconditionally
+assigned-to using the
+.CW = ' `
+.Ix 0 ref =
+.Ix 0 ref variable assignment
+operator, appended-to using the
+.CW += ' `
+.Ix 0 ref +=
+.Ix 0 ref variable assignment appended
+operator, conditionally (if the variable is undefined) assigned-to
+with the
+.CW ?= ' `
+.Ix 0 ref ?=
+.Ix 0 ref variable assignment conditional
+operator, and assigned-to with variable expansion with the
+.CW := ' `
+.Ix 0 ref :=
+.Ix 0 ref variable assignment expanded
+operator. The output of a shell command may be assigned to a variable
+using the
+.CW != ' `
+.Ix 0 ref !=
+.Ix 0 ref variable assignment shell-output
+operator. Variables may be expanded (their value inserted) by enclosing
+their name in parentheses or curly braces, preceded by a dollar sign.
+A dollar sign may be escaped with another dollar sign. Variables are
+not expanded if PMake doesn't know about them. There are seven local
+variables:
+.CW .TARGET ,
+.CW .ALLSRC ,
+.CW .OODATE ,
+.CW .PREFIX ,
+.CW .IMPSRC ,
+.CW .ARCHIVE ,
+and
+.CW .MEMBER .
+Four of them
+.CW .TARGET , (
+.CW .PREFIX ,
+.CW .ARCHIVE ,
+and
+.CW .MEMBER )
+may be used to specify ``dynamic sources.''
+.Ix 0 ref "dynamic source"
+.Ix 0 ref source dynamic
+Variables are good. Know them. Love them. Live them.
+.LP
+Debugging of makefiles is best accomplished using the
+.B \-n ,
+.B "\-d m" ,
+and
+.B "\-p 2"
+flags.
+.xH 2 Exercises
+.ce
+\s+4\fBTBA\fP\s0
+.xH 1 Short-cuts and Other Nice Things
+.LP
+Based on what I've told you so far, you may have gotten the impression
+that PMake is just a way of storing away commands and making sure you
+don't forget to compile something. Good. That's just what it is.
+However, the ways I've described have been inelegant, at best, and
+painful, at worst.
+This chapter contains things that make the
+writing of makefiles easier and the makefiles themselves shorter and
+easier to modify (and, occasionally, simpler). In this chapter, I
+assume you are somewhat more
+familiar with Sprite (or UNIX, if that's what you're using) than I did
+in chapter 2, just so you're on your toes.
+So without further ado...
+.xH 2 Transformation Rules
+.LP
+As you know, a file's name consists of two parts: a base name, which
+gives some hint as to the contents of the file, and a suffix, which
+usually indicates the format of the file.
+Over the years, as
+.UX
+has developed,
+naming conventions, with regard to suffixes, have also developed that have
+become almost as incontrovertible as Law. E.g. a file ending in
+.CW .c
+is assumed to contain C source code; one with a
+.CW .o
+suffix is assumed to be a compiled, relocatable object file that may
+be linked into any program; a file with a
+.CW .ms
+suffix is usually a text file to be processed by Troff with the \-ms
+macro package, and so on.
+One of the best aspects of both Make and PMake comes from their
+understanding of how the suffix of a file pertains to its contents and
+their ability to do things with a file based solely on its suffix. This
+ability comes from something known as a transformation rule. A
+transformation rule specifies how to change a file with one suffix
+into a file with another suffix.
+.LP
+A transformation rule looks much like a dependency line, except the
+target is made of two known suffixes stuck together. Suffixes are made
+known to PMake by placing them as sources on a dependency line whose
+target is the special target
+.CW .SUFFIXES .
+E.g.
+.DS
+\&.SUFFIXES : .o .c
+\&.c.o :
+ $(CC) $(CFLAGS) -c $(.IMPSRC)
+.DE
+The creation script attached to the target is used to transform a file with
+the first suffix (in this case,
+.CW .c )
+into a file with the second suffix (here,
+.CW .o ).
+In addition, the target inherits whatever attributes have been applied
+to the transformation rule.
+The simple rule given above says that to transform a C source file
+into an object file, you compile it using
+.CW cc
+with the
+.CW \-c
+flag.
+This rule is taken straight from the system makefile. Many
+transformation rules (and suffixes) are defined there, and I refer you
+to it for more examples (type
+.CW "pmake -h" '' ``
+to find out where it is).
+.LP
+There are several things to note about the transformation rule given
+above:
+.RS
+.IP 1)
+The
+.CW .IMPSRC
+variable.
+.Ix 0 def variable local .IMPSRC
+.Ix 0 def .IMPSRC
+This variable is set to the ``implied source'' (the file from which
+the target is being created; the one with the first suffix), which, in this
+case, is the .c file.
+.IP 2)
+The
+.CW CFLAGS
+variable. Almost all of the transformation rules in the system
+makefile are set up using variables that you can alter in your
+makefile to tailor the rule to your needs. In this case, if you want
+all your C files to be compiled with the
+.B \-g
+flag, to provide information for
+.CW dbx ,
+you would set the
+.CW CFLAGS
+variable to contain
+.CW -g
+.CW "CFLAGS = -g" '') (``
+and PMake would take care of the rest.
+.RE
+.LP
+To give you a quick example, the makefile in 2.3.4
+.Rm 3 2.3.4
+could be changed to this:
+.DS
+OBJS = a.o b.o c.o
+program : $(OBJS)
+ $(CC) -o $(.TARGET) $(.ALLSRC)
+$(OBJS) : defs.h
+.DE
+The transformation rule I gave above takes the place of the 6 lines\**
+.FS
+This is also somewhat cleaner, I think, than the dynamic source
+solution presented in 2.6
+.FE
+.Rm 4 2.6
+.DS
+a.o : a.c
+ cc -c a.c
+b.o : b.c
+ cc -c b.c
+c.o : c.c
+ cc -c c.c
+.DE
+.LP
+Now you may be wondering about the dependency between the
+.CW .o
+and
+.CW .c
+files \*- it's not mentioned anywhere in the new makefile. This is
+because it isn't needed: one of the effects of applying a
+transformation rule is the target comes to depend on the implied
+source. That's why it's called the implied
+.I source .
+.LP
+For a more detailed example. Say you have a makefile like this:
+.DS
+a.out : a.o b.o
+ $(CC) $(.ALLSRC)
+.DE
+and a directory set up like this:
+.DS
+total 4
+-rw-rw-r-- 1 deboor 34 Sep 7 00:43 Makefile
+-rw-rw-r-- 1 deboor 119 Oct 3 19:39 a.c
+-rw-rw-r-- 1 deboor 201 Sep 7 00:43 a.o
+-rw-rw-r-- 1 deboor 69 Sep 7 00:43 b.c
+.DE
+While just typing
+.CW pmake '' ``
+will do the right thing, it's much more informative to type
+.CW "pmake -d s" ''. ``
+This will show you what PMake is up to as it processes the files. In
+this case, PMake prints the following:
+.DS
+Suff_FindDeps (a.out)
+ using existing source a.o
+ applying .o -> .out to "a.o"
+Suff_FindDeps (a.o)
+ trying a.c...got it
+ applying .c -> .o to "a.c"
+Suff_FindDeps (b.o)
+ trying b.c...got it
+ applying .c -> .o to "b.c"
+Suff_FindDeps (a.c)
+ trying a.y...not there
+ trying a.l...not there
+ trying a.c,v...not there
+ trying a.y,v...not there
+ trying a.l,v...not there
+Suff_FindDeps (b.c)
+ trying b.y...not there
+ trying b.l...not there
+ trying b.c,v...not there
+ trying b.y,v...not there
+ trying b.l,v...not there
+--- a.o ---
+cc -c a.c
+--- b.o ---
+cc -c b.c
+--- a.out ---
+cc a.o b.o
+.DE
+.LP
+.CW Suff_FindDeps
+is the name of a function in PMake that is called to check for implied
+sources for a target using transformation rules.
+The transformations it tries are, naturally
+enough, limited to the ones that have been defined (a transformation
+may be defined multiple times, by the way, but only the most recent
+one will be used). You will notice, however, that there is a definite
+order to the suffixes that are tried. This order is set by the
+relative positions of the suffixes on the
+.CW .SUFFIXES
+line \*- the earlier a suffix appears, the earlier it is checked as
+the source of a transformation. Once a suffix has been defined, the
+only way to change its position in the pecking order is to remove all
+the suffixes (by having a
+.CW .SUFFIXES
+dependency line with no sources) and redefine them in the order you
+want. (Previously-defined transformation rules will be automatically
+redefined as the suffixes they involve are re-entered.)
+.LP
+Another way to affect the search order is to make the dependency
+explicit. In the above example,
+.CW a.out
+depends on
+.CW a.o
+and
+.CW b.o .
+Since a transformation exists from
+.CW .o
+to
+.CW .out ,
+PMake uses that, as indicated by the
+.CW "using existing source a.o" '' ``
+message.
+.LP
+The search for a transformation starts from the suffix of the target
+and continues through all the defined transformations, in the order
+dictated by the suffix ranking, until an existing file with the same
+base (the target name minus the suffix and any leading directories) is
+found. At that point, one or more transformation rules will have been
+found to change the one existing file into the target.
+.LP
+For example, ignoring what's in the system makefile for now, say you
+have a makefile like this:
+.DS
+\&.SUFFIXES : .out .o .c .y .l
+\&.l.c :
+ lex $(.IMPSRC)
+ mv lex.yy.c $(.TARGET)
+\&.y.c :
+ yacc $(.IMPSRC)
+ mv y.tab.c $(.TARGET)
+\&.c.o :
+ cc -c $(.IMPSRC)
+\&.o.out :
+ cc -o $(.TARGET) $(.IMPSRC)
+.DE
+and the single file
+.CW jive.l .
+If you were to type
+.CW "pmake -rd ms jive.out" ,'' ``
+you would get the following output for
+.CW jive.out :
+.DS
+Suff_FindDeps (jive.out)
+ trying jive.o...not there
+ trying jive.c...not there
+ trying jive.y...not there
+ trying jive.l...got it
+ applying .l -> .c to "jive.l"
+ applying .c -> .o to "jive.c"
+ applying .o -> .out to "jive.o"
+.DE
+and this is why: PMake starts with the target
+.CW jive.out ,
+figures out its suffix
+.CW .out ) (
+and looks for things it can transform to a
+.CW .out
+file. In this case, it only finds
+.CW .o ,
+so it looks for the file
+.CW jive.o .
+It fails to find it, so it looks for transformations into a
+.CW .o
+file. Again it has only one choice:
+.CW .c .
+So it looks for
+.CW jive.c
+and, as you know, fails to find it. At this point it has two choices:
+it can create the
+.CW .c
+file from either a
+.CW .y
+file or a
+.CW .l
+file. Since
+.CW .y
+came first on the
+.CW .SUFFIXES
+line, it checks for
+.CW jive.y
+first, but can't find it, so it looks for
+.CW jive.l
+and, lo and behold, there it is.
+At this point, it has defined a transformation path as follows:
+.CW .l
+\(->
+.CW .c
+\(->
+.CW .o
+\(->
+.CW .out
+and applies the transformation rules accordingly. For completeness,
+and to give you a better idea of what PMake actually did with this
+three-step transformation, this is what PMake printed for the rest of
+the process:
+.DS
+Suff_FindDeps (jive.o)
+ using existing source jive.c
+ applying .c -> .o to "jive.c"
+Suff_FindDeps (jive.c)
+ using existing source jive.l
+ applying .l -> .c to "jive.l"
+Suff_FindDeps (jive.l)
+Examining jive.l...modified 17:16:01 Oct 4, 1987...up-to-date
+Examining jive.c...non-existent...out-of-date
+--- jive.c ---
+lex jive.l
+\&.\|.\|. meaningless lex output deleted .\|.\|.
+mv lex.yy.c jive.c
+Examining jive.o...non-existent...out-of-date
+--- jive.o ---
+cc -c jive.c
+Examining jive.out...non-existent...out-of-date
+--- jive.out ---
+cc -o jive.out jive.o
+.DE
+.LP
+One final question remains: what does PMake do with targets that have
+no known suffix? PMake simply pretends it actually has a known suffix
+and searches for transformations accordingly.
+The suffix it chooses is the source for the
+.CW .NULL
+.Ix 0 ref .NULL
+target mentioned later. In the system makefile,
+.CW .out
+is chosen as the ``null suffix''
+.Ix 0 def suffix null
+.Ix 0 def "null suffix"
+because most people use PMake to create programs. You are, however,
+free and welcome to change it to a suffix of your own choosing.
+The null suffix is ignored, however, when PMake is in compatibility
+mode (see chapter 4).
+.xH 2 Including Other Makefiles
+.Ix 0 def makefile inclusion
+.Rd 2
+.LP
+Just as for programs, it is often useful to extract certain parts of a
+makefile into another file and just include it in other makefiles
+somehow. Many compilers allow you say something like
+.DS
+#include "defs.h"
+.DE
+to include the contents of
+.CW defs.h
+in the source file. PMake allows you to do the same thing for
+makefiles, with the added ability to use variables in the filenames.
+An include directive in a makefile looks either like this:
+.DS
+#include <file>
+.DE
+or this
+.DS
+#include "file"
+.DE
+The difference between the two is where PMake searches for the file:
+the first way, PMake will look for
+the file only in the system makefile directory (or directories)
+(to find out what that directory is, give PMake the
+.B \-h
+flag).
+.Ix 0 ref flags -h
+The system makefile directory search path can be overridden via the
+.B \-m
+option.
+.Ix 0 ref flags -m
+For files in double-quotes, the search is more complex:
+.RS
+.IP 1)
+The directory of the makefile that's including the file.
+.IP 2)
+The current directory (the one in which you invoked PMake).
+.IP 3)
+The directories given by you using
+.B \-I
+flags, in the order in which you gave them.
+.IP 4)
+Directories given by
+.CW .PATH
+dependency lines (see chapter 4).
+.IP 5)
+The system makefile directory.
+.RE
+.LP
+in that order.
+.LP
+You are free to use PMake variables in the filename\*-PMake will
+expand them before searching for the file. You must specify the
+searching method with either angle brackets or double-quotes
+.I outside
+of a variable expansion. I.e. the following
+.DS
+SYSTEM = <command.mk>
+
+#include $(SYSTEM)
+.DE
+won't work.
+.xH 2 Saving Commands
+.LP
+.Ix 0 def ...
+There may come a time when you will want to save certain commands to
+be executed when everything else is done. For instance: you're
+making several different libraries at one time and you want to create the
+members in parallel. Problem is,
+.CW ranlib
+is another one of those programs that can't be run more than once in
+the same directory at the same time (each one creates a file called
+.CW __.SYMDEF
+into which it stuffs information for the linker to use. Two of them
+running at once will overwrite each other's file and the result will
+be garbage for both parties). You might want a way to save the ranlib
+commands til the end so they can be run one after the other, thus
+keeping them from trashing each other's file. PMake allows you to do
+this by inserting an ellipsis (``.\|.\|.'') as a command between
+commands to be run at once and those to be run later.
+.LP
+So for the
+.CW ranlib
+case above, you might do this:
+.Rd 5
+.DS
+lib1.a : $(LIB1OBJS)
+ rm -f $(.TARGET)
+ ar cr $(.TARGET) $(.ALLSRC)
+ ...
+ ranlib $(.TARGET)
+
+lib2.a : $(LIB2OBJS)
+ rm -f $(.TARGET)
+ ar cr $(.TARGET) $(.ALLSRC)
+ ...
+ ranlib $(.TARGET)
+.DE
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref variable local .ALLSRC
+This would save both
+.DS
+ranlib $(.TARGET)
+.DE
+commands until the end, when they would run one after the other
+(using the correct value for the
+.CW .TARGET
+variable, of course).
+.LP
+Commands saved in this manner are only executed if PMake manages to
+re-create everything without an error.
+.xH 2 Target Attributes
+.LP
+PMake allows you to give attributes to targets by means of special
+sources. Like everything else PMake uses, these sources begin with a
+period and are made up of all upper-case letters. There are various
+reasons for using them, and I will try to give examples for most of
+them. Others you'll have to find uses for yourself. Think of it as ``an
+exercise for the reader.'' By placing one (or more) of these as a source on a
+dependency line, you are ``marking the target(s) with that
+attribute.'' That's just the way I phrase it, so you know.
+.LP
+Any attributes given as sources for a transformation rule are applied
+to the target of the transformation rule when the rule is applied.
+.Ix 0 def attributes
+.Ix 0 ref source
+.Ix 0 ref target
+.nr pw 12
+.IP .DONTCARE \n(pw
+.Ix 0 def attributes .DONTCARE
+.Ix 0 def .DONTCARE
+If a target is marked with this attribute and PMake can't figure out
+how to create it, it will ignore this fact and assume the file isn't
+really needed or actually exists and PMake just can't find it. This may prove
+wrong, but the error will be noted later on, not when PMake tries to create
+the target so marked. This attribute also prevents PMake from
+attempting to touch the target if it is given the
+.B \-t
+flag.
+.Ix 0 ref flags -t
+.IP .EXEC \n(pw
+.Ix 0 def attributes .EXEC
+.Ix 0 def .EXEC
+This attribute causes its shell script to be executed while having no
+effect on targets that depend on it. This makes the target into a sort
+of subroutine. An example. Say you have some LISP files that need to
+be compiled and loaded into a LISP process. To do this, you echo LISP
+commands into a file and execute a LISP with this file as its input
+when everything's done. Say also that you have to load other files
+from another system before you can compile your files and further,
+that you don't want to go through the loading and dumping unless one
+of
+.I your
+files has changed. Your makefile might look a little bit
+like this (remember, this is an educational example, and don't worry
+about the
+.CW COMPILE
+rule, all will soon become clear, grasshopper):
+.DS
+system : init a.fasl b.fasl c.fasl
+ for i in $(.ALLSRC);
+ do
+ echo -n '(load "' >> input
+ echo -n ${i} >> input
+ echo '")' >> input
+ done
+ echo '(dump "$(.TARGET)")' >> input
+ lisp < input
+
+a.fasl : a.l init COMPILE
+b.fasl : b.l init COMPILE
+c.fasl : c.l init COMPILE
+COMPILE : .USE
+ echo '(compile "$(.ALLSRC)")' >> input
+init : .EXEC
+ echo '(load-system)' > input
+.DE
+.Ix 0 ref .USE
+.Ix 0 ref attributes .USE
+.Ix 0 ref variable local .ALLSRC
+.IP "\&"
+.CW .EXEC
+sources, don't appear in the local variables of targets that depend on
+them (nor are they touched if PMake is given the
+.B \-t
+flag).
+.Ix 0 ref flags -t
+Note that all the rules, not just that for
+.CW system ,
+include
+.CW init
+as a source. This is because none of the other targets can be made
+until
+.CW init
+has been made, thus they depend on it.
+.IP .EXPORT \n(pw
+.Ix 0 def attributes .EXPORT
+.Ix 0 def .EXPORT
+This is used to mark those targets whose creation should be sent to
+another machine if at all possible. This may be used by some
+exportation schemes if the exportation is expensive. You should ask
+your system administrator if it is necessary.
+.IP .EXPORTSAME \n(pw
+.Ix 0 def attributes .EXPORTSAME
+.Ix 0 def .EXPORTSAME
+Tells the export system that the job should be exported to a machine
+of the same architecture as the current one. Certain operations (e.g.
+running text through
+.CW nroff )
+can be performed the same on any architecture (CPU and
+operating system type), while others (e.g. compiling a program with
+.CW cc )
+must be performed on a machine with the same architecture. Not all
+export systems will support this attribute.
+.IP .IGNORE \n(pw
+.Ix 0 def attributes .IGNORE
+.Ix 0 def .IGNORE attribute
+Giving a target the
+.CW .IGNORE
+attribute causes PMake to ignore errors from any of the target's commands, as
+if they all had `\-' before them.
+.IP .INVISIBLE \n(pw
+.Ix 0 def attributes .INVISIBLE
+.Ix 0 def .INVISIBLE
+This allows you to specify one target as a source for another without
+the one affecting the other's local variables. Useful if, say, you
+have a makefile that creates two programs, one of which is used to
+create the other, so it must exist before the other is created. You
+could say
+.DS
+prog1 : $(PROG1OBJS) prog2 MAKEINSTALL
+prog2 : $(PROG2OBJS) .INVISIBLE MAKEINSTALL
+.DE
+where
+.CW MAKEINSTALL
+is some complex .USE rule (see below) that depends on the
+.Ix 0 ref .USE
+.CW .ALLSRC
+variable containing the right things. Without the
+.CW .INVISIBLE
+attribute for
+.CW prog2 ,
+the
+.CW MAKEINSTALL
+rule couldn't be applied. This is not as useful as it should be, and
+the semantics may change (or the whole thing go away) in the
+not-too-distant future.
+.IP .JOIN \n(pw
+.Ix 0 def attributes .JOIN
+.Ix 0 def .JOIN
+This is another way to avoid performing some operations in parallel
+while permitting everything else to be done so. Specifically it
+forces the target's shell script to be executed only if one or more of the
+sources was out-of-date. In addition, the target's name,
+in both its
+.CW .TARGET
+variable and all the local variables of any target that depends on it,
+is replaced by the value of its
+.CW .ALLSRC
+variable.
+As an example, suppose you have a program that has four libraries that
+compile in the same directory along with, and at the same time as, the
+program. You again have the problem with
+.CW ranlib
+that I mentioned earlier, only this time it's more severe: you
+can't just put the ranlib off to the end since the program
+will need those libraries before it can be re-created. You can do
+something like this:
+.DS
+program : $(OBJS) libraries
+ cc -o $(.TARGET) $(.ALLSRC)
+
+libraries : lib1.a lib2.a lib3.a lib4.a .JOIN
+ ranlib $(.OODATE)
+.DE
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref variable local .ALLSRC
+.Ix 0 ref variable local .OODATE
+.Ix 0 ref .TARGET
+.Ix 0 ref .ALLSRC
+.Ix 0 ref .OODATE
+In this case, PMake will re-create the
+.CW $(OBJS)
+as necessary, along with
+.CW lib1.a ,
+.CW lib2.a ,
+.CW lib3.a
+and
+.CW lib4.a .
+It will then execute
+.CW ranlib
+on any library that was changed and set
+.CW program 's
+.CW .ALLSRC
+variable to contain what's in
+.CW $(OBJS)
+followed by
+.CW "lib1.a lib2.a lib3.a lib4.a" .'' ``
+In case you're wondering, it's called
+.CW .JOIN
+because it joins together different threads of the ``input graph'' at
+the target marked with the attribute.
+Another aspect of the .JOIN attribute is it keeps the target from
+being created if the
+.B \-t
+flag was given.
+.Ix 0 ref flags -t
+.IP .MAKE \n(pw
+.Ix 0 def attributes .MAKE
+.Ix 0 def .MAKE
+The
+.CW .MAKE
+attribute marks its target as being a recursive invocation of PMake.
+This forces PMake to execute the script associated with the target (if
+it's out-of-date) even if you gave the
+.B \-n
+or
+.B \-t
+flag. By doing this, you can start at the top of a system and type
+.DS
+pmake -n
+.DE
+and have it descend the directory tree (if your makefiles are set up
+correctly), printing what it would have executed if you hadn't
+included the
+.B \-n
+flag.
+.IP .NOEXPORT \n(pw
+.Ix 0 def attributes .NOEXPORT
+.Ix 0 def .NOEXPORT attribute
+If possible, PMake will attempt to export the creation of all targets to
+another machine (this depends on how PMake was configured). Sometimes,
+the creation is so simple, it is pointless to send it to another
+machine. If you give the target the
+.CW .NOEXPORT
+attribute, it will be run locally, even if you've given PMake the
+.B "\-L 0"
+flag.
+.IP .NOTMAIN \n(pw
+.Ix 0 def attributes .NOTMAIN
+.Ix 0 def .NOTMAIN
+Normally, if you do not specify a target to make in any other way,
+PMake will take the first target on the first dependency line of a
+makefile as the target to create. That target is known as the ``Main
+Target'' and is labeled as such if you print the dependencies out
+using the
+.B \-p
+flag.
+.Ix 0 ref flags -p
+Giving a target this attribute tells PMake that the target is
+definitely
+.I not
+the Main Target.
+This allows you to place targets in an included makefile and
+have PMake create something else by default.
+.IP .PRECIOUS \n(pw
+.Ix 0 def attributes .PRECIOUS
+.Ix 0 def .PRECIOUS attribute
+When PMake is interrupted (you type control-C at the keyboard), it
+will attempt to clean up after itself by removing any half-made
+targets. If a target has the
+.CW .PRECIOUS
+attribute, however, PMake will leave it alone. An additional side
+effect of the `::' operator is to mark the targets as
+.CW .PRECIOUS .
+.Ix 0 ref operator double-colon
+.Ix 0 ref ::
+.IP .SILENT \n(pw
+.Ix 0 def attributes .SILENT
+.Ix 0 def .SILENT attribute
+Marking a target with this attribute keeps its commands from being
+printed when they're executed, just as if they had an `@' in front of them.
+.IP .USE \n(pw
+.Ix 0 def attributes .USE
+.Ix 0 def .USE
+By giving a target this attribute, you turn it into PMake's equivalent
+of a macro. When the target is used as a source for another target,
+the other target acquires the commands, sources and attributes (except
+.CW .USE )
+of the source.
+If the target already has commands, the
+.CW .USE
+target's commands are added to the end. If more than one .USE-marked
+source is given to a target, the rules are applied sequentially.
+.IP "\&" \n(pw
+The typical .USE rule (as I call them) will use the sources of the
+target to which it is applied (as stored in the
+.CW .ALLSRC
+variable for the target) as its ``arguments,'' if you will.
+For example, you probably noticed that the commands for creating
+.CW lib1.a
+and
+.CW lib2.a
+in the example in section 3.3
+.Rm 5 3.3
+were exactly the same. You can use the
+.CW .USE
+attribute to eliminate the repetition, like so:
+.DS
+lib1.a : $(LIB1OBJS) MAKELIB
+lib2.a : $(LIB2OBJS) MAKELIB
+
+MAKELIB : .USE
+ rm -f $(.TARGET)
+ ar cr $(.TARGET) $(.ALLSRC)
+ ...
+ ranlib $(.TARGET)
+.DE
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref variable local .ALLSRC
+.IP "\&" \n(pw
+Several system makefiles (not to be confused with The System Makefile)
+make use of these .USE rules to make your
+life easier (they're in the default, system makefile directory...take a look).
+Note that the .USE rule source itself
+.CW MAKELIB ) (
+does not appear in any of the targets's local variables.
+There is no limit to the number of times I could use the
+.CW MAKELIB
+rule. If there were more libraries, I could continue with
+.CW "lib3.a : $(LIB3OBJS) MAKELIB" '' ``
+and so on and so forth.
+.xH 2 Special Targets
+.LP
+As there were in Make, so there are certain targets that have special
+meaning to PMake. When you use one on a dependency line, it is the
+only target that may appear on the left-hand-side of the operator.
+.Ix 0 ref target
+.Ix 0 ref operator
+As for the attributes and variables, all the special targets
+begin with a period and consist of upper-case letters only.
+I won't describe them all in detail because some of them are rather
+complex and I'll describe them in more detail than you'll want in
+chapter 4.
+The targets are as follows:
+.nr pw 10
+.IP .BEGIN \n(pw
+.Ix 0 def .BEGIN
+Any commands attached to this target are executed before anything else
+is done. You can use it for any initialization that needs doing.
+.IP .DEFAULT \n(pw
+.Ix 0 def .DEFAULT
+This is sort of a .USE rule for any target (that was used only as a
+source) that PMake can't figure out any other way to create. It's only
+``sort of'' a .USE rule because only the shell script attached to the
+.CW .DEFAULT
+target is used. The
+.CW .IMPSRC
+variable of a target that inherits
+.CW .DEFAULT 's
+commands is set to the target's own name.
+.Ix 0 ref .IMPSRC
+.Ix 0 ref variable local .IMPSRC
+.IP .END \n(pw
+.Ix 0 def .END
+This serves a function similar to
+.CW .BEGIN ,
+in that commands attached to it are executed once everything has been
+re-created (so long as no errors occurred). It also serves the extra
+function of being a place on which PMake can hang commands you put off
+to the end. Thus the script for this target will be executed before
+any of the commands you save with the ``.\|.\|.''.
+.Ix 0 ref ...
+.IP .EXPORT \n(pw
+The sources for this target are passed to the exportation system compiled
+into PMake. Some systems will use these sources to configure
+themselves. You should ask your system administrator about this.
+.IP .IGNORE \n(pw
+.Ix 0 def .IGNORE target
+.Ix 0 ref .IGNORE attribute
+.Ix 0 ref attributes .IGNORE
+This target marks each of its sources with the
+.CW .IGNORE
+attribute. If you don't give it any sources, then it is like
+giving the
+.B \-i
+flag when you invoke PMake \*- errors are ignored for all commands.
+.Ix 0 ref flags -i
+.IP .INCLUDES \n(pw
+.Ix 0 def .INCLUDES target
+.Ix 0 def variable global .INCLUDES
+.Ix 0 def .INCLUDES variable
+The sources for this target are taken to be suffixes that indicate a
+file that can be included in a program source file.
+The suffix must have already been declared with
+.CW .SUFFIXES
+(see below).
+Any suffix so marked will have the directories on its search path
+(see
+.CW .PATH ,
+below) placed in the
+.CW .INCLUDES
+variable, each preceded by a
+.B \-I
+flag. This variable can then be used as an argument for the compiler
+in the normal fashion. The
+.CW .h
+suffix is already marked in this way in the system makefile.
+.Ix 0 ref makefile system
+E.g. if you have
+.DS
+\&.SUFFIXES : .bitmap
+\&.PATH.bitmap : /usr/local/X/lib/bitmaps
+\&.INCLUDES : .bitmap
+.DE
+PMake will place
+.CW "-I/usr/local/X/lib/bitmaps" '' ``
+in the
+.CW .INCLUDES
+variable and you can then say
+.DS
+cc $(.INCLUDES) -c xprogram.c
+.DE
+(Note: the
+.CW .INCLUDES
+variable is not actually filled in until the entire makefile has been read.)
+.IP .INTERRUPT \n(pw
+.Ix 0 def .INTERRUPT
+When PMake is interrupted,
+it will execute the commands in the script for this target, if it
+exists.
+.IP .LIBS \n(pw
+.Ix 0 def .LIBS target
+.Ix 0 def .LIBS variable
+.Ix 0 def variable global .LIBS
+This does for libraries what
+.CW .INCLUDES
+does for include files, except the flag used is
+.B \-L ,
+as required by those linkers that allow you to tell them where to find
+libraries. The variable used is
+.CW .LIBS .
+Be forewarned that PMake may not have been compiled to do this if the
+linker on your system doesn't accept the
+.B \-L
+flag, though the
+.CW .LIBS
+variable will always be defined once the makefile has been read.
+.IP .MAIN \n(pw
+.Ix 0 def .MAIN
+If you didn't give a target (or targets) to create when you invoked
+PMake, it will take the sources of this target as the targets to
+create.
+.IP .MAKEFLAGS \n(pw
+.Ix 0 def .MAKEFLAGS target
+This target provides a way for you to always specify flags for PMake
+when the makefile is used. The flags are just as they would be typed
+to the shell (except you can't use shell variables unless they're in
+the environment),
+though the
+.B \-f
+and
+.B \-r
+flags have no effect.
+.IP .NULL \n(pw
+.Ix 0 def .NULL
+.Ix 0 ref suffix null
+.Ix 0 ref "null suffix"
+This allows you to specify what suffix PMake should pretend a file has
+if, in fact, it has no known suffix. Only one suffix may be so
+designated. The last source on the dependency line is the suffix that
+is used (you should, however, only give one suffix.\|.\|.).
+.IP .PATH \n(pw
+.Ix 0 def .PATH
+If you give sources for this target, PMake will take them as
+directories in which to search for files it cannot find in the current
+directory. If you give no sources, it will clear out any directories
+added to the search path before. Since the effects of this all get
+very complex, I'll leave it til chapter four to give you a complete
+explanation.
+.IP .PATH\fIsuffix\fP \n(pw
+.Ix 0 ref .PATH
+This does a similar thing to
+.CW .PATH ,
+but it does it only for files with the given suffix. The suffix must
+have been defined already. Look at
+.B "Search Paths"
+(section 4.1)
+.Rm 6 4.1
+for more information.
+.IP .PRECIOUS \n(pw
+.Ix 0 def .PRECIOUS target
+.Ix 0 ref .PRECIOUS attribute
+.Ix 0 ref attributes .PRECIOUS
+Similar to
+.CW .IGNORE ,
+this gives the
+.CW .PRECIOUS
+attribute to each source on the dependency line, unless there are no
+sources, in which case the
+.CW .PRECIOUS
+attribute is given to every target in the file.
+.IP .RECURSIVE \n(pw
+.Ix 0 def .RECURSIVE
+.Ix 0 ref attributes .MAKE
+.Ix 0 ref .MAKE
+This target applies the
+.CW .MAKE
+attribute to all its sources. It does nothing if you don't give it any sources.
+.IP .SHELL \n(pw
+.Ix 0 def .SHELL
+PMake is not constrained to only using the Bourne shell to execute
+the commands you put in the makefile. You can tell it some other shell
+to use with this target. Check out
+.B "A Shell is a Shell is a Shell"
+(section 4.4)
+.Rm 7 4.4
+for more information.
+.IP .SILENT \n(pw
+.Ix 0 def .SILENT target
+.Ix 0 ref .SILENT attribute
+.Ix 0 ref attributes .SILENT
+When you use
+.CW .SILENT
+as a target, it applies the
+.CW .SILENT
+attribute to each of its sources. If there are no sources on the
+dependency line, then it is as if you gave PMake the
+.B \-s
+flag and no commands will be echoed.
+.IP .SUFFIXES \n(pw
+.Ix 0 def .SUFFIXES
+This is used to give new file suffixes for PMake to handle. Each
+source is a suffix PMake should recognize. If you give a
+.CW .SUFFIXES
+dependency line with no sources, PMake will forget about all the
+suffixes it knew (this also nukes the null suffix).
+For those targets that need to have suffixes defined, this is how you do it.
+.LP
+In addition to these targets, a line of the form
+.DS
+\fIattribute\fP : \fIsources\fP
+.DE
+applies the
+.I attribute
+to all the targets listed as
+.I sources .
+.xH 2 Modifying Variable Expansion
+.LP
+.Ix 0 def variable expansion modified
+.Ix 0 ref variable expansion
+.Ix 0 def variable modifiers
+Variables need not always be expanded verbatim. PMake defines several
+modifiers that may be applied to a variable's value before it is
+expanded. You apply a modifier by placing it after the variable name
+with a colon between the two, like so:
+.DS
+${\fIVARIABLE\fP:\fImodifier\fP}
+.DE
+Each modifier is a single character followed by something specific to
+the modifier itself.
+You may apply as many modifiers as you want \*- each one is applied to
+the result of the previous and is separated from the previous by
+another colon.
+.LP
+There are seven ways to modify a variable's expansion, most of which
+come from the C shell variable modification characters:
+.RS
+.IP "M\fIpattern\fP"
+.Ix 0 def :M
+.Ix 0 def modifier match
+This is used to select only those words (a word is a series of
+characters that are neither spaces nor tabs) that match the given
+.I pattern .
+The pattern is a wildcard pattern like that used by the shell, where
+.CW *
+means 0 or more characters of any sort;
+.CW ?
+is any single character;
+.CW [abcd]
+matches any single character that is either `a', `b', `c' or `d'
+(there may be any number of characters between the brackets);
+.CW [0-9]
+matches any single character that is between `0' and `9' (i.e. any
+digit. This form may be freely mixed with the other bracket form), and
+`\\' is used to escape any of the characters `*', `?', `[' or `:',
+leaving them as regular characters to match themselves in a word.
+For example, the system makefile
+.CW <makedepend.mk>
+uses
+.CW "$(CFLAGS:M-[ID]*)" '' ``
+to extract all the
+.CW \-I
+and
+.CW \-D
+flags that would be passed to the C compiler. This allows it to
+properly locate include files and generate the correct dependencies.
+.IP "N\fIpattern\fP"
+.Ix 0 def :N
+.Ix 0 def modifier nomatch
+This is identical to
+.CW :M
+except it substitutes all words that don't match the given pattern.
+.IP "S/\fIsearch-string\fP/\fIreplacement-string\fP/[g]"
+.Ix 0 def :S
+.Ix 0 def modifier substitute
+Causes the first occurrence of
+.I search-string
+in the variable to be replaced by
+.I replacement-string ,
+unless the
+.CW g
+flag is given at the end, in which case all occurrences of the string
+are replaced. The substitution is performed on each word in the
+variable in turn. If
+.I search-string
+begins with a
+.CW ^ ,
+the string must match starting at the beginning of the word. If
+.I search-string
+ends with a
+.CW $ ,
+the string must match to the end of the word (these two may be
+combined to force an exact match). If a backslash precedes these two
+characters, however, they lose their special meaning. Variable
+expansion also occurs in the normal fashion inside both the
+.I search-string
+and the
+.I replacement-string ,
+.B except
+that a backslash is used to prevent the expansion of a
+.CW $ ,
+not another dollar sign, as is usual.
+Note that
+.I search-string
+is just a string, not a pattern, so none of the usual
+regular-expression/wildcard characters have any special meaning save
+.CW ^
+and
+.CW $ .
+In the replacement string,
+the
+.CW &
+character is replaced by the
+.I search-string
+unless it is preceded by a backslash.
+You are allowed to use any character except
+colon or exclamation point to separate the two strings. This so-called
+delimiter character may be placed in either string by preceding it
+with a backslash.
+.IP T
+.Ix 0 def :T
+.Ix 0 def modifier tail
+Replaces each word in the variable expansion by its last
+component (its ``tail''). For example, given
+.DS
+OBJS = ../lib/a.o b /usr/lib/libm.a
+TAILS = $(OBJS:T)
+.DE
+the variable
+.CW TAILS
+would expand to
+.CW "a.o b libm.a" .'' ``
+.IP H
+.Ix 0 def :H
+.Ix 0 def modifier head
+This is similar to
+.CW :T ,
+except that every word is replaced by everything but the tail (the
+``head''). Using the same definition of
+.CW OBJS ,
+the string
+.CW "$(OBJS:H)" '' ``
+would expand to
+.CW "../lib /usr/lib" .'' ``
+Note that the final slash on the heads is removed and
+anything without a head is replaced by the empty string.
+.IP E
+.Ix 0 def :E
+.Ix 0 def modifier extension
+.Ix 0 def modifier suffix
+.Ix 0 ref suffix "variable modifier"
+.CW :E
+replaces each word by its suffix (``extension''). So
+.CW "$(OBJS:E)" '' ``
+would give you
+.CW ".o .a" .'' ``
+.IP R
+.Ix 0 def :R
+.Ix 0 def modifier root
+.Ix 0 def modifier base
+This replaces each word by everything but the suffix (the ``root'' of
+the word).
+.CW "$(OBJS:R)" '' ``
+expands to ``
+.CW "../lib/a b /usr/lib/libm" .''
+.RE
+.LP
+In addition, the System V style of substitution is also supported.
+This looks like:
+.DS
+$(\fIVARIABLE\fP:\fIsearch-string\fP=\fIreplacement\fP)
+.DE
+It must be the last modifier in the chain. The search is anchored at
+the end of each word, so only suffixes or whole words may be replaced.
+.xH 2 More on Debugging
+.xH 2 More Exercises
+.IP (3.1)
+You've got a set programs, each of which is created from its own
+assembly-language source file (suffix
+.CW .asm ).
+Each program can be assembled into two versions, one with error-checking
+code assembled in and one without. You could assemble them into files
+with different suffixes
+.CW .eobj \& (
+and
+.CW .obj ,
+for instance), but your linker only understands files that end in
+.CW .obj .
+To top it all off, the final executables
+.I must
+have the suffix
+.CW .exe .
+How can you still use transformation rules to make your life easier
+(Hint: assume the error-checking versions have
+.CW ec
+tacked onto their prefix)?
+.IP (3.2)
+Assume, for a moment or two, you want to perform a sort of
+``indirection'' by placing the name of a variable into another one,
+then you want to get the value of the first by expanding the second
+somehow. Unfortunately, PMake doesn't allow constructs like
+.DS I
+$($(FOO))
+.DE
+What do you do? Hint: no further variable expansion is performed after
+modifiers are applied, thus if you cause a $ to occur in the
+expansion, that's what will be in the result.
+.xH 1 PMake for Gods
+.LP
+This chapter is devoted to those facilities in PMake that allow you to
+do a great deal in a makefile with very little work, as well as do
+some things you couldn't do in Make without a great deal of work (and
+perhaps the use of other programs). The problem with these features,
+is they must be handled with care, or you will end up with a mess.
+.LP
+Once more, I assume a greater familiarity with
+.UX
+or Sprite than I did in the previous two chapters.
+.xH 2 Search Paths
+.Rd 6
+.LP
+PMake supports the dispersal of files into multiple directories by
+allowing you to specify places to look for sources with
+.CW .PATH
+targets in the makefile. The directories you give as sources for these
+targets make up a ``search path.'' Only those files used exclusively
+as sources are actually sought on a search path, the assumption being
+that anything listed as a target in the makefile can be created by the
+makefile and thus should be in the current directory.
+.LP
+There are two types of search paths
+in PMake: one is used for all types of files (including included
+makefiles) and is specified with a plain
+.CW .PATH
+target (e.g.
+.CW ".PATH : RCS" ''), ``
+while the other is specific to a certain type of file, as indicated by
+the file's suffix. A specific search path is indicated by immediately following
+the
+.CW .PATH
+with the suffix of the file. For instance
+.DS
+\&.PATH.h : /sprite/lib/include /sprite/att/lib/include
+.DE
+would tell PMake to look in the directories
+.CW /sprite/lib/include
+and
+.CW /sprite/att/lib/include
+for any files whose suffix is
+.CW .h .
+.LP
+The current directory is always consulted first to see if a file
+exists. Only if it cannot be found there are the directories in the
+specific search path, followed by those in the general search path,
+consulted.
+.LP
+A search path is also used when expanding wildcard characters. If the
+pattern has a recognizable suffix on it, the path for that suffix will
+be used for the expansion. Otherwise the default search path is employed.
+.LP
+When a file is found in some directory other than the current one, all
+local variables that would have contained the target's name
+.CW .ALLSRC , (
+and
+.CW .IMPSRC )
+will instead contain the path to the file, as found by PMake.
+Thus if you have a file
+.CW ../lib/mumble.c
+and a makefile
+.DS
+\&.PATH.c : ../lib
+mumble : mumble.c
+ $(CC) -o $(.TARGET) $(.ALLSRC)
+.DE
+the command executed to create
+.CW mumble
+would be
+.CW "cc -o mumble ../lib/mumble.c" .'' ``
+(As an aside, the command in this case isn't strictly necessary, since
+it will be found using transformation rules if it isn't given. This is because
+.CW .out
+is the null suffix by default and a transformation exists from
+.CW .c
+to
+.CW .out .
+Just thought I'd throw that in.)
+.LP
+If a file exists in two directories on the same search path, the file
+in the first directory on the path will be the one PMake uses. So if
+you have a large system spread over many directories, it would behoove
+you to follow a naming convention that avoids such conflicts.
+.LP
+Something you should know about the way search paths are implemented
+is that each directory is read, and its contents cached, exactly once
+\&\*- when it is first encountered \*- so any changes to the
+directories while PMake is running will not be noted when searching
+for implicit sources, nor will they be found when PMake attempts to
+discover when the file was last modified, unless the file was created in the
+current directory. While people have suggested that PMake should read
+the directories each time, my experience suggests that the caching seldom
+causes problems. In addition, not caching the directories slows things
+down enormously because of PMake's attempts to apply transformation
+rules through non-existent files \*- the number of extra file-system
+searches is truly staggering, especially if many files without
+suffixes are used and the null suffix isn't changed from
+.CW .out .
+.xH 2 Archives and Libraries
+.LP
+.UX
+and Sprite allow you to merge files into an archive using the
+.CW ar
+command. Further, if the files are relocatable object files, you can
+run
+.CW ranlib
+on the archive and get yourself a library that you can link into any
+program you want. The main problem with archives is they double the
+space you need to store the archived files, since there's one copy in
+the archive and one copy out by itself. The problem with libraries is
+you usually think of them as
+.CW -lm
+rather than
+.CW /usr/lib/libm.a
+and the linker thinks they're out-of-date if you so much as look at
+them.
+.LP
+PMake solves the problem with archives by allowing you to tell it to
+examine the files in the archives (so you can remove the individual
+files without having to regenerate them later). To handle the problem
+with libraries, PMake adds an additional way of deciding if a library
+is out-of-date:
+.IP \(bu 2
+If the table of contents is older than the library, or is missing, the
+library is out-of-date.
+.LP
+A library is any target that looks like
+.CW \-l name'' ``
+or that ends in a suffix that was marked as a library using the
+.CW .LIBS
+target.
+.CW .a
+is so marked in the system makefile.
+.LP
+Members of an archive are specified as
+``\fIarchive\fP(\fImember\fP[ \fImember\fP...])''.
+Thus
+.CW libdix.a(window.o) '' ``'
+specifies the file
+.CW window.o
+in the archive
+.CW libdix.a .
+You may also use wildcards to specify the members of the archive. Just
+remember that most the wildcard characters will only find
+.I existing
+files.
+.LP
+A file that is a member of an archive is treated specially. If the
+file doesn't exist, but it is in the archive, the modification time
+recorded in the archive is used for the file when determining if the
+file is out-of-date. When figuring out how to make an archived member target
+(not the file itself, but the file in the archive \*- the
+\fIarchive\fP(\fImember\fP) target), special care is
+taken with the transformation rules, as follows:
+.IP \(bu 2
+\&\fIarchive\fP(\fImember\fP) is made to depend on \fImember\fP.
+.IP \(bu 2
+The transformation from the \fImember\fP's suffix to the
+\fIarchive\fP's suffix is applied to the \fIarchive\fP(\fImember\fP) target.
+.IP \(bu 2
+The \fIarchive\fP(\fImember\fP)'s
+.CW .TARGET
+variable is set to the name of the \fImember\fP if \fImember\fP is
+actually a target, or the path to the member file if \fImember\fP is
+only a source.
+.IP \(bu 2
+The
+.CW .ARCHIVE
+variable for the \fIarchive\fP(\fImember\fP) target is set to the name
+of the \fIarchive\fP.
+.Ix 0 def variable local .ARCHIVE
+.Ix 0 def .ARCHIVE
+.IP \(bu 2
+The
+.CW .MEMBER
+variable is set to the actual string inside the parentheses. In most
+cases, this will be the same as the
+.CW .TARGET
+variable.
+.Ix 0 def variable local .MEMBER
+.Ix 0 def .MEMBER
+.IP \(bu 2
+The \fIarchive\fP(\fImember\fP)'s place in the local variables of the
+targets that depend on it is taken by the value of its
+.CW .TARGET
+variable.
+.LP
+Thus, a program library could be created with the following makefile:
+.DS
+\&.o.a :
+ ...
+ rm -f $(.TARGET:T)
+OBJS = obj1.o obj2.o obj3.o
+libprog.a : libprog.a($(OBJS))
+ ar cru $(.TARGET) $(.OODATE)
+ ranlib $(.TARGET)
+.DE
+This will cause the three object files to be compiled (if the
+corresponding source files were modified after the object file or, if
+that doesn't exist, the archived object file), the out-of-date ones
+archived in
+.CW libprog.a ,
+a table of contents placed in the archive and the newly-archived
+object files to be removed.
+.LP
+All this is used in the
+.CW makelib.mk
+system makefile to create a single library with ease. This makefile
+looks like this:
+.DS
+.SM
+#
+# Rules for making libraries. The object files that make up the library
+# are removed once they are archived.
+#
+# To make several libraries in parallel, you should define the variable
+# "many_libraries". This will serialize the invocations of ranlib.
+#
+# To use, do something like this:
+#
+# OBJECTS = <files in the library>
+#
+# fish.a: fish.a($(OBJECTS)) MAKELIB
+#
+#
+
+#ifndef _MAKELIB_MK
+_MAKELIB_MK =
+
+#include <po.mk>
+
+\&.po.a .o.a :
+ ...
+ rm -f $(.MEMBER)
+
+ARFLAGS ?= crl
+
+#
+# Re-archive the out-of-date members and recreate the library's table of
+# contents using ranlib. If many_libraries is defined, put the ranlib
+# off til the end so many libraries can be made at once.
+#
+MAKELIB : .USE .PRECIOUS
+ ar $(ARFLAGS) $(.TARGET) $(.OODATE)
+#ifndef no_ranlib
+# ifdef many_libraries
+ ...
+# endif /* many_libraries */
+ ranlib $(.TARGET)
+#endif /* no_ranlib */
+
+#endif /* _MAKELIB_MK */
+.DE
+.xH 2 On the Condition...
+.Rd 1
+.LP
+Like the C compiler before it, PMake allows you to configure the makefile,
+based on the current environment, using conditional statements. A
+conditional looks like this:
+.DS
+#if \fIboolean expression\fP
+\fIlines\fP
+#elif \fIanother boolean expression\fP
+\fImore lines\fP
+#else
+\fIstill more lines\fP
+#endif
+.DE
+They may be nested to a maximum depth of 30 and may occur anywhere
+(except in a comment, of course). The
+.CW # '' ``
+must the very first character on the line.
+.LP
+Each
+.I "boolean expression"
+is made up of terms that look like function calls, the standard C
+boolean operators
+.CW && ,
+.CW || ,
+and
+.CW ! ,
+and the standard relational operators
+.CW == ,
+.CW != ,
+.CW > ,
+.CW >= ,
+.CW < ,
+and
+.CW <= ,
+with
+.CW ==
+and
+.CW !=
+being overloaded to allow string comparisons as well.
+.CW &&
+represents logical AND;
+.CW ||
+is logical OR and
+.CW !
+is logical NOT. The arithmetic and string operators take precedence
+over all three of these operators, while NOT takes precedence over
+AND, which takes precedence over OR. This precedence may be
+overridden with parentheses, and an expression may be parenthesized to
+your heart's content. Each term looks like a call on one of four
+functions:
+.nr pw 9
+.Ix 0 def make
+.Ix 0 def conditional make
+.Ix 0 def if make
+.IP make \n(pw
+The syntax is
+.CW make( \fItarget\fP\c
+.CW )
+where
+.I target
+is a target in the makefile. This is true if the given target was
+specified on the command line, or as the source for a
+.CW .MAIN
+target (note that the sources for
+.CW .MAIN
+are only used if no targets were given on the command line).
+.IP defined \n(pw
+.Ix 0 def defined
+.Ix 0 def conditional defined
+.Ix 0 def if defined
+The syntax is
+.CW defined( \fIvariable\fP\c
+.CW )
+and is true if
+.I variable
+is defined. Certain variables are defined in the system makefile that
+identify the system on which PMake is being run.
+.IP exists \n(pw
+.Ix 0 def exists
+.Ix 0 def conditional exists
+.Ix 0 def if exists
+The syntax is
+.CW exists( \fIfile\fP\c
+.CW )
+and is true if the file can be found on the global search path
+(i.e. that defined by
+.CW .PATH
+targets, not by
+.CW .PATH \fIsuffix\fP
+targets).
+.IP empty \n(pw
+.Ix 0 def empty
+.Ix 0 def conditional empty
+.Ix 0 def if empty
+This syntax is much like the others, except the string inside the
+parentheses is of the same form as you would put between parentheses
+when expanding a variable, complete with modifiers and everything. The
+function returns true if the resulting string is empty (NOTE: an undefined
+variable in this context will cause at the very least a warning
+message about a malformed conditional, and at the worst will cause the
+process to stop once it has read the makefile. If you want to check
+for a variable being defined or empty, use the expression
+.CW !defined( \fIvar\fP\c ``
+.CW ") || empty(" \fIvar\fP\c
+.CW ) ''
+as the definition of
+.CW ||
+will prevent the
+.CW empty()
+from being evaluated and causing an error, if the variable is
+undefined). This can be used to see if a variable contains a given
+word, for example:
+.DS
+#if !empty(\fIvar\fP:M\fIword\fP)
+.DE
+.LP
+The arithmetic and string operators may only be used to test the value
+of a variable. The lefthand side must contain the variable expansion,
+while the righthand side contains either a string, enclosed in
+double-quotes, or a number. The standard C numeric conventions (except
+for specifying an octal number) apply to both sides. E.g.
+.DS
+#if $(OS) == 4.3
+
+#if $(MACHINE) == "sun3"
+
+#if $(LOAD_ADDR) < 0xc000
+.DE
+are all valid conditionals. In addition, the numeric value of a
+variable can be tested as a boolean as follows:
+.DS
+#if $(LOAD)
+.DE
+would see if
+.CW LOAD
+contains a non-zero value and
+.DS
+#if !$(LOAD)
+.DE
+would test if
+.CW LOAD
+contains a zero value.
+.LP
+In addition to the bare
+.CW #if ,'' ``
+there are other forms that apply one of the first two functions to each
+term. They are as follows:
+.DS
+ ifdef \fRdefined\fP
+ ifndef \fR!defined\fP
+ ifmake \fRmake\fP
+ ifnmake \fR!make\fP
+.DE
+There are also the ``else if'' forms:
+.CW elif ,
+.CW elifdef ,
+.CW elifndef ,
+.CW elifmake ,
+and
+.CW elifnmake .
+.LP
+For instance, if you wish to create two versions of a program, one of which
+is optimized (the production version) and the other of which is for debugging
+(has symbols for dbx), you have two choices: you can create two
+makefiles, one of which uses the
+.CW \-g
+flag for the compilation, while the other uses the
+.CW \-O
+flag, or you can use another target (call it
+.CW debug )
+to create the debug version. The construct below will take care of
+this for you. I have also made it so defining the variable
+.CW DEBUG
+(say with
+.CW "pmake -D DEBUG" )
+will also cause the debug version to be made.
+.DS
+#if defined(DEBUG) || make(debug)
+CFLAGS += -g
+#else
+CFLAGS += -O
+#endif
+.DE
+There are, of course, problems with this approach. The most glaring
+annoyance is that if you want to go from making a debug version to
+making a production version, you have to remove all the object files,
+or you will get some optimized and some debug versions in the same
+program. Another annoyance is you have to be careful not to make two
+targets that ``conflict'' because of some conditionals in the
+makefile. For instance
+.DS
+#if make(print)
+FORMATTER = ditroff -Plaser_printer
+#endif
+#if make(draft)
+FORMATTER = nroff -Pdot_matrix_printer
+#endif
+.DE
+would wreak havoc if you tried
+.CW "pmake draft print" '' ``
+since you would use the same formatter for each target. As I said,
+this all gets somewhat complicated.
+.xH 2 A Shell is a Shell is a Shell
+.Rd 7
+.LP
+In normal operation, the Bourne Shell (better known as
+.CW sh '') ``
+is used to execute the commands to re-create targets. PMake also allows you
+to specify a different shell for it to use when executing these
+commands. There are several things PMake must know about the shell you
+wish to use. These things are specified as the sources for the
+.CW .SHELL
+.Ix 0 ref .SHELL
+.Ix 0 ref target .SHELL
+target by keyword, as follows:
+.IP "\fBpath=\fP\fIpath\fP"
+PMake needs to know where the shell actually resides, so it can
+execute it. If you specify this and nothing else, PMake will use the
+last component of the path and look in its table of the shells it
+knows and use the specification it finds, if any. Use this if you just
+want to use a different version of the Bourne or C Shell (yes, PMake knows
+how to use the C Shell too).
+.IP "\fBname=\fP\fIname\fP"
+This is the name by which the shell is to be known. It is a single
+word and, if no other keywords are specified (other than
+.B path ),
+it is the name by which PMake attempts to find a specification for
+it (as mentioned above). You can use this if you would just rather use
+the C Shell than the Bourne Shell
+.CW ".SHELL: name=csh" '' (``
+will do it).
+.IP "\fBquiet=\fP\fIecho-off command\fP"
+As mentioned before, PMake actually controls whether commands are
+printed by introducing commands into the shell's input stream. This
+keyword, and the next two, control what those commands are. The
+.B quiet
+keyword is the command used to turn echoing off. Once it is turned
+off, echoing is expected to remain off until the echo-on command is given.
+.IP "\fBecho=\fP\fIecho-on command\fP"
+The command PMake should give to turn echoing back on again.
+.IP "\fBfilter=\fP\fIprinted echo-off command\fP"
+Many shells will echo the echo-off command when it is given. This
+keyword tells PMake in what format the shell actually prints the
+echo-off command. Wherever PMake sees this string in the shell's
+output, it will delete it and any following whitespace, up to and
+including the next newline. See the example at the end of this section
+for more details.
+.IP "\fBechoFlag=\fP\fIflag to turn echoing on\fP"
+Unless a target has been marked
+.CW .SILENT ,
+PMake wants to start the shell running with echoing on. To do this, it
+passes this flag to the shell as one of its arguments. If either this
+or the next flag begins with a `\-', the flags will be passed to the
+shell as separate arguments. Otherwise, the two will be concatenated
+(if they are used at the same time, of course).
+.IP "\fBerrFlag=\fP\fIflag to turn error checking on\fP"
+Likewise, unless a target is marked
+.CW .IGNORE ,
+PMake wishes error-checking to be on from the very start. To this end,
+it will pass this flag to the shell as an argument. The same rules for
+an initial `\-' apply as for the
+.B echoFlag .
+.IP "\fBcheck=\fP\fIcommand to turn error checking on\fP"
+Just as for echo-control, error-control is achieved by inserting
+commands into the shell's input stream. This is the command to make
+the shell check for errors. It also serves another purpose if the
+shell doesn't have error-control as commands, but I'll get into that
+in a minute. Again, once error checking has been turned on, it is
+expected to remain on until it is turned off again.
+.IP "\fBignore=\fP\fIcommand to turn error checking off\fP"
+This is the command PMake uses to turn error checking off. It has
+another use if the shell doesn't do error-control, but I'll tell you
+about that.\|.\|.\|now.
+.IP "\fBhasErrCtl=\fP\fIyes or no\fP"
+This takes a value that is either
+.B yes
+or
+.B no .
+Now you might think that the existence of the
+.B check
+and
+.B ignore
+keywords would be enough to tell PMake if the shell can do
+error-control, but you'd be wrong. If
+.B hasErrCtl
+is
+.B yes ,
+PMake uses the check and ignore commands in a straight-forward manner.
+If this is
+.B no ,
+however, their use is rather different. In this case, the check
+command is used as a template, in which the string
+.B %s
+is replaced by the command that's about to be executed, to produce a
+command for the shell that will echo the command to be executed. The
+ignore command is also used as a template, again with
+.B %s
+replaced by the command to be executed, to produce a command that will
+execute the command to be executed and ignore any error it returns.
+When these strings are used as templates, you must provide newline(s)
+.CW \en '') (``
+in the appropriate place(s).
+.LP
+The strings that follow these keywords may be enclosed in single or
+double quotes (the quotes will be stripped off) and may contain the
+usual C backslash-characters (\en is newline, \er is return, \eb is
+backspace, \e' escapes a single-quote inside single-quotes, \e"
+escapes a double-quote inside double-quotes). Now for an example.
+.LP
+This is actually the contents of the
+.CW <shx.mk>
+system makefile, and causes PMake to use the Bourne Shell in such a
+way that each command is printed as it is executed. That is, if more
+than one command is given on a line, each will be printed separately.
+Similarly, each time the body of a loop is executed, the commands
+within that loop will be printed, etc. The specification runs like
+this:
+.DS
+#
+# This is a shell specification to have the Bourne shell echo
+# the commands just before executing them, rather than when it reads
+# them. Useful if you want to see how variables are being expanded, etc.
+#
+\&.SHELL : path=/bin/sh \e
+ quiet="set -" \e
+ echo="set -x" \e
+ filter="+ set - " \e
+ echoFlag=x \e
+ errFlag=e \e
+ hasErrCtl=yes \e
+ check="set -e" \e
+ ignore="set +e"
+.DE
+.LP
+It tells PMake the following:
+.Bp
+The shell is located in the file
+.CW /bin/sh .
+It need not tell PMake that the name of the shell is
+.CW sh
+as PMake can figure that out for itself (it's the last component of
+the path).
+.Bp
+The command to stop echoing is
+.CW "set -" .
+.Bp
+The command to start echoing is
+.CW "set -x" .
+.Bp
+When the echo off command is executed, the shell will print
+.CW "+ set - "
+(The `+' comes from using the
+.CW \-x
+flag (rather than the
+.CW \-v
+flag PMake usually uses)). PMake will remove all occurrences of this
+string from the output, so you don't notice extra commands you didn't
+put there.
+.Bp
+The flag the Bourne Shell will take to start echoing in this way is
+the
+.CW \-x
+flag. The Bourne Shell will only take its flag arguments concatenated
+as its first argument, so neither this nor the
+.B errFlag
+specification begins with a \-.
+.Bp
+The flag to use to turn error-checking on from the start is
+.CW \-e .
+.Bp
+The shell can turn error-checking on and off, and the commands to do
+so are
+.CW "set +e"
+and
+.CW "set -e" ,
+respectively.
+.LP
+I should note that this specification is for Bourne Shells that are
+not part of Berkeley
+.UX ,
+as shells from Berkeley don't do error control. You can get a similar
+effect, however, by changing the last three lines to be:
+.DS
+ hasErrCtl=no \e
+ check="echo \e"+ %s\e"\en" \e
+ ignore="sh -c '%s || exit 0\en"
+.DE
+.LP
+This will cause PMake to execute the two commands
+.DS
+echo "+ \fIcmd\fP"
+sh -c '\fIcmd\fP || true'
+.DE
+for each command for which errors are to be ignored. (In case you are
+wondering, the thing for
+.CW ignore
+tells the shell to execute another shell without error checking on and
+always exit 0, since the
+.B ||
+causes the
+.CW "exit 0"
+to be executed only if the first command exited non-zero, and if the
+first command exited zero, the shell will also exit zero, since that's
+the last command it executed).
+.xH 2 Compatibility
+.Ix 0 ref compatibility
+.LP
+There are three (well, 3 \(12) levels of backwards-compatibility built
+into PMake. Most makefiles will need none at all. Some may need a
+little bit of work to operate correctly when run in parallel. Each
+level encompasses the previous levels (e.g.
+.B \-B
+(one shell per command) implies
+.B \-V )
+The three levels are described in the following three sections.
+.xH 3 DEFCON 3 \*- Variable Expansion
+.Ix 0 ref compatibility
+.LP
+As noted before, PMake will not expand a variable unless it knows of a
+value for it. This can cause problems for makefiles that expect to
+leave variables undefined except in special circumstances (e.g. if
+more flags need to be passed to the C compiler or the output from a
+text processor should be sent to a different printer). If the
+variables are enclosed in curly braces
+.CW ${PRINTER} ''), (``
+the shell will let them pass. If they are enclosed in parentheses,
+however, the shell will declare a syntax error and the make will come
+to a grinding halt.
+.LP
+You have two choices: change the makefile to define the variables
+(their values can be overridden on the command line, since that's
+where they would have been set if you used Make, anyway) or always give the
+.B \-V
+flag (this can be done with the
+.CW .MAKEFLAGS
+target, if you want).
+.xH 3 DEFCON 2 \*- The Number of the Beast
+.Ix 0 ref compatibility
+.LP
+Then there are the makefiles that expect certain commands, such as
+changing to a different directory, to not affect other commands in a
+target's creation script. You can solve this is either by going
+back to executing one shell per command (which is what the
+.B \-B
+flag forces PMake to do), which slows the process down a good bit and
+requires you to use semicolons and escaped newlines for shell constructs, or
+by changing the makefile to execute the offending command(s) in a subshell
+(by placing the line inside parentheses), like so:
+.DS
+install :: .MAKE
+ (cd src; $(.PMAKE) install)
+ (cd lib; $(.PMAKE) install)
+ (cd man; $(.PMAKE) install)
+.DE
+.Ix 0 ref operator double-colon
+.Ix 0 ref variable global .PMAKE
+.Ix 0 ref .PMAKE
+.Ix 0 ref .MAKE
+.Ix 0 ref attribute .MAKE
+This will always execute the three makes (even if the
+.B \-n
+flag was given) because of the combination of the ``::'' operator and
+the
+.CW .MAKE
+attribute. Each command will change to the proper directory to perform
+the install, leaving the main shell in the directory in which it started.
+.xH 3 "DEFCON 1 \*- Imitation is the Not the Highest Form of Flattery"
+.Ix 0 ref compatibility
+.LP
+The final category of makefile is the one where every command requires
+input, the dependencies are incompletely specified, or you simply
+cannot create more than one target at a time, as mentioned earlier. In
+addition, you may not have the time or desire to upgrade the makefile
+to run smoothly with PMake. If you are the conservative sort, this is
+the compatibility mode for you. It is entered either by giving PMake
+the
+.B \-M
+flag (for Make), or by executing PMake as
+.CW make .'' ``
+In either case, PMake performs things exactly like Make (while still
+supporting most of the nice new features PMake provides). This
+includes:
+.IP \(bu 2
+No parallel execution.
+.IP \(bu 2
+Targets are made in the exact order specified by the makefile. The
+sources for each target are made in strict left-to-right order, etc.
+.IP \(bu 2
+A single Bourne shell is used to execute each command, thus the
+shell's
+.CW $$
+variable is useless, changing directories doesn't work across command
+lines, etc.
+.IP \(bu 2
+If no special characters exist in a command line, PMake will break the
+command into words itself and execute the command directly, without
+executing a shell first. The characters that cause PMake to execute a
+shell are:
+.CW # ,
+.CW = ,
+.CW | ,
+.CW ^ ,
+.CW ( ,
+.CW ) ,
+.CW { ,
+.CW } ,
+.CW ; ,
+.CW & ,
+.CW < ,
+.CW > ,
+.CW * ,
+.CW ? ,
+.CW [ ,
+.CW ] ,
+.CW : ,
+.CW $ ,
+.CW ` ,
+and
+.CW \e .
+You should notice that these are all the characters that are given
+special meaning by the shell (except
+.CW '
+and
+.CW " ,
+which PMake deals with all by its lonesome).
+.IP \(bu 2
+The use of the null suffix is turned off.
+.Ix 0 ref "null suffix"
+.Ix 0 ref suffix null
+.xH 2 The Way Things Work
+.LP
+When PMake reads the makefile, it parses sources and targets into
+nodes in a graph. The graph is directed only in the sense that PMake
+knows which way is up. Each node contains not only links to all its
+parents and children (the nodes that depend on it and those on which
+it depends, respectively), but also a count of the number of its
+children that have already been processed.
+.LP
+The most important thing to know about how PMake uses this graph is
+that the traversal is breadth-first and occurs in two passes.
+.LP
+After PMake has parsed the makefile, it begins with the nodes the user
+has told it to make (either on the command line, or via a
+.CW .MAIN
+target, or by the target being the first in the file not labeled with
+the
+.CW .NOTMAIN
+attribute) placed in a queue. It continues to take the node off the
+front of the queue, mark it as something that needs to be made, pass
+the node to
+.CW Suff_FindDeps
+(mentioned earlier) to find any implicit sources for the node, and
+place all the node's children that have yet to be marked at the end of
+the queue. If any of the children is a
+.CW .USE
+rule, its attributes are applied to the parent, then its commands are
+appended to the parent's list of commands and its children are linked
+to its parent. The parent's unmade children counter is then decremented
+(since the
+.CW .USE
+node has been processed). You will note that this allows a
+.CW .USE
+node to have children that are
+.CW .USE
+nodes and the rules will be applied in sequence.
+If the node has no children, it is placed at the end of
+another queue to be examined in the second pass. This process
+continues until the first queue is empty.
+.LP
+At this point, all the leaves of the graph are in the examination
+queue. PMake removes the node at the head of the queue and sees if it
+is out-of-date. If it is, it is passed to a function that will execute
+the commands for the node asynchronously. When the commands have
+completed, all the node's parents have their unmade children counter
+decremented and, if the counter is then 0, they are placed on the
+examination queue. Likewise, if the node is up-to-date. Only those
+parents that were marked on the downward pass are processed in this
+way. Thus PMake traverses the graph back up to the nodes the user
+instructed it to create. When the examination queue is empty and no
+shells are running to create a target, PMake is finished.
+.LP
+Once all targets have been processed, PMake executes the commands
+attached to the
+.CW .END
+target, either explicitly or through the use of an ellipsis in a shell
+script. If there were no errors during the entire process but there
+are still some targets unmade (PMake keeps a running count of how many
+targets are left to be made), there is a cycle in the graph. PMake does
+a depth-first traversal of the graph to find all the targets that
+weren't made and prints them out one by one.
+.xH 1 Answers to Exercises
+.IP (3.1)
+This is something of a trick question, for which I apologize. The
+trick comes from the UNIX definition of a suffix, which PMake doesn't
+necessarily share. You will have noticed that all the suffixes used in
+this tutorial (and in UNIX in general) begin with a period
+.CW .ms , (
+.CW .c ,
+etc.). Now, PMake's idea of a suffix is more like English's: it's the
+characters at the end of a word. With this in mind, one possible
+.Ix 0 def suffix
+solution to this problem goes as follows:
+.DS I
+\&.SUFFIXES : ec.exe .exe ec.obj .obj .asm
+ec.objec.exe .obj.exe :
+ link -o $(.TARGET) $(.IMPSRC)
+\&.asmec.obj :
+ asm -o $(.TARGET) -DDO_ERROR_CHECKING $(.IMPSRC)
+\&.asm.obj :
+ asm -o $(.TARGET) $(.IMPSRC)
+.DE
+.IP (3.2)
+The trick to this one lies in the ``:='' variable-assignment operator
+and the ``:S'' variable-expansion modifier.
+.Ix 0 ref variable assignment expanded
+.Ix 0 ref variable expansion modified
+.Ix 0 ref modifier substitute
+.Ix 0 ref :S
+.Ix 0 ref :=
+Basically what you want is to take the pointer variable, so to speak,
+and transform it into an invocation of the variable at which it
+points. You might try something like
+.DS I
+$(PTR:S/^/\e$(/:S/$/))
+.DE
+which places
+.CW $( '' ``
+at the front of the variable name and
+.CW ) '' ``
+at the end, thus transforming
+.CW VAR ,'' ``
+for example, into
+.CW $(VAR) ,'' ``
+which is just what we want. Unfortunately (as you know if you've tried
+it), since, as it says in the hint, PMake does no further substitution
+on the result of a modified expansion, that's \fIall\fP you get. The
+solution is to make use of ``:='' to place that string into yet
+another variable, then invoke the other variable directly:
+.DS I
+*PTR := $(PTR:S/^/\e$(/:S/$/)/)
+.DE
+You can then use
+.CW $(*PTR) '' ``
+to your heart's content.
+.de Gp
+.XP
+\&\fB\\$1:\fP
+..
+.xH 1 Glossary of Jargon
+.Gp "attribute"
+A property given to a target that causes PMake to treat it differently.
+.Gp "command script"
+The lines immediately following a dependency line that specify
+commands to execute to create each of the targets on the dependency
+line. Each line in the command script must begin with a tab.
+.Gp "command-line variable"
+A variable defined in an argument when PMake is first executed.
+Overrides all assignments to the same variable name in the makefile.
+.Gp "conditional"
+A construct much like that used in C that allows a makefile to be
+configured on the fly based on the local environment, or on what is being
+made by that invocation of PMake.
+.Gp "creation script"
+Commands used to create a target. See ``command script.''
+.Gp "dependency"
+The relationship between a source and a target. This comes in three
+flavors, as indicated by the operator between the target and the
+source. `:' gives a straight time-wise dependency (if the target is
+older than the source, the target is out-of-date), while `!' provides
+simply an ordering and always considers the target out-of-date. `::'
+is much like `:', save it creates multiple instances of a target each
+of which depends on its own list of sources.
+.Gp "dynamic source"
+This refers to a source that has a local variable invocation in it. It
+allows a single dependency line to specify a different source for each
+target on the line.
+.Gp "global variable"
+Any variable defined in a makefile. Takes precedence over variables
+defined in the environment, but not over command-line or local variables.
+.Gp "input graph"
+What PMake constructs from a makefile. Consists of nodes made of the
+targets in the makefile, and the links between them (the
+dependencies). The links are directed (from source to target) and
+there may not be any cycles (loops) in the graph.
+.Gp "local variable"
+A variable defined by PMake visible only in a target's shell script.
+There are seven local variables, not all of which are defined for
+every target:
+.CW .TARGET ,
+.CW .ALLSRC ,
+.CW .OODATE ,
+.CW .PREFIX ,
+.CW .IMPSRC ,
+.CW .ARCHIVE ,
+and
+.CW .MEMBER .
+.CW .TARGET ,
+.CW .PREFIX ,
+.CW .ARCHIVE ,
+and
+.CW .MEMBER
+may be used on dependency lines to create ``dynamic sources.''
+.Gp "makefile"
+A file that describes how a system is built. If you don't know what it
+is after reading this tutorial.\|.\|.\|.
+.Gp "modifier"
+A letter, following a colon, used to alter how a variable is expanded.
+It has no effect on the variable itself.
+.Gp "operator"
+What separates a source from a target (on a dependency line) and specifies
+the relationship between the two. There are three:
+.CW : ', `
+.CW :: ', `
+and
+.CW ! '. `
+.Gp "search path"
+A list of directories in which a file should be sought. PMake's view
+of the contents of directories in a search path does not change once
+the makefile has been read. A file is sought on a search path only if
+it is exclusively a source.
+.Gp "shell"
+A program to which commands are passed in order to create targets.
+.Gp "source"
+Anything to the right of an operator on a dependency line. Targets on
+the dependency line are usually created from the sources.
+.Gp "special target"
+A target that causes PMake to do special things when it's encountered.
+.Gp "suffix"
+The tail end of a file name. Usually begins with a period,
+.CW .c
+or
+.CW .ms ,
+e.g.
+.Gp "target"
+A word to the left of the operator on a dependency line. More
+generally, any file that PMake might create. A file may be (and often
+is) both a target and a source (what it is depends on how PMake is
+looking at it at the time \*- sort of like the wave/particle duality
+of light, you know).
+.Gp "transformation rule"
+A special construct in a makefile that specifies how to create a file
+of one type from a file of another, as indicated by their suffixes.
+.Gp "variable expansion"
+The process of substituting the value of a variable for a reference to
+it. Expansion may be altered by means of modifiers.
+.Gp "variable"
+A place in which to store text that may be retrieved later. Also used
+to define the local environment. Conditionals exist that test whether
+a variable is defined or not.
+.bp
+.\" Output table of contents last, with an entry for the index, making
+.\" sure to save and restore the last real page number for the index...
+.nr @n \n(PN+1
+.\" We are not generating an index
+.\" .XS \n(@n
+.\" Index
+.\" .XE
+.nr %% \n%
+.PX
+.nr % \n(%%
diff --git a/usr.bin/make/arch.c b/usr.bin/make/arch.c
new file mode 100644
index 0000000..8241434
--- /dev/null
+++ b/usr.bin/make/arch.c
@@ -0,0 +1,1369 @@
+/* $NetBSD: arch.c,v 1.70 2017/04/16 20:49:09 riastradh Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: arch.c,v 1.70 2017/04/16 20:49:09 riastradh Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)arch.c 8.2 (Berkeley) 1/2/94";
+#else
+__RCSID("$NetBSD: arch.c,v 1.70 2017/04/16 20:49:09 riastradh Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * arch.c --
+ * Functions to manipulate libraries, archives and their members.
+ *
+ * Once again, cacheing/hashing comes into play in the manipulation
+ * of archives. The first time an archive is referenced, all of its members'
+ * headers are read and hashed and the archive closed again. All hashed
+ * archives are kept on a list which is searched each time an archive member
+ * is referenced.
+ *
+ * The interface to this module is:
+ * Arch_ParseArchive Given an archive specification, return a list
+ * of GNode's, one for each member in the spec.
+ * FAILURE is returned if the specification is
+ * invalid for some reason.
+ *
+ * Arch_Touch Alter the modification time of the archive
+ * member described by the given node to be
+ * the current time.
+ *
+ * Arch_TouchLib Update the modification time of the library
+ * described by the given node. This is special
+ * because it also updates the modification time
+ * of the library's table of contents.
+ *
+ * Arch_MTime Find the modification time of a member of
+ * an archive *in the archive*. The time is also
+ * placed in the member's GNode. Returns the
+ * modification time.
+ *
+ * Arch_MemTime Find the modification time of a member of
+ * an archive. Called when the member doesn't
+ * already exist. Looks in the archive for the
+ * modification time. Returns the modification
+ * time.
+ *
+ * Arch_FindLib Search for a library along a path. The
+ * library name in the GNode should be in
+ * -l<name> format.
+ *
+ * Arch_LibOODate Special function to decide if a library node
+ * is out-of-date.
+ *
+ * Arch_Init Initialize this module.
+ *
+ * Arch_End Cleanup this module.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/param.h>
+
+#include <ar.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <utime.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "config.h"
+
+#ifdef TARGET_MACHINE
+#undef MAKE_MACHINE
+#define MAKE_MACHINE TARGET_MACHINE
+#endif
+#ifdef TARGET_MACHINE_ARCH
+#undef MAKE_MACHINE_ARCH
+#define MAKE_MACHINE_ARCH TARGET_MACHINE_ARCH
+#endif
+
+static Lst archives; /* Lst of archives we've already examined */
+
+typedef struct Arch {
+ char *name; /* Name of archive */
+ Hash_Table members; /* All the members of the archive described
+ * by <name, struct ar_hdr *> key/value pairs */
+ char *fnametab; /* Extended name table strings */
+ size_t fnamesize; /* Size of the string table */
+} Arch;
+
+static int ArchFindArchive(const void *, const void *);
+#ifdef CLEANUP
+static void ArchFree(void *);
+#endif
+static struct ar_hdr *ArchStatMember(char *, char *, Boolean);
+static FILE *ArchFindMember(char *, char *, struct ar_hdr *, const char *);
+#if defined(__svr4__) || defined(__SVR4) || defined(__ELF__)
+#define SVR4ARCHIVES
+static int ArchSVR4Entry(Arch *, char *, size_t, FILE *);
+#endif
+
+#ifdef CLEANUP
+/*-
+ *-----------------------------------------------------------------------
+ * ArchFree --
+ * Free memory used by an archive
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+ArchFree(void *ap)
+{
+ Arch *a = (Arch *)ap;
+ Hash_Search search;
+ Hash_Entry *entry;
+
+ /* Free memory from hash entries */
+ for (entry = Hash_EnumFirst(&a->members, &search);
+ entry != NULL;
+ entry = Hash_EnumNext(&search))
+ free(Hash_GetValue(entry));
+
+ free(a->name);
+ free(a->fnametab);
+ Hash_DeleteTable(&a->members);
+ free(a);
+}
+#endif
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_ParseArchive --
+ * Parse the archive specification in the given line and find/create
+ * the nodes for the specified archive members, placing their nodes
+ * on the given list.
+ *
+ * Input:
+ * linePtr Pointer to start of specification
+ * nodeLst Lst on which to place the nodes
+ * ctxt Context in which to expand variables
+ *
+ * Results:
+ * SUCCESS if it was a valid specification. The linePtr is updated
+ * to point to the first non-space after the archive spec. The
+ * nodes for the members are placed on the given list.
+ *
+ * Side Effects:
+ * Some nodes may be created. The given list is extended.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Arch_ParseArchive(char **linePtr, Lst nodeLst, GNode *ctxt)
+{
+ char *cp; /* Pointer into line */
+ GNode *gn; /* New node */
+ char *libName; /* Library-part of specification */
+ char *memName; /* Member-part of specification */
+ char *nameBuf; /* temporary place for node name */
+ char saveChar; /* Ending delimiter of member-name */
+ Boolean subLibName; /* TRUE if libName should have/had
+ * variable substitution performed on it */
+
+ libName = *linePtr;
+
+ subLibName = FALSE;
+
+ for (cp = libName; *cp != '(' && *cp != '\0'; cp++) {
+ if (*cp == '$') {
+ /*
+ * Variable spec, so call the Var module to parse the puppy
+ * so we can safely advance beyond it...
+ */
+ int length;
+ void *freeIt;
+ char *result;
+
+ result = Var_Parse(cp, ctxt, VARF_UNDEFERR|VARF_WANTRES,
+ &length, &freeIt);
+ free(freeIt);
+
+ if (result == var_Error) {
+ return(FAILURE);
+ } else {
+ subLibName = TRUE;
+ }
+
+ cp += length-1;
+ }
+ }
+
+ *cp++ = '\0';
+ if (subLibName) {
+ libName = Var_Subst(NULL, libName, ctxt, VARF_UNDEFERR|VARF_WANTRES);
+ }
+
+
+ for (;;) {
+ /*
+ * First skip to the start of the member's name, mark that
+ * place and skip to the end of it (either white-space or
+ * a close paren).
+ */
+ Boolean doSubst = FALSE; /* TRUE if need to substitute in memName */
+
+ while (*cp != '\0' && *cp != ')' && isspace ((unsigned char)*cp)) {
+ cp++;
+ }
+ memName = cp;
+ while (*cp != '\0' && *cp != ')' && !isspace ((unsigned char)*cp)) {
+ if (*cp == '$') {
+ /*
+ * Variable spec, so call the Var module to parse the puppy
+ * so we can safely advance beyond it...
+ */
+ int length;
+ void *freeIt;
+ char *result;
+
+ result = Var_Parse(cp, ctxt, VARF_UNDEFERR|VARF_WANTRES,
+ &length, &freeIt);
+ free(freeIt);
+
+ if (result == var_Error) {
+ return(FAILURE);
+ } else {
+ doSubst = TRUE;
+ }
+
+ cp += length;
+ } else {
+ cp++;
+ }
+ }
+
+ /*
+ * If the specification ends without a closing parenthesis,
+ * chances are there's something wrong (like a missing backslash),
+ * so it's better to return failure than allow such things to happen
+ */
+ if (*cp == '\0') {
+ printf("No closing parenthesis in archive specification\n");
+ return (FAILURE);
+ }
+
+ /*
+ * If we didn't move anywhere, we must be done
+ */
+ if (cp == memName) {
+ break;
+ }
+
+ saveChar = *cp;
+ *cp = '\0';
+
+ /*
+ * XXX: This should be taken care of intelligently by
+ * SuffExpandChildren, both for the archive and the member portions.
+ */
+ /*
+ * If member contains variables, try and substitute for them.
+ * This will slow down archive specs with dynamic sources, of course,
+ * since we'll be (non-)substituting them three times, but them's
+ * the breaks -- we need to do this since SuffExpandChildren calls
+ * us, otherwise we could assume the thing would be taken care of
+ * later.
+ */
+ if (doSubst) {
+ char *buf;
+ char *sacrifice;
+ char *oldMemName = memName;
+ size_t sz;
+
+ memName = Var_Subst(NULL, memName, ctxt,
+ VARF_UNDEFERR|VARF_WANTRES);
+
+ /*
+ * Now form an archive spec and recurse to deal with nested
+ * variables and multi-word variable values.... The results
+ * are just placed at the end of the nodeLst we're returning.
+ */
+ sz = strlen(memName)+strlen(libName)+3;
+ buf = sacrifice = bmake_malloc(sz);
+
+ snprintf(buf, sz, "%s(%s)", libName, memName);
+
+ if (strchr(memName, '$') && strcmp(memName, oldMemName) == 0) {
+ /*
+ * Must contain dynamic sources, so we can't deal with it now.
+ * Just create an ARCHV node for the thing and let
+ * SuffExpandChildren handle it...
+ */
+ gn = Targ_FindNode(buf, TARG_CREATE);
+
+ if (gn == NULL) {
+ free(buf);
+ return(FAILURE);
+ } else {
+ gn->type |= OP_ARCHV;
+ (void)Lst_AtEnd(nodeLst, gn);
+ }
+ } else if (Arch_ParseArchive(&sacrifice, nodeLst, ctxt)!=SUCCESS) {
+ /*
+ * Error in nested call -- free buffer and return FAILURE
+ * ourselves.
+ */
+ free(buf);
+ return(FAILURE);
+ }
+ /*
+ * Free buffer and continue with our work.
+ */
+ free(buf);
+ } else if (Dir_HasWildcards(memName)) {
+ Lst members = Lst_Init(FALSE);
+ char *member;
+ size_t sz = MAXPATHLEN, nsz;
+ nameBuf = bmake_malloc(sz);
+
+ Dir_Expand(memName, dirSearchPath, members);
+ while (!Lst_IsEmpty(members)) {
+ member = (char *)Lst_DeQueue(members);
+ nsz = strlen(libName) + strlen(member) + 3;
+ if (sz > nsz)
+ nameBuf = bmake_realloc(nameBuf, sz = nsz * 2);
+
+ snprintf(nameBuf, sz, "%s(%s)", libName, member);
+ free(member);
+ gn = Targ_FindNode(nameBuf, TARG_CREATE);
+ if (gn == NULL) {
+ free(nameBuf);
+ return (FAILURE);
+ } else {
+ /*
+ * We've found the node, but have to make sure the rest of
+ * the world knows it's an archive member, without having
+ * to constantly check for parentheses, so we type the
+ * thing with the OP_ARCHV bit before we place it on the
+ * end of the provided list.
+ */
+ gn->type |= OP_ARCHV;
+ (void)Lst_AtEnd(nodeLst, gn);
+ }
+ }
+ Lst_Destroy(members, NULL);
+ free(nameBuf);
+ } else {
+ size_t sz = strlen(libName) + strlen(memName) + 3;
+ nameBuf = bmake_malloc(sz);
+ snprintf(nameBuf, sz, "%s(%s)", libName, memName);
+ gn = Targ_FindNode(nameBuf, TARG_CREATE);
+ free(nameBuf);
+ if (gn == NULL) {
+ return (FAILURE);
+ } else {
+ /*
+ * We've found the node, but have to make sure the rest of the
+ * world knows it's an archive member, without having to
+ * constantly check for parentheses, so we type the thing with
+ * the OP_ARCHV bit before we place it on the end of the
+ * provided list.
+ */
+ gn->type |= OP_ARCHV;
+ (void)Lst_AtEnd(nodeLst, gn);
+ }
+ }
+ if (doSubst) {
+ free(memName);
+ }
+
+ *cp = saveChar;
+ }
+
+ /*
+ * If substituted libName, free it now, since we need it no longer.
+ */
+ if (subLibName) {
+ free(libName);
+ }
+
+ /*
+ * We promised the pointer would be set up at the next non-space, so
+ * we must advance cp there before setting *linePtr... (note that on
+ * entrance to the loop, cp is guaranteed to point at a ')')
+ */
+ do {
+ cp++;
+ } while (*cp != '\0' && isspace ((unsigned char)*cp));
+
+ *linePtr = cp;
+ return (SUCCESS);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ArchFindArchive --
+ * See if the given archive is the one we are looking for. Called
+ * From ArchStatMember and ArchFindMember via Lst_Find.
+ *
+ * Input:
+ * ar Current list element
+ * archName Name we want
+ *
+ * Results:
+ * 0 if it is, non-zero if it isn't.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ArchFindArchive(const void *ar, const void *archName)
+{
+ return (strcmp(archName, ((const Arch *)ar)->name));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ArchStatMember --
+ * Locate a member of an archive, given the path of the archive and
+ * the path of the desired member.
+ *
+ * Input:
+ * archive Path to the archive
+ * member Name of member. If it is a path, only the last
+ * component is used.
+ * hash TRUE if archive should be hashed if not already so.
+ *
+ * Results:
+ * A pointer to the current struct ar_hdr structure for the member. Note
+ * That no position is returned, so this is not useful for touching
+ * archive members. This is mostly because we have no assurances that
+ * The archive will remain constant after we read all the headers, so
+ * there's not much point in remembering the position...
+ *
+ * Side Effects:
+ *
+ *-----------------------------------------------------------------------
+ */
+static struct ar_hdr *
+ArchStatMember(char *archive, char *member, Boolean hash)
+{
+#define AR_MAX_NAME_LEN (sizeof(arh.ar_name)-1)
+ FILE * arch; /* Stream to archive */
+ int size; /* Size of archive member */
+ char *cp; /* Useful character pointer */
+ char magic[SARMAG];
+ LstNode ln; /* Lst member containing archive descriptor */
+ Arch *ar; /* Archive descriptor */
+ Hash_Entry *he; /* Entry containing member's description */
+ struct ar_hdr arh; /* archive-member header for reading archive */
+ char memName[MAXPATHLEN+1];
+ /* Current member name while hashing. */
+
+ /*
+ * Because of space constraints and similar things, files are archived
+ * using their final path components, not the entire thing, so we need
+ * to point 'member' to the final component, if there is one, to make
+ * the comparisons easier...
+ */
+ cp = strrchr(member, '/');
+ if (cp != NULL) {
+ member = cp + 1;
+ }
+
+ ln = Lst_Find(archives, archive, ArchFindArchive);
+ if (ln != NULL) {
+ ar = (Arch *)Lst_Datum(ln);
+
+ he = Hash_FindEntry(&ar->members, member);
+
+ if (he != NULL) {
+ return ((struct ar_hdr *)Hash_GetValue(he));
+ } else {
+ /* Try truncated name */
+ char copy[AR_MAX_NAME_LEN+1];
+ size_t len = strlen(member);
+
+ if (len > AR_MAX_NAME_LEN) {
+ len = AR_MAX_NAME_LEN;
+ strncpy(copy, member, AR_MAX_NAME_LEN);
+ copy[AR_MAX_NAME_LEN] = '\0';
+ }
+ if ((he = Hash_FindEntry(&ar->members, copy)) != NULL)
+ return ((struct ar_hdr *)Hash_GetValue(he));
+ return NULL;
+ }
+ }
+
+ if (!hash) {
+ /*
+ * Caller doesn't want the thing hashed, just use ArchFindMember
+ * to read the header for the member out and close down the stream
+ * again. Since the archive is not to be hashed, we assume there's
+ * no need to allocate extra room for the header we're returning,
+ * so just declare it static.
+ */
+ static struct ar_hdr sarh;
+
+ arch = ArchFindMember(archive, member, &sarh, "r");
+
+ if (arch == NULL) {
+ return NULL;
+ } else {
+ fclose(arch);
+ return (&sarh);
+ }
+ }
+
+ /*
+ * We don't have this archive on the list yet, so we want to find out
+ * everything that's in it and cache it so we can get at it quickly.
+ */
+ arch = fopen(archive, "r");
+ if (arch == NULL) {
+ return NULL;
+ }
+
+ /*
+ * We use the ARMAG string to make sure this is an archive we
+ * can handle...
+ */
+ if ((fread(magic, SARMAG, 1, arch) != 1) ||
+ (strncmp(magic, ARMAG, SARMAG) != 0)) {
+ fclose(arch);
+ return NULL;
+ }
+
+ ar = bmake_malloc(sizeof(Arch));
+ ar->name = bmake_strdup(archive);
+ ar->fnametab = NULL;
+ ar->fnamesize = 0;
+ Hash_InitTable(&ar->members, -1);
+ memName[AR_MAX_NAME_LEN] = '\0';
+
+ while (fread((char *)&arh, sizeof(struct ar_hdr), 1, arch) == 1) {
+ if (strncmp( arh.ar_fmag, ARFMAG, sizeof(arh.ar_fmag)) != 0) {
+ /*
+ * The header is bogus, so the archive is bad
+ * and there's no way we can recover...
+ */
+ goto badarch;
+ } else {
+ /*
+ * We need to advance the stream's pointer to the start of the
+ * next header. Files are padded with newlines to an even-byte
+ * boundary, so we need to extract the size of the file from the
+ * 'size' field of the header and round it up during the seek.
+ */
+ arh.ar_size[sizeof(arh.ar_size)-1] = '\0';
+ size = (int)strtol(arh.ar_size, NULL, 10);
+
+ (void)strncpy(memName, arh.ar_name, sizeof(arh.ar_name));
+ for (cp = &memName[AR_MAX_NAME_LEN]; *cp == ' '; cp--) {
+ continue;
+ }
+ cp[1] = '\0';
+
+#ifdef SVR4ARCHIVES
+ /*
+ * svr4 names are slash terminated. Also svr4 extended AR format.
+ */
+ if (memName[0] == '/') {
+ /*
+ * svr4 magic mode; handle it
+ */
+ switch (ArchSVR4Entry(ar, memName, size, arch)) {
+ case -1: /* Invalid data */
+ goto badarch;
+ case 0: /* List of files entry */
+ continue;
+ default: /* Got the entry */
+ break;
+ }
+ }
+ else {
+ if (cp[0] == '/')
+ cp[0] = '\0';
+ }
+#endif
+
+#ifdef AR_EFMT1
+ /*
+ * BSD 4.4 extended AR format: #1/<namelen>, with name as the
+ * first <namelen> bytes of the file
+ */
+ if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 &&
+ isdigit((unsigned char)memName[sizeof(AR_EFMT1) - 1])) {
+
+ unsigned int elen = atoi(&memName[sizeof(AR_EFMT1)-1]);
+
+ if (elen > MAXPATHLEN)
+ goto badarch;
+ if (fread(memName, elen, 1, arch) != 1)
+ goto badarch;
+ memName[elen] = '\0';
+ if (fseek(arch, -elen, SEEK_CUR) != 0)
+ goto badarch;
+ if (DEBUG(ARCH) || DEBUG(MAKE)) {
+ fprintf(debug_file, "ArchStat: Extended format entry for %s\n", memName);
+ }
+ }
+#endif
+
+ he = Hash_CreateEntry(&ar->members, memName, NULL);
+ Hash_SetValue(he, bmake_malloc(sizeof(struct ar_hdr)));
+ memcpy(Hash_GetValue(he), &arh, sizeof(struct ar_hdr));
+ }
+ if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0)
+ goto badarch;
+ }
+
+ fclose(arch);
+
+ (void)Lst_AtEnd(archives, ar);
+
+ /*
+ * Now that the archive has been read and cached, we can look into
+ * the hash table to find the desired member's header.
+ */
+ he = Hash_FindEntry(&ar->members, member);
+
+ if (he != NULL) {
+ return ((struct ar_hdr *)Hash_GetValue(he));
+ } else {
+ return NULL;
+ }
+
+badarch:
+ fclose(arch);
+ Hash_DeleteTable(&ar->members);
+ free(ar->fnametab);
+ free(ar);
+ return NULL;
+}
+
+#ifdef SVR4ARCHIVES
+/*-
+ *-----------------------------------------------------------------------
+ * ArchSVR4Entry --
+ * Parse an SVR4 style entry that begins with a slash.
+ * If it is "//", then load the table of filenames
+ * If it is "/<offset>", then try to substitute the long file name
+ * from offset of a table previously read.
+ *
+ * Results:
+ * -1: Bad data in archive
+ * 0: A table was loaded from the file
+ * 1: Name was successfully substituted from table
+ * 2: Name was not successfully substituted from table
+ *
+ * Side Effects:
+ * If a table is read, the file pointer is moved to the next archive
+ * member
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch)
+{
+#define ARLONGNAMES1 "//"
+#define ARLONGNAMES2 "/ARFILENAMES"
+ size_t entry;
+ char *ptr, *eptr;
+
+ if (strncmp(name, ARLONGNAMES1, sizeof(ARLONGNAMES1) - 1) == 0 ||
+ strncmp(name, ARLONGNAMES2, sizeof(ARLONGNAMES2) - 1) == 0) {
+
+ if (ar->fnametab != NULL) {
+ if (DEBUG(ARCH)) {
+ fprintf(debug_file, "Attempted to redefine an SVR4 name table\n");
+ }
+ return -1;
+ }
+
+ /*
+ * This is a table of archive names, so we build one for
+ * ourselves
+ */
+ ar->fnametab = bmake_malloc(size);
+ ar->fnamesize = size;
+
+ if (fread(ar->fnametab, size, 1, arch) != 1) {
+ if (DEBUG(ARCH)) {
+ fprintf(debug_file, "Reading an SVR4 name table failed\n");
+ }
+ return -1;
+ }
+ eptr = ar->fnametab + size;
+ for (entry = 0, ptr = ar->fnametab; ptr < eptr; ptr++)
+ switch (*ptr) {
+ case '/':
+ entry++;
+ *ptr = '\0';
+ break;
+
+ case '\n':
+ break;
+
+ default:
+ break;
+ }
+ if (DEBUG(ARCH)) {
+ fprintf(debug_file, "Found svr4 archive name table with %lu entries\n",
+ (unsigned long)entry);
+ }
+ return 0;
+ }
+
+ if (name[1] == ' ' || name[1] == '\0')
+ return 2;
+
+ entry = (size_t)strtol(&name[1], &eptr, 0);
+ if ((*eptr != ' ' && *eptr != '\0') || eptr == &name[1]) {
+ if (DEBUG(ARCH)) {
+ fprintf(debug_file, "Could not parse SVR4 name %s\n", name);
+ }
+ return 2;
+ }
+ if (entry >= ar->fnamesize) {
+ if (DEBUG(ARCH)) {
+ fprintf(debug_file, "SVR4 entry offset %s is greater than %lu\n",
+ name, (unsigned long)ar->fnamesize);
+ }
+ return 2;
+ }
+
+ if (DEBUG(ARCH)) {
+ fprintf(debug_file, "Replaced %s with %s\n", name, &ar->fnametab[entry]);
+ }
+
+ (void)strncpy(name, &ar->fnametab[entry], MAXPATHLEN);
+ name[MAXPATHLEN] = '\0';
+ return 1;
+}
+#endif
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * ArchFindMember --
+ * Locate a member of an archive, given the path of the archive and
+ * the path of the desired member. If the archive is to be modified,
+ * the mode should be "r+", if not, it should be "r".
+ *
+ * Input:
+ * archive Path to the archive
+ * member Name of member. If it is a path, only the last
+ * component is used.
+ * arhPtr Pointer to header structure to be filled in
+ * mode The mode for opening the stream
+ *
+ * Results:
+ * An FILE *, opened for reading and writing, positioned at the
+ * start of the member's struct ar_hdr, or NULL if the member was
+ * nonexistent. The current struct ar_hdr for member.
+ *
+ * Side Effects:
+ * The passed struct ar_hdr structure is filled in.
+ *
+ *-----------------------------------------------------------------------
+ */
+static FILE *
+ArchFindMember(char *archive, char *member, struct ar_hdr *arhPtr,
+ const char *mode)
+{
+ FILE * arch; /* Stream to archive */
+ int size; /* Size of archive member */
+ char *cp; /* Useful character pointer */
+ char magic[SARMAG];
+ size_t len, tlen;
+
+ arch = fopen(archive, mode);
+ if (arch == NULL) {
+ return NULL;
+ }
+
+ /*
+ * We use the ARMAG string to make sure this is an archive we
+ * can handle...
+ */
+ if ((fread(magic, SARMAG, 1, arch) != 1) ||
+ (strncmp(magic, ARMAG, SARMAG) != 0)) {
+ fclose(arch);
+ return NULL;
+ }
+
+ /*
+ * Because of space constraints and similar things, files are archived
+ * using their final path components, not the entire thing, so we need
+ * to point 'member' to the final component, if there is one, to make
+ * the comparisons easier...
+ */
+ cp = strrchr(member, '/');
+ if (cp != NULL) {
+ member = cp + 1;
+ }
+ len = tlen = strlen(member);
+ if (len > sizeof(arhPtr->ar_name)) {
+ tlen = sizeof(arhPtr->ar_name);
+ }
+
+ while (fread((char *)arhPtr, sizeof(struct ar_hdr), 1, arch) == 1) {
+ if (strncmp(arhPtr->ar_fmag, ARFMAG, sizeof(arhPtr->ar_fmag) ) != 0) {
+ /*
+ * The header is bogus, so the archive is bad
+ * and there's no way we can recover...
+ */
+ fclose(arch);
+ return NULL;
+ } else if (strncmp(member, arhPtr->ar_name, tlen) == 0) {
+ /*
+ * If the member's name doesn't take up the entire 'name' field,
+ * we have to be careful of matching prefixes. Names are space-
+ * padded to the right, so if the character in 'name' at the end
+ * of the matched string is anything but a space, this isn't the
+ * member we sought.
+ */
+ if (tlen != sizeof(arhPtr->ar_name) && arhPtr->ar_name[tlen] != ' '){
+ goto skip;
+ } else {
+ /*
+ * To make life easier, we reposition the file at the start
+ * of the header we just read before we return the stream.
+ * In a more general situation, it might be better to leave
+ * the file at the actual member, rather than its header, but
+ * not here...
+ */
+ if (fseek(arch, -sizeof(struct ar_hdr), SEEK_CUR) != 0) {
+ fclose(arch);
+ return NULL;
+ }
+ return (arch);
+ }
+ } else
+#ifdef AR_EFMT1
+ /*
+ * BSD 4.4 extended AR format: #1/<namelen>, with name as the
+ * first <namelen> bytes of the file
+ */
+ if (strncmp(arhPtr->ar_name, AR_EFMT1,
+ sizeof(AR_EFMT1) - 1) == 0 &&
+ isdigit((unsigned char)arhPtr->ar_name[sizeof(AR_EFMT1) - 1])) {
+
+ unsigned int elen = atoi(&arhPtr->ar_name[sizeof(AR_EFMT1)-1]);
+ char ename[MAXPATHLEN + 1];
+
+ if (elen > MAXPATHLEN) {
+ fclose(arch);
+ return NULL;
+ }
+ if (fread(ename, elen, 1, arch) != 1) {
+ fclose(arch);
+ return NULL;
+ }
+ ename[elen] = '\0';
+ if (DEBUG(ARCH) || DEBUG(MAKE)) {
+ fprintf(debug_file, "ArchFind: Extended format entry for %s\n", ename);
+ }
+ if (strncmp(ename, member, len) == 0) {
+ /* Found as extended name */
+ if (fseek(arch, -sizeof(struct ar_hdr) - elen,
+ SEEK_CUR) != 0) {
+ fclose(arch);
+ return NULL;
+ }
+ return (arch);
+ }
+ if (fseek(arch, -elen, SEEK_CUR) != 0) {
+ fclose(arch);
+ return NULL;
+ }
+ goto skip;
+ } else
+#endif
+ {
+skip:
+ /*
+ * This isn't the member we're after, so we need to advance the
+ * stream's pointer to the start of the next header. Files are
+ * padded with newlines to an even-byte boundary, so we need to
+ * extract the size of the file from the 'size' field of the
+ * header and round it up during the seek.
+ */
+ arhPtr->ar_size[sizeof(arhPtr->ar_size)-1] = '\0';
+ size = (int)strtol(arhPtr->ar_size, NULL, 10);
+ if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) {
+ fclose(arch);
+ return NULL;
+ }
+ }
+ }
+
+ /*
+ * We've looked everywhere, but the member is not to be found. Close the
+ * archive and return NULL -- an error.
+ */
+ fclose(arch);
+ return NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_Touch --
+ * Touch a member of an archive.
+ *
+ * Input:
+ * gn Node of member to touch
+ *
+ * Results:
+ * The 'time' field of the member's header is updated.
+ *
+ * Side Effects:
+ * The modification time of the entire archive is also changed.
+ * For a library, this could necessitate the re-ranlib'ing of the
+ * whole thing.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_Touch(GNode *gn)
+{
+ FILE * arch; /* Stream open to archive, positioned properly */
+ struct ar_hdr arh; /* Current header describing member */
+ char *p1, *p2;
+
+ arch = ArchFindMember(Var_Value(ARCHIVE, gn, &p1),
+ Var_Value(MEMBER, gn, &p2),
+ &arh, "r+");
+
+ free(p1);
+ free(p2);
+
+ snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long) now);
+
+ if (arch != NULL) {
+ (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch);
+ fclose(arch);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_TouchLib --
+ * Given a node which represents a library, touch the thing, making
+ * sure that the table of contents also is touched.
+ *
+ * Input:
+ * gn The node of the library to touch
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Both the modification time of the library and of the RANLIBMAG
+ * member are set to 'now'.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+#if !defined(RANLIBMAG)
+Arch_TouchLib(GNode *gn MAKE_ATTR_UNUSED)
+#else
+Arch_TouchLib(GNode *gn)
+#endif
+{
+#ifdef RANLIBMAG
+ FILE * arch; /* Stream open to archive */
+ struct ar_hdr arh; /* Header describing table of contents */
+ struct utimbuf times; /* Times for utime() call */
+
+ arch = ArchFindMember(gn->path, UNCONST(RANLIBMAG), &arh, "r+");
+ snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long) now);
+
+ if (arch != NULL) {
+ (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch);
+ fclose(arch);
+
+ times.actime = times.modtime = now;
+ utime(gn->path, &times);
+ }
+#endif
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_MTime --
+ * Return the modification time of a member of an archive.
+ *
+ * Input:
+ * gn Node describing archive member
+ *
+ * Results:
+ * The modification time(seconds).
+ *
+ * Side Effects:
+ * The mtime field of the given node is filled in with the value
+ * returned by the function.
+ *
+ *-----------------------------------------------------------------------
+ */
+time_t
+Arch_MTime(GNode *gn)
+{
+ struct ar_hdr *arhPtr; /* Header of desired member */
+ time_t modTime; /* Modification time as an integer */
+ char *p1, *p2;
+
+ arhPtr = ArchStatMember(Var_Value(ARCHIVE, gn, &p1),
+ Var_Value(MEMBER, gn, &p2),
+ TRUE);
+
+ free(p1);
+ free(p2);
+
+ if (arhPtr != NULL) {
+ modTime = (time_t)strtol(arhPtr->ar_date, NULL, 10);
+ } else {
+ modTime = 0;
+ }
+
+ gn->mtime = modTime;
+ return (modTime);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_MemMTime --
+ * Given a non-existent archive member's node, get its modification
+ * time from its archived form, if it exists.
+ *
+ * Results:
+ * The modification time.
+ *
+ * Side Effects:
+ * The mtime field is filled in.
+ *
+ *-----------------------------------------------------------------------
+ */
+time_t
+Arch_MemMTime(GNode *gn)
+{
+ LstNode ln;
+ GNode *pgn;
+ char *nameStart,
+ *nameEnd;
+
+ if (Lst_Open(gn->parents) != SUCCESS) {
+ gn->mtime = 0;
+ return (0);
+ }
+ while ((ln = Lst_Next(gn->parents)) != NULL) {
+ pgn = (GNode *)Lst_Datum(ln);
+
+ if (pgn->type & OP_ARCHV) {
+ /*
+ * If the parent is an archive specification and is being made
+ * and its member's name matches the name of the node we were
+ * given, record the modification time of the parent in the
+ * child. We keep searching its parents in case some other
+ * parent requires this child to exist...
+ */
+ nameStart = strchr(pgn->name, '(') + 1;
+ nameEnd = strchr(nameStart, ')');
+
+ if ((pgn->flags & REMAKE) &&
+ strncmp(nameStart, gn->name, nameEnd - nameStart) == 0) {
+ gn->mtime = Arch_MTime(pgn);
+ }
+ } else if (pgn->flags & REMAKE) {
+ /*
+ * Something which isn't a library depends on the existence of
+ * this target, so it needs to exist.
+ */
+ gn->mtime = 0;
+ break;
+ }
+ }
+
+ Lst_Close(gn->parents);
+
+ return (gn->mtime);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_FindLib --
+ * Search for a library along the given search path.
+ *
+ * Input:
+ * gn Node of library to find
+ * path Search path
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The node's 'path' field is set to the found path (including the
+ * actual file name, not -l...). If the system can handle the -L
+ * flag when linking (or we cannot find the library), we assume that
+ * the user has placed the .LIBRARIES variable in the final linking
+ * command (or the linker will know where to find it) and set the
+ * TARGET variable for this node to be the node's name. Otherwise,
+ * we set the TARGET variable to be the full path of the library,
+ * as returned by Dir_FindFile.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_FindLib(GNode *gn, Lst path)
+{
+ char *libName; /* file name for archive */
+ size_t sz = strlen(gn->name) + 6 - 2;
+
+ libName = bmake_malloc(sz);
+ snprintf(libName, sz, "lib%s.a", &gn->name[2]);
+
+ gn->path = Dir_FindFile(libName, path);
+
+ free(libName);
+
+#ifdef LIBRARIES
+ Var_Set(TARGET, gn->name, gn, 0);
+#else
+ Var_Set(TARGET, gn->path == NULL ? gn->name : gn->path, gn, 0);
+#endif /* LIBRARIES */
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_LibOODate --
+ * Decide if a node with the OP_LIB attribute is out-of-date. Called
+ * from Make_OODate to make its life easier.
+ *
+ * There are several ways for a library to be out-of-date that are
+ * not available to ordinary files. In addition, there are ways
+ * that are open to regular files that are not available to
+ * libraries. A library that is only used as a source is never
+ * considered out-of-date by itself. This does not preclude the
+ * library's modification time from making its parent be out-of-date.
+ * A library will be considered out-of-date for any of these reasons,
+ * given that it is a target on a dependency line somewhere:
+ * Its modification time is less than that of one of its
+ * sources (gn->mtime < gn->cmgn->mtime).
+ * Its modification time is greater than the time at which the
+ * make began (i.e. it's been modified in the course
+ * of the make, probably by archiving).
+ * The modification time of one of its sources is greater than
+ * the one of its RANLIBMAG member (i.e. its table of contents
+ * is out-of-date). We don't compare of the archive time
+ * vs. TOC time because they can be too close. In my
+ * opinion we should not bother with the TOC at all since
+ * this is used by 'ar' rules that affect the data contents
+ * of the archive, not by ranlib rules, which affect the
+ * TOC.
+ *
+ * Input:
+ * gn The library's graph node
+ *
+ * Results:
+ * TRUE if the library is out-of-date. FALSE otherwise.
+ *
+ * Side Effects:
+ * The library will be hashed if it hasn't been already.
+ *
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Arch_LibOODate(GNode *gn)
+{
+ Boolean oodate;
+
+ if (gn->type & OP_PHONY) {
+ oodate = TRUE;
+ } else if (OP_NOP(gn->type) && Lst_IsEmpty(gn->children)) {
+ oodate = FALSE;
+ } else if ((!Lst_IsEmpty(gn->children) && gn->cmgn == NULL) ||
+ (gn->mtime > now) ||
+ (gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime)) {
+ oodate = TRUE;
+ } else {
+#ifdef RANLIBMAG
+ struct ar_hdr *arhPtr; /* Header for __.SYMDEF */
+ int modTimeTOC; /* The table-of-contents's mod time */
+
+ arhPtr = ArchStatMember(gn->path, UNCONST(RANLIBMAG), FALSE);
+
+ if (arhPtr != NULL) {
+ modTimeTOC = (int)strtol(arhPtr->ar_date, NULL, 10);
+
+ if (DEBUG(ARCH) || DEBUG(MAKE)) {
+ fprintf(debug_file, "%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC));
+ }
+ oodate = (gn->cmgn == NULL || gn->cmgn->mtime > modTimeTOC);
+ } else {
+ /*
+ * A library w/o a table of contents is out-of-date
+ */
+ if (DEBUG(ARCH) || DEBUG(MAKE)) {
+ fprintf(debug_file, "No t.o.c....");
+ }
+ oodate = TRUE;
+ }
+#else
+ oodate = FALSE;
+#endif
+ }
+ return (oodate);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_Init --
+ * Initialize things for this module.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The 'archives' list is initialized.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_Init(void)
+{
+ archives = Lst_Init(FALSE);
+}
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_End --
+ * Cleanup things for this module.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The 'archives' list is freed
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_End(void)
+{
+#ifdef CLEANUP
+ Lst_Destroy(archives, ArchFree);
+#endif
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_IsLib --
+ * Check if the node is a library
+ *
+ * Results:
+ * True or False.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Arch_IsLib(GNode *gn)
+{
+ static const char armag[] = "!<arch>\n";
+ char buf[sizeof(armag)-1];
+ int fd;
+
+ if ((fd = open(gn->path, O_RDONLY)) == -1)
+ return FALSE;
+
+ if (read(fd, buf, sizeof(buf)) != sizeof(buf)) {
+ (void)close(fd);
+ return FALSE;
+ }
+
+ (void)close(fd);
+
+ return memcmp(buf, armag, sizeof(buf)) == 0;
+}
diff --git a/usr.bin/make/buf.c b/usr.bin/make/buf.c
new file mode 100644
index 0000000..ac95c16
--- /dev/null
+++ b/usr.bin/make/buf.c
@@ -0,0 +1,291 @@
+/* $NetBSD: buf.c,v 1.25 2012/04/24 20:26:58 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: buf.c,v 1.25 2012/04/24 20:26:58 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)buf.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: buf.c,v 1.25 2012/04/24 20:26:58 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * buf.c --
+ * Functions for automatically-expanded buffers.
+ */
+
+#include "make.h"
+#include "buf.h"
+
+#ifndef max
+#define max(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+#define BUF_DEF_SIZE 256 /* Default buffer size */
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Expand_1 --
+ * Extend buffer for single byte add.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_Expand_1(Buffer *bp)
+{
+ bp->size += max(bp->size, 16);
+ bp->buffer = bmake_realloc(bp->buffer, bp->size);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_AddBytes --
+ * Add a number of bytes to the buffer.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Guess what?
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_AddBytes(Buffer *bp, int numBytes, const Byte *bytesPtr)
+{
+ int count = bp->count;
+ Byte *ptr;
+
+ if (__predict_false(count + numBytes >= bp->size)) {
+ bp->size += max(bp->size, numBytes + 16);
+ bp->buffer = bmake_realloc(bp->buffer, bp->size);
+ }
+
+ ptr = bp->buffer + count;
+ bp->count = count + numBytes;
+ ptr[numBytes] = 0;
+ memcpy(ptr, bytesPtr, numBytes);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_GetAll --
+ * Get all the available data at once.
+ *
+ * Results:
+ * A pointer to the data and the number of bytes available.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+Byte *
+Buf_GetAll(Buffer *bp, int *numBytesPtr)
+{
+
+ if (numBytesPtr != NULL)
+ *numBytesPtr = bp->count;
+
+ return (bp->buffer);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Empty --
+ * Throw away bytes in a buffer.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The bytes are discarded.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_Empty(Buffer *bp)
+{
+
+ bp->count = 0;
+ *bp->buffer = 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Init --
+ * Initialize a buffer. If no initial size is given, a reasonable
+ * default is used.
+ *
+ * Input:
+ * size Initial size for the buffer
+ *
+ * Results:
+ * A buffer to be given to other functions in this library.
+ *
+ * Side Effects:
+ * The buffer is created, the space allocated and pointers
+ * initialized.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_Init(Buffer *bp, int size)
+{
+ if (size <= 0) {
+ size = BUF_DEF_SIZE;
+ }
+ bp->size = size;
+ bp->count = 0;
+ bp->buffer = bmake_malloc(size);
+ *bp->buffer = 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Destroy --
+ * Nuke a buffer and all its resources.
+ *
+ * Input:
+ * buf Buffer to destroy
+ * freeData TRUE if the data should be destroyed
+ *
+ * Results:
+ * Data buffer, NULL if freed
+ *
+ * Side Effects:
+ * The buffer is freed.
+ *
+ *-----------------------------------------------------------------------
+ */
+Byte *
+Buf_Destroy(Buffer *buf, Boolean freeData)
+{
+ Byte *data;
+
+ data = buf->buffer;
+ if (freeData) {
+ free(data);
+ data = NULL;
+ }
+
+ buf->size = 0;
+ buf->count = 0;
+ buf->buffer = NULL;
+
+ return data;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_DestroyCompact --
+ * Nuke a buffer and return its data.
+ *
+ * Input:
+ * buf Buffer to destroy
+ *
+ * Results:
+ * Data buffer
+ *
+ * Side Effects:
+ * If the buffer size is much greater than its content,
+ * a new buffer will be allocated and the old one freed.
+ *
+ *-----------------------------------------------------------------------
+ */
+#ifndef BUF_COMPACT_LIMIT
+# define BUF_COMPACT_LIMIT 128 /* worthwhile saving */
+#endif
+
+Byte *
+Buf_DestroyCompact(Buffer *buf)
+{
+#if BUF_COMPACT_LIMIT > 0
+ Byte *data;
+
+ if (buf->size - buf->count >= BUF_COMPACT_LIMIT) {
+ /* We trust realloc to be smart */
+ data = bmake_realloc(buf->buffer, buf->count + 1);
+ if (data) {
+ data[buf->count] = 0;
+ Buf_Destroy(buf, FALSE);
+ return data;
+ }
+ }
+#endif
+ return Buf_Destroy(buf, FALSE);
+}
diff --git a/usr.bin/make/buf.h b/usr.bin/make/buf.h
new file mode 100644
index 0000000..7bd2d2b
--- /dev/null
+++ b/usr.bin/make/buf.h
@@ -0,0 +1,119 @@
+/* $NetBSD: buf.h,v 1.19 2017/05/31 22:02:06 maya Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)buf.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)buf.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*-
+ * buf.h --
+ * Header for users of the buf library.
+ */
+
+#ifndef MAKE_BUF_H
+#define MAKE_BUF_H
+
+typedef char Byte;
+
+typedef struct Buffer {
+ int size; /* Current size of the buffer */
+ int count; /* Number of bytes in buffer */
+ Byte *buffer; /* The buffer itself (zero terminated) */
+} Buffer;
+
+/* If we aren't on netbsd, __predict_false() might not be defined. */
+#ifndef __predict_false
+#define __predict_false(x) (x)
+#endif
+
+/* Buf_AddByte adds a single byte to a buffer. */
+#define Buf_AddByte(bp, byte) do { \
+ int _count = ++(bp)->count; \
+ char *_ptr; \
+ if (__predict_false(_count >= (bp)->size)) \
+ Buf_Expand_1(bp); \
+ _ptr = (bp)->buffer + _count; \
+ _ptr[-1] = (byte); \
+ _ptr[0] = 0; \
+ } while (0)
+
+#define BUF_ERROR 256
+
+#define Buf_Size(bp) ((bp)->count)
+
+void Buf_Expand_1(Buffer *);
+void Buf_AddBytes(Buffer *, int, const Byte *);
+Byte *Buf_GetAll(Buffer *, int *);
+void Buf_Empty(Buffer *);
+void Buf_Init(Buffer *, int);
+Byte *Buf_Destroy(Buffer *, Boolean);
+Byte *Buf_DestroyCompact(Buffer *);
+
+#endif /* MAKE_BUF_H */
diff --git a/usr.bin/make/compat.c b/usr.bin/make/compat.c
new file mode 100644
index 0000000..538c456
--- /dev/null
+++ b/usr.bin/make/compat.c
@@ -0,0 +1,778 @@
+/* $NetBSD: compat.c,v 1.107 2017/07/20 19:29:54 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: compat.c,v 1.107 2017/07/20 19:29:54 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)compat.c 8.2 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: compat.c,v 1.107 2017/07/20 19:29:54 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * compat.c --
+ * The routines in this file implement the full-compatibility
+ * mode of PMake. Most of the special functionality of PMake
+ * is available in this mode. Things not supported:
+ * - different shells.
+ * - friendly variable substitution.
+ *
+ * Interface:
+ * Compat_Run Initialize things for this module and recreate
+ * thems as need creatin'
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "metachar.h"
+#include "pathnames.h"
+
+
+static GNode *curTarg = NULL;
+static GNode *ENDNode;
+static void CompatInterrupt(int);
+static pid_t compatChild;
+static int compatSigno;
+
+/*
+ * CompatDeleteTarget -- delete a failed, interrupted, or otherwise
+ * duffed target if not inhibited by .PRECIOUS.
+ */
+static void
+CompatDeleteTarget(GNode *gn)
+{
+ if ((gn != NULL) && !Targ_Precious (gn)) {
+ char *p1;
+ char *file = Var_Value(TARGET, gn, &p1);
+
+ if (!noExecute && eunlink(file) != -1) {
+ Error("*** %s removed", file);
+ }
+
+ free(p1);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CompatInterrupt --
+ * Interrupt the creation of the current target and remove it if
+ * it ain't precious.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The target is removed and the process exits. If .INTERRUPT exists,
+ * its commands are run first WITH INTERRUPTS IGNORED..
+ *
+ * XXX: is .PRECIOUS supposed to inhibit .INTERRUPT? I doubt it, but I've
+ * left the logic alone for now. - dholland 20160826
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+CompatInterrupt(int signo)
+{
+ GNode *gn;
+
+ CompatDeleteTarget(curTarg);
+
+ if ((curTarg != NULL) && !Targ_Precious (curTarg)) {
+ /*
+ * Run .INTERRUPT only if hit with interrupt signal
+ */
+ if (signo == SIGINT) {
+ gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE);
+ if (gn != NULL) {
+ Compat_Make(gn, gn);
+ }
+ }
+ }
+ if (signo == SIGQUIT)
+ _exit(signo);
+ /*
+ * If there is a child running, pass the signal on
+ * we will exist after it has exited.
+ */
+ compatSigno = signo;
+ if (compatChild > 0) {
+ KILLPG(compatChild, signo);
+ } else {
+ bmake_signal(signo, SIG_DFL);
+ kill(myPid, signo);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CompatRunCommand --
+ * Execute the next command for a target. If the command returns an
+ * error, the node's made field is set to ERROR and creation stops.
+ *
+ * Input:
+ * cmdp Command to execute
+ * gnp Node from which the command came
+ *
+ * Results:
+ * 0 if the command succeeded, 1 if an error occurred.
+ *
+ * Side Effects:
+ * The node's 'made' field may be set to ERROR.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+CompatRunCommand(void *cmdp, void *gnp)
+{
+ char *cmdStart; /* Start of expanded command */
+ char *cp, *bp;
+ Boolean silent, /* Don't print command */
+ doIt; /* Execute even if -n */
+ volatile Boolean errCheck; /* Check errors */
+ int reason; /* Reason for child's death */
+ int status; /* Description of child's death */
+ pid_t cpid; /* Child actually found */
+ pid_t retstat; /* Result of wait */
+ LstNode cmdNode; /* Node where current command is located */
+ const char ** volatile av; /* Argument vector for thing to exec */
+ char ** volatile mav;/* Copy of the argument vector for freeing */
+ int argc; /* Number of arguments in av or 0 if not
+ * dynamically allocated */
+ Boolean local; /* TRUE if command should be executed
+ * locally */
+ Boolean useShell; /* TRUE if command should be executed
+ * using a shell */
+ char * volatile cmd = (char *)cmdp;
+ GNode *gn = (GNode *)gnp;
+
+ silent = gn->type & OP_SILENT;
+ errCheck = !(gn->type & OP_IGNORE);
+ doIt = FALSE;
+
+ cmdNode = Lst_Member(gn->commands, cmd);
+ cmdStart = Var_Subst(NULL, cmd, gn, VARF_WANTRES);
+
+ /*
+ * brk_string will return an argv with a NULL in av[0], thus causing
+ * execvp to choke and die horribly. Besides, how can we execute a null
+ * command? In any case, we warn the user that the command expanded to
+ * nothing (is this the right thing to do?).
+ */
+
+ if (*cmdStart == '\0') {
+ free(cmdStart);
+ return(0);
+ }
+ cmd = cmdStart;
+ Lst_Replace(cmdNode, cmdStart);
+
+ if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) {
+ (void)Lst_AtEnd(ENDNode->commands, cmdStart);
+ return(0);
+ }
+ if (strcmp(cmdStart, "...") == 0) {
+ gn->type |= OP_SAVE_CMDS;
+ return(0);
+ }
+
+ while ((*cmd == '@') || (*cmd == '-') || (*cmd == '+')) {
+ switch (*cmd) {
+ case '@':
+ silent = DEBUG(LOUD) ? FALSE : TRUE;
+ break;
+ case '-':
+ errCheck = FALSE;
+ break;
+ case '+':
+ doIt = TRUE;
+ if (!shellName) /* we came here from jobs */
+ Shell_Init();
+ break;
+ }
+ cmd++;
+ }
+
+ while (isspace((unsigned char)*cmd))
+ cmd++;
+
+ /*
+ * If we did not end up with a command, just skip it.
+ */
+ if (!*cmd)
+ return (0);
+
+#if !defined(MAKE_NATIVE)
+ /*
+ * In a non-native build, the host environment might be weird enough
+ * that it's necessary to go through a shell to get the correct
+ * behaviour. Or perhaps the shell has been replaced with something
+ * that does extra logging, and that should not be bypassed.
+ */
+ useShell = TRUE;
+#else
+ /*
+ * Search for meta characters in the command. If there are no meta
+ * characters, there's no need to execute a shell to execute the
+ * command.
+ *
+ * Additionally variable assignments and empty commands
+ * go to the shell. Therefore treat '=' and ':' like shell
+ * meta characters as documented in make(1).
+ */
+
+ useShell = needshell(cmd, FALSE);
+#endif
+
+ /*
+ * Print the command before echoing if we're not supposed to be quiet for
+ * this one. We also print the command if -n given.
+ */
+ if (!silent || NoExecute(gn)) {
+ printf("%s\n", cmd);
+ fflush(stdout);
+ }
+
+ /*
+ * If we're not supposed to execute any commands, this is as far as
+ * we go...
+ */
+ if (!doIt && NoExecute(gn)) {
+ return (0);
+ }
+ if (DEBUG(JOB))
+ fprintf(debug_file, "Execute: '%s'\n", cmd);
+
+again:
+ if (useShell) {
+ /*
+ * We need to pass the command off to the shell, typically
+ * because the command contains a "meta" character.
+ */
+ static const char *shargv[5];
+ int shargc;
+
+ shargc = 0;
+ shargv[shargc++] = shellPath;
+ /*
+ * The following work for any of the builtin shell specs.
+ */
+ if (errCheck && shellErrFlag) {
+ shargv[shargc++] = shellErrFlag;
+ }
+ if (DEBUG(SHELL))
+ shargv[shargc++] = "-xc";
+ else
+ shargv[shargc++] = "-c";
+ shargv[shargc++] = cmd;
+ shargv[shargc++] = NULL;
+ av = shargv;
+ argc = 0;
+ bp = NULL;
+ mav = NULL;
+ } else {
+ /*
+ * No meta-characters, so no need to exec a shell. Break the command
+ * into words to form an argument vector we can execute.
+ */
+ mav = brk_string(cmd, &argc, TRUE, &bp);
+ if (mav == NULL) {
+ useShell = 1;
+ goto again;
+ }
+ av = (void *)mav;
+ }
+
+ local = TRUE;
+
+#ifdef USE_META
+ if (useMeta) {
+ meta_compat_start();
+ }
+#endif
+
+ /*
+ * Fork and execute the single command. If the fork fails, we abort.
+ */
+ compatChild = cpid = vFork();
+ if (cpid < 0) {
+ Fatal("Could not fork");
+ }
+ if (cpid == 0) {
+ Var_ExportVars();
+#ifdef USE_META
+ if (useMeta) {
+ meta_compat_child();
+ }
+#endif
+ if (local)
+ (void)execvp(av[0], (char *const *)UNCONST(av));
+ else
+ (void)execv(av[0], (char *const *)UNCONST(av));
+ execError("exec", av[0]);
+ _exit(1);
+ }
+
+ free(mav);
+ free(bp);
+
+ Lst_Replace(cmdNode, NULL);
+
+#ifdef USE_META
+ if (useMeta) {
+ meta_compat_parent();
+ }
+#endif
+
+ /*
+ * The child is off and running. Now all we can do is wait...
+ */
+ while (1) {
+
+ while ((retstat = wait(&reason)) != cpid) {
+ if (retstat > 0)
+ JobReapChild(retstat, reason, FALSE); /* not ours? */
+ if (retstat == -1 && errno != EINTR) {
+ break;
+ }
+ }
+
+ if (retstat > -1) {
+ if (WIFSTOPPED(reason)) {
+ status = WSTOPSIG(reason); /* stopped */
+ } else if (WIFEXITED(reason)) {
+ status = WEXITSTATUS(reason); /* exited */
+#if defined(USE_META) && defined(USE_FILEMON_ONCE)
+ if (useMeta) {
+ meta_cmd_finish(NULL);
+ }
+#endif
+ if (status != 0) {
+ if (DEBUG(ERROR)) {
+ fprintf(debug_file, "\n*** Failed target: %s\n*** Failed command: ",
+ gn->name);
+ for (cp = cmd; *cp; ) {
+ if (isspace((unsigned char)*cp)) {
+ fprintf(debug_file, " ");
+ while (isspace((unsigned char)*cp))
+ cp++;
+ } else {
+ fprintf(debug_file, "%c", *cp);
+ cp++;
+ }
+ }
+ fprintf(debug_file, "\n");
+ }
+ printf("*** Error code %d", status);
+ }
+ } else {
+ status = WTERMSIG(reason); /* signaled */
+ printf("*** Signal %d", status);
+ }
+
+
+ if (!WIFEXITED(reason) || (status != 0)) {
+ if (errCheck) {
+#ifdef USE_META
+ if (useMeta) {
+ meta_job_error(NULL, gn, 0, status);
+ }
+#endif
+ gn->made = ERROR;
+ if (keepgoing) {
+ /*
+ * Abort the current target, but let others
+ * continue.
+ */
+ printf(" (continuing)\n");
+ } else {
+ printf("\n");
+ }
+ if (deleteOnError) {
+ CompatDeleteTarget(gn);
+ }
+ } else {
+ /*
+ * Continue executing commands for this target.
+ * If we return 0, this will happen...
+ */
+ printf(" (ignored)\n");
+ status = 0;
+ }
+ }
+ break;
+ } else {
+ Fatal("error in wait: %d: %s", retstat, strerror(errno));
+ /*NOTREACHED*/
+ }
+ }
+ free(cmdStart);
+ compatChild = 0;
+ if (compatSigno) {
+ bmake_signal(compatSigno, SIG_DFL);
+ kill(myPid, compatSigno);
+ }
+
+ return (status);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Compat_Make --
+ * Make a target.
+ *
+ * Input:
+ * gnp The node to make
+ * pgnp Parent to abort if necessary
+ *
+ * Results:
+ * 0
+ *
+ * Side Effects:
+ * If an error is detected and not being ignored, the process exits.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Compat_Make(void *gnp, void *pgnp)
+{
+ GNode *gn = (GNode *)gnp;
+ GNode *pgn = (GNode *)pgnp;
+
+ if (!shellName) /* we came here from jobs */
+ Shell_Init();
+ if (gn->made == UNMADE && (gn == pgn || (pgn->type & OP_MADE) == 0)) {
+ /*
+ * First mark ourselves to be made, then apply whatever transformations
+ * the suffix module thinks are necessary. Once that's done, we can
+ * descend and make all our children. If any of them has an error
+ * but the -k flag was given, our 'make' field will be set FALSE again.
+ * This is our signal to not attempt to do anything but abort our
+ * parent as well.
+ */
+ gn->flags |= REMAKE;
+ gn->made = BEINGMADE;
+ if ((gn->type & OP_MADE) == 0)
+ Suff_FindDeps(gn);
+ Lst_ForEach(gn->children, Compat_Make, gn);
+ if ((gn->flags & REMAKE) == 0) {
+ gn->made = ABORTED;
+ pgn->flags &= ~REMAKE;
+ goto cohorts;
+ }
+
+ if (Lst_Member(gn->iParents, pgn) != NULL) {
+ char *p1;
+ Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0);
+ free(p1);
+ }
+
+ /*
+ * All the children were made ok. Now cmgn->mtime contains the
+ * modification time of the newest child, we need to find out if we
+ * exist and when we were modified last. The criteria for datedness
+ * are defined by the Make_OODate function.
+ */
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "Examining %s...", gn->name);
+ }
+ if (! Make_OODate(gn)) {
+ gn->made = UPTODATE;
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "up-to-date.\n");
+ }
+ goto cohorts;
+ } else if (DEBUG(MAKE)) {
+ fprintf(debug_file, "out-of-date.\n");
+ }
+
+ /*
+ * If the user is just seeing if something is out-of-date, exit now
+ * to tell him/her "yes".
+ */
+ if (queryFlag) {
+ exit(1);
+ }
+
+ /*
+ * We need to be re-made. We also have to make sure we've got a $?
+ * variable. To be nice, we also define the $> variable using
+ * Make_DoAllVar().
+ */
+ Make_DoAllVar(gn);
+
+ /*
+ * Alter our type to tell if errors should be ignored or things
+ * should not be printed so CompatRunCommand knows what to do.
+ */
+ if (Targ_Ignore(gn)) {
+ gn->type |= OP_IGNORE;
+ }
+ if (Targ_Silent(gn)) {
+ gn->type |= OP_SILENT;
+ }
+
+ if (Job_CheckCommands(gn, Fatal)) {
+ /*
+ * Our commands are ok, but we still have to worry about the -t
+ * flag...
+ */
+ if (!touchFlag || (gn->type & OP_MAKE)) {
+ curTarg = gn;
+#ifdef USE_META
+ if (useMeta && !NoExecute(gn)) {
+ meta_job_start(NULL, gn);
+ }
+#endif
+ Lst_ForEach(gn->commands, CompatRunCommand, gn);
+ curTarg = NULL;
+ } else {
+ Job_Touch(gn, gn->type & OP_SILENT);
+ }
+ } else {
+ gn->made = ERROR;
+ }
+#ifdef USE_META
+ if (useMeta && !NoExecute(gn)) {
+ if (meta_job_finish(NULL) != 0)
+ gn->made = ERROR;
+ }
+#endif
+
+ if (gn->made != ERROR) {
+ /*
+ * If the node was made successfully, mark it so, update
+ * its modification time and timestamp all its parents. Note
+ * that for .ZEROTIME targets, the timestamping isn't done.
+ * This is to keep its state from affecting that of its parent.
+ */
+ gn->made = MADE;
+ pgn->flags |= Make_Recheck(gn) == 0 ? FORCE : 0;
+ if (!(gn->type & OP_EXEC)) {
+ pgn->flags |= CHILDMADE;
+ Make_TimeStamp(pgn, gn);
+ }
+ } else if (keepgoing) {
+ pgn->flags &= ~REMAKE;
+ } else {
+ PrintOnError(gn, "\nStop.");
+ exit(1);
+ }
+ } else if (gn->made == ERROR) {
+ /*
+ * Already had an error when making this beastie. Tell the parent
+ * to abort.
+ */
+ pgn->flags &= ~REMAKE;
+ } else {
+ if (Lst_Member(gn->iParents, pgn) != NULL) {
+ char *p1;
+ Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0);
+ free(p1);
+ }
+ switch(gn->made) {
+ case BEINGMADE:
+ Error("Graph cycles through %s", gn->name);
+ gn->made = ERROR;
+ pgn->flags &= ~REMAKE;
+ break;
+ case MADE:
+ if ((gn->type & OP_EXEC) == 0) {
+ pgn->flags |= CHILDMADE;
+ Make_TimeStamp(pgn, gn);
+ }
+ break;
+ case UPTODATE:
+ if ((gn->type & OP_EXEC) == 0) {
+ Make_TimeStamp(pgn, gn);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+cohorts:
+ Lst_ForEach(gn->cohorts, Compat_Make, pgnp);
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Compat_Run --
+ * Initialize this mode and start making.
+ *
+ * Input:
+ * targs List of target nodes to re-create
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Guess what?
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Compat_Run(Lst targs)
+{
+ GNode *gn = NULL;/* Current root target */
+ int errors; /* Number of targets not remade due to errors */
+
+ if (!shellName)
+ Shell_Init();
+
+ if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN) {
+ bmake_signal(SIGINT, CompatInterrupt);
+ }
+ if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN) {
+ bmake_signal(SIGTERM, CompatInterrupt);
+ }
+ if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN) {
+ bmake_signal(SIGHUP, CompatInterrupt);
+ }
+ if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN) {
+ bmake_signal(SIGQUIT, CompatInterrupt);
+ }
+
+ ENDNode = Targ_FindNode(".END", TARG_CREATE);
+ ENDNode->type = OP_SPECIAL;
+ /*
+ * If the user has defined a .BEGIN target, execute the commands attached
+ * to it.
+ */
+ if (!queryFlag) {
+ gn = Targ_FindNode(".BEGIN", TARG_NOCREATE);
+ if (gn != NULL) {
+ Compat_Make(gn, gn);
+ if (gn->made == ERROR) {
+ PrintOnError(gn, "\nStop.");
+ exit(1);
+ }
+ }
+ }
+
+ /*
+ * Expand .USE nodes right now, because they can modify the structure
+ * of the tree.
+ */
+ Make_ExpandUse(targs);
+
+ /*
+ * For each entry in the list of targets to create, call Compat_Make on
+ * it to create the thing. Compat_Make will leave the 'made' field of gn
+ * in one of several states:
+ * UPTODATE gn was already up-to-date
+ * MADE gn was recreated successfully
+ * ERROR An error occurred while gn was being created
+ * ABORTED gn was not remade because one of its inferiors
+ * could not be made due to errors.
+ */
+ errors = 0;
+ while (!Lst_IsEmpty (targs)) {
+ gn = (GNode *)Lst_DeQueue(targs);
+ Compat_Make(gn, gn);
+
+ if (gn->made == UPTODATE) {
+ printf("`%s' is up to date.\n", gn->name);
+ } else if (gn->made == ABORTED) {
+ printf("`%s' not remade because of errors.\n", gn->name);
+ errors += 1;
+ }
+ }
+
+ /*
+ * If the user has defined a .END target, run its commands.
+ */
+ if (errors == 0) {
+ Compat_Make(ENDNode, ENDNode);
+ if (gn->made == ERROR) {
+ PrintOnError(gn, "\nStop.");
+ exit(1);
+ }
+ }
+}
diff --git a/usr.bin/make/cond.c b/usr.bin/make/cond.c
new file mode 100644
index 0000000..7c9c96a
--- /dev/null
+++ b/usr.bin/make/cond.c
@@ -0,0 +1,1436 @@
+/* $NetBSD: cond.c,v 1.75 2017/04/16 20:59:04 riastradh Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: cond.c,v 1.75 2017/04/16 20:59:04 riastradh Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cond.c 8.2 (Berkeley) 1/2/94";
+#else
+__RCSID("$NetBSD: cond.c,v 1.75 2017/04/16 20:59:04 riastradh Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * cond.c --
+ * Functions to handle conditionals in a makefile.
+ *
+ * Interface:
+ * Cond_Eval Evaluate the conditional in the passed line.
+ *
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h> /* For strtoul() error checking */
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "buf.h"
+
+/*
+ * The parsing of conditional expressions is based on this grammar:
+ * E -> F || E
+ * E -> F
+ * F -> T && F
+ * F -> T
+ * T -> defined(variable)
+ * T -> make(target)
+ * T -> exists(file)
+ * T -> empty(varspec)
+ * T -> target(name)
+ * T -> commands(name)
+ * T -> symbol
+ * T -> $(varspec) op value
+ * T -> $(varspec) == "string"
+ * T -> $(varspec) != "string"
+ * T -> "string"
+ * T -> ( E )
+ * T -> ! T
+ * op -> == | != | > | < | >= | <=
+ *
+ * 'symbol' is some other symbol to which the default function (condDefProc)
+ * is applied.
+ *
+ * Tokens are scanned from the 'condExpr' string. The scanner (CondToken)
+ * will return TOK_AND for '&' and '&&', TOK_OR for '|' and '||',
+ * TOK_NOT for '!', TOK_LPAREN for '(', TOK_RPAREN for ')' and will evaluate
+ * the other terminal symbols, using either the default function or the
+ * function given in the terminal, and return the result as either TOK_TRUE
+ * or TOK_FALSE.
+ *
+ * TOK_FALSE is 0 and TOK_TRUE 1 so we can directly assign C comparisons.
+ *
+ * All Non-Terminal functions (CondE, CondF and CondT) return TOK_ERROR on
+ * error.
+ */
+typedef enum {
+ TOK_FALSE = 0, TOK_TRUE = 1, TOK_AND, TOK_OR, TOK_NOT,
+ TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR
+} Token;
+
+/*-
+ * Structures to handle elegantly the different forms of #if's. The
+ * last two fields are stored in condInvert and condDefProc, respectively.
+ */
+static void CondPushBack(Token);
+static int CondGetArg(char **, char **, const char *);
+static Boolean CondDoDefined(int, const char *);
+static int CondStrMatch(const void *, const void *);
+static Boolean CondDoMake(int, const char *);
+static Boolean CondDoExists(int, const char *);
+static Boolean CondDoTarget(int, const char *);
+static Boolean CondDoCommands(int, const char *);
+static Boolean CondCvtArg(char *, double *);
+static Token CondToken(Boolean);
+static Token CondT(Boolean);
+static Token CondF(Boolean);
+static Token CondE(Boolean);
+static int do_Cond_EvalExpression(Boolean *);
+
+static const struct If {
+ const char *form; /* Form of if */
+ int formlen; /* Length of form */
+ Boolean doNot; /* TRUE if default function should be negated */
+ Boolean (*defProc)(int, const char *); /* Default function to apply */
+} ifs[] = {
+ { "def", 3, FALSE, CondDoDefined },
+ { "ndef", 4, TRUE, CondDoDefined },
+ { "make", 4, FALSE, CondDoMake },
+ { "nmake", 5, TRUE, CondDoMake },
+ { "", 0, FALSE, CondDoDefined },
+ { NULL, 0, FALSE, NULL }
+};
+
+static const struct If *if_info; /* Info for current statement */
+static char *condExpr; /* The expression to parse */
+static Token condPushBack=TOK_NONE; /* Single push-back token used in
+ * parsing */
+
+static unsigned int cond_depth = 0; /* current .if nesting level */
+static unsigned int cond_min_depth = 0; /* depth at makefile open */
+
+/*
+ * Indicate when we should be strict about lhs of comparisons.
+ * TRUE when Cond_EvalExpression is called from Cond_Eval (.if etc)
+ * FALSE when Cond_EvalExpression is called from var.c:ApplyModifiers
+ * since lhs is already expanded and we cannot tell if
+ * it was a variable reference or not.
+ */
+static Boolean lhsStrict;
+
+static int
+istoken(const char *str, const char *tok, size_t len)
+{
+ return strncmp(str, tok, len) == 0 && !isalpha((unsigned char)str[len]);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondPushBack --
+ * Push back the most recent token read. We only need one level of
+ * this, so the thing is just stored in 'condPushback'.
+ *
+ * Input:
+ * t Token to push back into the "stream"
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * condPushback is overwritten.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+CondPushBack(Token t)
+{
+ condPushBack = t;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondGetArg --
+ * Find the argument of a built-in function.
+ *
+ * Input:
+ * parens TRUE if arg should be bounded by parens
+ *
+ * Results:
+ * The length of the argument and the address of the argument.
+ *
+ * Side Effects:
+ * The pointer is set to point to the closing parenthesis of the
+ * function call.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+CondGetArg(char **linePtr, char **argPtr, const char *func)
+{
+ char *cp;
+ int argLen;
+ Buffer buf;
+ int paren_depth;
+ char ch;
+
+ cp = *linePtr;
+ if (func != NULL)
+ /* Skip opening '(' - verfied by caller */
+ cp++;
+
+ if (*cp == '\0') {
+ /*
+ * No arguments whatsoever. Because 'make' and 'defined' aren't really
+ * "reserved words", we don't print a message. I think this is better
+ * than hitting the user with a warning message every time s/he uses
+ * the word 'make' or 'defined' at the beginning of a symbol...
+ */
+ *argPtr = NULL;
+ return (0);
+ }
+
+ while (*cp == ' ' || *cp == '\t') {
+ cp++;
+ }
+
+ /*
+ * Create a buffer for the argument and start it out at 16 characters
+ * long. Why 16? Why not?
+ */
+ Buf_Init(&buf, 16);
+
+ paren_depth = 0;
+ for (;;) {
+ ch = *cp;
+ if (ch == 0 || ch == ' ' || ch == '\t')
+ break;
+ if ((ch == '&' || ch == '|') && paren_depth == 0)
+ break;
+ if (*cp == '$') {
+ /*
+ * Parse the variable spec and install it as part of the argument
+ * if it's valid. We tell Var_Parse to complain on an undefined
+ * variable, so we don't do it too. Nor do we return an error,
+ * though perhaps we should...
+ */
+ char *cp2;
+ int len;
+ void *freeIt;
+
+ cp2 = Var_Parse(cp, VAR_CMD, VARF_UNDEFERR|VARF_WANTRES,
+ &len, &freeIt);
+ Buf_AddBytes(&buf, strlen(cp2), cp2);
+ free(freeIt);
+ cp += len;
+ continue;
+ }
+ if (ch == '(')
+ paren_depth++;
+ else
+ if (ch == ')' && --paren_depth < 0)
+ break;
+ Buf_AddByte(&buf, *cp);
+ cp++;
+ }
+
+ *argPtr = Buf_GetAll(&buf, &argLen);
+ Buf_Destroy(&buf, FALSE);
+
+ while (*cp == ' ' || *cp == '\t') {
+ cp++;
+ }
+
+ if (func != NULL && *cp++ != ')') {
+ Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()",
+ func);
+ return (0);
+ }
+
+ *linePtr = cp;
+ return (argLen);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoDefined --
+ * Handle the 'defined' function for conditionals.
+ *
+ * Results:
+ * TRUE if the given variable is defined.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoDefined(int argLen MAKE_ATTR_UNUSED, const char *arg)
+{
+ char *p1;
+ Boolean result;
+
+ if (Var_Value(arg, VAR_CMD, &p1) != NULL) {
+ result = TRUE;
+ } else {
+ result = FALSE;
+ }
+
+ free(p1);
+ return (result);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondStrMatch --
+ * Front-end for Str_Match so it returns 0 on match and non-zero
+ * on mismatch. Callback function for CondDoMake via Lst_Find
+ *
+ * Results:
+ * 0 if string matches pattern
+ *
+ * Side Effects:
+ * None
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+CondStrMatch(const void *string, const void *pattern)
+{
+ return(!Str_Match(string, pattern));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoMake --
+ * Handle the 'make' function for conditionals.
+ *
+ * Results:
+ * TRUE if the given target is being made.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoMake(int argLen MAKE_ATTR_UNUSED, const char *arg)
+{
+ return Lst_Find(create, arg, CondStrMatch) != NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoExists --
+ * See if the given file exists.
+ *
+ * Results:
+ * TRUE if the file exists and FALSE if it does not.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoExists(int argLen MAKE_ATTR_UNUSED, const char *arg)
+{
+ Boolean result;
+ char *path;
+
+ path = Dir_FindFile(arg, dirSearchPath);
+ if (DEBUG(COND)) {
+ fprintf(debug_file, "exists(%s) result is \"%s\"\n",
+ arg, path ? path : "");
+ }
+ if (path != NULL) {
+ result = TRUE;
+ free(path);
+ } else {
+ result = FALSE;
+ }
+ return (result);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoTarget --
+ * See if the given node exists and is an actual target.
+ *
+ * Results:
+ * TRUE if the node exists as a target and FALSE if it does not.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoTarget(int argLen MAKE_ATTR_UNUSED, const char *arg)
+{
+ GNode *gn;
+
+ gn = Targ_FindNode(arg, TARG_NOCREATE);
+ return (gn != NULL) && !OP_NOP(gn->type);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoCommands --
+ * See if the given node exists and is an actual target with commands
+ * associated with it.
+ *
+ * Results:
+ * TRUE if the node exists as a target and has commands associated with
+ * it and FALSE if it does not.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoCommands(int argLen MAKE_ATTR_UNUSED, const char *arg)
+{
+ GNode *gn;
+
+ gn = Targ_FindNode(arg, TARG_NOCREATE);
+ return (gn != NULL) && !OP_NOP(gn->type) && !Lst_IsEmpty(gn->commands);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondCvtArg --
+ * Convert the given number into a double.
+ * We try a base 10 or 16 integer conversion first, if that fails
+ * then we try a floating point conversion instead.
+ *
+ * Results:
+ * Sets 'value' to double value of string.
+ * Returns 'true' if the convertion suceeded
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondCvtArg(char *str, double *value)
+{
+ char *eptr, ech;
+ unsigned long l_val;
+ double d_val;
+
+ errno = 0;
+ if (!*str) {
+ *value = (double)0;
+ return TRUE;
+ }
+ l_val = strtoul(str, &eptr, str[1] == 'x' ? 16 : 10);
+ ech = *eptr;
+ if (ech == 0 && errno != ERANGE) {
+ d_val = str[0] == '-' ? -(double)-l_val : (double)l_val;
+ } else {
+ if (ech != 0 && ech != '.' && ech != 'e' && ech != 'E')
+ return FALSE;
+ d_val = strtod(str, &eptr);
+ if (*eptr)
+ return FALSE;
+ }
+
+ *value = d_val;
+ return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondGetString --
+ * Get a string from a variable reference or an optionally quoted
+ * string. This is called for the lhs and rhs of string compares.
+ *
+ * Results:
+ * Sets freeIt if needed,
+ * Sets quoted if string was quoted,
+ * Returns NULL on error,
+ * else returns string - absent any quotes.
+ *
+ * Side Effects:
+ * Moves condExpr to end of this token.
+ *
+ *
+ *-----------------------------------------------------------------------
+ */
+/* coverity:[+alloc : arg-*2] */
+static char *
+CondGetString(Boolean doEval, Boolean *quoted, void **freeIt, Boolean strictLHS)
+{
+ Buffer buf;
+ char *cp;
+ char *str;
+ int len;
+ int qt;
+ char *start;
+
+ Buf_Init(&buf, 0);
+ str = NULL;
+ *freeIt = NULL;
+ *quoted = qt = *condExpr == '"' ? 1 : 0;
+ if (qt)
+ condExpr++;
+ for (start = condExpr; *condExpr && str == NULL; condExpr++) {
+ switch (*condExpr) {
+ case '\\':
+ if (condExpr[1] != '\0') {
+ condExpr++;
+ Buf_AddByte(&buf, *condExpr);
+ }
+ break;
+ case '"':
+ if (qt) {
+ condExpr++; /* we don't want the quotes */
+ goto got_str;
+ } else
+ Buf_AddByte(&buf, *condExpr); /* likely? */
+ break;
+ case ')':
+ case '!':
+ case '=':
+ case '>':
+ case '<':
+ case ' ':
+ case '\t':
+ if (!qt)
+ goto got_str;
+ else
+ Buf_AddByte(&buf, *condExpr);
+ break;
+ case '$':
+ /* if we are in quotes, then an undefined variable is ok */
+ str = Var_Parse(condExpr, VAR_CMD,
+ ((!qt && doEval) ? VARF_UNDEFERR : 0) |
+ VARF_WANTRES, &len, freeIt);
+ if (str == var_Error) {
+ if (*freeIt) {
+ free(*freeIt);
+ *freeIt = NULL;
+ }
+ /*
+ * Even if !doEval, we still report syntax errors, which
+ * is what getting var_Error back with !doEval means.
+ */
+ str = NULL;
+ goto cleanup;
+ }
+ condExpr += len;
+ /*
+ * If the '$' was first char (no quotes), and we are
+ * followed by space, the operator or end of expression,
+ * we are done.
+ */
+ if ((condExpr == start + len) &&
+ (*condExpr == '\0' ||
+ isspace((unsigned char) *condExpr) ||
+ strchr("!=><)", *condExpr))) {
+ goto cleanup;
+ }
+ /*
+ * Nope, we better copy str to buf
+ */
+ for (cp = str; *cp; cp++) {
+ Buf_AddByte(&buf, *cp);
+ }
+ if (*freeIt) {
+ free(*freeIt);
+ *freeIt = NULL;
+ }
+ str = NULL; /* not finished yet */
+ condExpr--; /* don't skip over next char */
+ break;
+ default:
+ if (strictLHS && !qt && *start != '$' &&
+ !isdigit((unsigned char) *start)) {
+ /* lhs must be quoted, a variable reference or number */
+ if (*freeIt) {
+ free(*freeIt);
+ *freeIt = NULL;
+ }
+ str = NULL;
+ goto cleanup;
+ }
+ Buf_AddByte(&buf, *condExpr);
+ break;
+ }
+ }
+ got_str:
+ str = Buf_GetAll(&buf, NULL);
+ *freeIt = str;
+ cleanup:
+ Buf_Destroy(&buf, FALSE);
+ return str;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondToken --
+ * Return the next token from the input.
+ *
+ * Results:
+ * A Token for the next lexical token in the stream.
+ *
+ * Side Effects:
+ * condPushback will be set back to TOK_NONE if it is used.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+compare_expression(Boolean doEval)
+{
+ Token t;
+ char *lhs;
+ char *rhs;
+ char *op;
+ void *lhsFree;
+ void *rhsFree;
+ Boolean lhsQuoted;
+ Boolean rhsQuoted;
+ double left, right;
+
+ t = TOK_ERROR;
+ rhs = NULL;
+ lhsFree = rhsFree = FALSE;
+ lhsQuoted = rhsQuoted = FALSE;
+
+ /*
+ * Parse the variable spec and skip over it, saving its
+ * value in lhs.
+ */
+ lhs = CondGetString(doEval, &lhsQuoted, &lhsFree, lhsStrict);
+ if (!lhs)
+ goto done;
+
+ /*
+ * Skip whitespace to get to the operator
+ */
+ while (isspace((unsigned char) *condExpr))
+ condExpr++;
+
+ /*
+ * Make sure the operator is a valid one. If it isn't a
+ * known relational operator, pretend we got a
+ * != 0 comparison.
+ */
+ op = condExpr;
+ switch (*condExpr) {
+ case '!':
+ case '=':
+ case '<':
+ case '>':
+ if (condExpr[1] == '=') {
+ condExpr += 2;
+ } else {
+ condExpr += 1;
+ }
+ break;
+ default:
+ if (!doEval) {
+ t = TOK_FALSE;
+ goto done;
+ }
+ /* For .ifxxx "..." check for non-empty string. */
+ if (lhsQuoted) {
+ t = lhs[0] != 0;
+ goto done;
+ }
+ /* For .ifxxx <number> compare against zero */
+ if (CondCvtArg(lhs, &left)) {
+ t = left != 0.0;
+ goto done;
+ }
+ /* For .if ${...} check for non-empty string (defProc is ifdef). */
+ if (if_info->form[0] == 0) {
+ t = lhs[0] != 0;
+ goto done;
+ }
+ /* Otherwise action default test ... */
+ t = if_info->defProc(strlen(lhs), lhs) != if_info->doNot;
+ goto done;
+ }
+
+ while (isspace((unsigned char)*condExpr))
+ condExpr++;
+
+ if (*condExpr == '\0') {
+ Parse_Error(PARSE_WARNING,
+ "Missing right-hand-side of operator");
+ goto done;
+ }
+
+ rhs = CondGetString(doEval, &rhsQuoted, &rhsFree, FALSE);
+ if (!rhs)
+ goto done;
+
+ if (rhsQuoted || lhsQuoted) {
+do_string_compare:
+ if (((*op != '!') && (*op != '=')) || (op[1] != '=')) {
+ Parse_Error(PARSE_WARNING,
+ "String comparison operator should be either == or !=");
+ goto done;
+ }
+
+ if (DEBUG(COND)) {
+ fprintf(debug_file, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n",
+ lhs, rhs, op);
+ }
+ /*
+ * Null-terminate rhs and perform the comparison.
+ * t is set to the result.
+ */
+ if (*op == '=') {
+ t = strcmp(lhs, rhs) == 0;
+ } else {
+ t = strcmp(lhs, rhs) != 0;
+ }
+ } else {
+ /*
+ * rhs is either a float or an integer. Convert both the
+ * lhs and the rhs to a double and compare the two.
+ */
+
+ if (!CondCvtArg(lhs, &left) || !CondCvtArg(rhs, &right))
+ goto do_string_compare;
+
+ if (DEBUG(COND)) {
+ fprintf(debug_file, "left = %f, right = %f, op = %.2s\n", left,
+ right, op);
+ }
+ switch(op[0]) {
+ case '!':
+ if (op[1] != '=') {
+ Parse_Error(PARSE_WARNING,
+ "Unknown operator");
+ goto done;
+ }
+ t = (left != right);
+ break;
+ case '=':
+ if (op[1] != '=') {
+ Parse_Error(PARSE_WARNING,
+ "Unknown operator");
+ goto done;
+ }
+ t = (left == right);
+ break;
+ case '<':
+ if (op[1] == '=') {
+ t = (left <= right);
+ } else {
+ t = (left < right);
+ }
+ break;
+ case '>':
+ if (op[1] == '=') {
+ t = (left >= right);
+ } else {
+ t = (left > right);
+ }
+ break;
+ }
+ }
+
+done:
+ free(lhsFree);
+ free(rhsFree);
+ return t;
+}
+
+static int
+get_mpt_arg(char **linePtr, char **argPtr, const char *func MAKE_ATTR_UNUSED)
+{
+ /*
+ * Use Var_Parse to parse the spec in parens and return
+ * TOK_TRUE if the resulting string is empty.
+ */
+ int length;
+ void *freeIt;
+ char *val;
+ char *cp = *linePtr;
+
+ /* We do all the work here and return the result as the length */
+ *argPtr = NULL;
+
+ val = Var_Parse(cp - 1, VAR_CMD, VARF_WANTRES, &length, &freeIt);
+ /*
+ * Advance *linePtr to beyond the closing ). Note that
+ * we subtract one because 'length' is calculated from 'cp - 1'.
+ */
+ *linePtr = cp - 1 + length;
+
+ if (val == var_Error) {
+ free(freeIt);
+ return -1;
+ }
+
+ /* A variable is empty when it just contains spaces... 4/15/92, christos */
+ while (isspace(*(unsigned char *)val))
+ val++;
+
+ /*
+ * For consistency with the other functions we can't generate the
+ * true/false here.
+ */
+ length = *val ? 2 : 1;
+ free(freeIt);
+ return length;
+}
+
+static Boolean
+CondDoEmpty(int arglen, const char *arg MAKE_ATTR_UNUSED)
+{
+ return arglen == 1;
+}
+
+static Token
+compare_function(Boolean doEval)
+{
+ static const struct fn_def {
+ const char *fn_name;
+ int fn_name_len;
+ int (*fn_getarg)(char **, char **, const char *);
+ Boolean (*fn_proc)(int, const char *);
+ } fn_defs[] = {
+ { "defined", 7, CondGetArg, CondDoDefined },
+ { "make", 4, CondGetArg, CondDoMake },
+ { "exists", 6, CondGetArg, CondDoExists },
+ { "empty", 5, get_mpt_arg, CondDoEmpty },
+ { "target", 6, CondGetArg, CondDoTarget },
+ { "commands", 8, CondGetArg, CondDoCommands },
+ { NULL, 0, NULL, NULL },
+ };
+ const struct fn_def *fn_def;
+ Token t;
+ char *arg = NULL;
+ int arglen;
+ char *cp = condExpr;
+ char *cp1;
+
+ for (fn_def = fn_defs; fn_def->fn_name != NULL; fn_def++) {
+ if (!istoken(cp, fn_def->fn_name, fn_def->fn_name_len))
+ continue;
+ cp += fn_def->fn_name_len;
+ /* There can only be whitespace before the '(' */
+ while (isspace(*(unsigned char *)cp))
+ cp++;
+ if (*cp != '(')
+ break;
+
+ arglen = fn_def->fn_getarg(&cp, &arg, fn_def->fn_name);
+ if (arglen <= 0) {
+ condExpr = cp;
+ return arglen < 0 ? TOK_ERROR : TOK_FALSE;
+ }
+ /* Evaluate the argument using the required function. */
+ t = !doEval || fn_def->fn_proc(arglen, arg);
+ free(arg);
+ condExpr = cp;
+ return t;
+ }
+
+ /* Push anything numeric through the compare expression */
+ cp = condExpr;
+ if (isdigit((unsigned char)cp[0]) || strchr("+-", cp[0]))
+ return compare_expression(doEval);
+
+ /*
+ * Most likely we have a naked token to apply the default function to.
+ * However ".if a == b" gets here when the "a" is unquoted and doesn't
+ * start with a '$'. This surprises people.
+ * If what follows the function argument is a '=' or '!' then the syntax
+ * would be invalid if we did "defined(a)" - so instead treat as an
+ * expression.
+ */
+ arglen = CondGetArg(&cp, &arg, NULL);
+ for (cp1 = cp; isspace(*(unsigned char *)cp1); cp1++)
+ continue;
+ if (*cp1 == '=' || *cp1 == '!')
+ return compare_expression(doEval);
+ condExpr = cp;
+
+ /*
+ * Evaluate the argument using the default function.
+ * This path always treats .if as .ifdef. To get here the character
+ * after .if must have been taken literally, so the argument cannot
+ * be empty - even if it contained a variable expansion.
+ */
+ t = !doEval || if_info->defProc(arglen, arg) != if_info->doNot;
+ free(arg);
+ return t;
+}
+
+static Token
+CondToken(Boolean doEval)
+{
+ Token t;
+
+ t = condPushBack;
+ if (t != TOK_NONE) {
+ condPushBack = TOK_NONE;
+ return t;
+ }
+
+ while (*condExpr == ' ' || *condExpr == '\t') {
+ condExpr++;
+ }
+
+ switch (*condExpr) {
+
+ case '(':
+ condExpr++;
+ return TOK_LPAREN;
+
+ case ')':
+ condExpr++;
+ return TOK_RPAREN;
+
+ case '|':
+ if (condExpr[1] == '|') {
+ condExpr++;
+ }
+ condExpr++;
+ return TOK_OR;
+
+ case '&':
+ if (condExpr[1] == '&') {
+ condExpr++;
+ }
+ condExpr++;
+ return TOK_AND;
+
+ case '!':
+ condExpr++;
+ return TOK_NOT;
+
+ case '#':
+ case '\n':
+ case '\0':
+ return TOK_EOF;
+
+ case '"':
+ case '$':
+ return compare_expression(doEval);
+
+ default:
+ return compare_function(doEval);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondT --
+ * Parse a single term in the expression. This consists of a terminal
+ * symbol or TOK_NOT and a terminal symbol (not including the binary
+ * operators):
+ * T -> defined(variable) | make(target) | exists(file) | symbol
+ * T -> ! T | ( E )
+ *
+ * Results:
+ * TOK_TRUE, TOK_FALSE or TOK_ERROR.
+ *
+ * Side Effects:
+ * Tokens are consumed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondT(Boolean doEval)
+{
+ Token t;
+
+ t = CondToken(doEval);
+
+ if (t == TOK_EOF) {
+ /*
+ * If we reached the end of the expression, the expression
+ * is malformed...
+ */
+ t = TOK_ERROR;
+ } else if (t == TOK_LPAREN) {
+ /*
+ * T -> ( E )
+ */
+ t = CondE(doEval);
+ if (t != TOK_ERROR) {
+ if (CondToken(doEval) != TOK_RPAREN) {
+ t = TOK_ERROR;
+ }
+ }
+ } else if (t == TOK_NOT) {
+ t = CondT(doEval);
+ if (t == TOK_TRUE) {
+ t = TOK_FALSE;
+ } else if (t == TOK_FALSE) {
+ t = TOK_TRUE;
+ }
+ }
+ return (t);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondF --
+ * Parse a conjunctive factor (nice name, wot?)
+ * F -> T && F | T
+ *
+ * Results:
+ * TOK_TRUE, TOK_FALSE or TOK_ERROR
+ *
+ * Side Effects:
+ * Tokens are consumed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondF(Boolean doEval)
+{
+ Token l, o;
+
+ l = CondT(doEval);
+ if (l != TOK_ERROR) {
+ o = CondToken(doEval);
+
+ if (o == TOK_AND) {
+ /*
+ * F -> T && F
+ *
+ * If T is TOK_FALSE, the whole thing will be TOK_FALSE, but we have to
+ * parse the r.h.s. anyway (to throw it away).
+ * If T is TOK_TRUE, the result is the r.h.s., be it an TOK_ERROR or no.
+ */
+ if (l == TOK_TRUE) {
+ l = CondF(doEval);
+ } else {
+ (void)CondF(FALSE);
+ }
+ } else {
+ /*
+ * F -> T
+ */
+ CondPushBack(o);
+ }
+ }
+ return (l);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondE --
+ * Main expression production.
+ * E -> F || E | F
+ *
+ * Results:
+ * TOK_TRUE, TOK_FALSE or TOK_ERROR.
+ *
+ * Side Effects:
+ * Tokens are, of course, consumed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondE(Boolean doEval)
+{
+ Token l, o;
+
+ l = CondF(doEval);
+ if (l != TOK_ERROR) {
+ o = CondToken(doEval);
+
+ if (o == TOK_OR) {
+ /*
+ * E -> F || E
+ *
+ * A similar thing occurs for ||, except that here we make sure
+ * the l.h.s. is TOK_FALSE before we bother to evaluate the r.h.s.
+ * Once again, if l is TOK_FALSE, the result is the r.h.s. and once
+ * again if l is TOK_TRUE, we parse the r.h.s. to throw it away.
+ */
+ if (l == TOK_FALSE) {
+ l = CondE(doEval);
+ } else {
+ (void)CondE(FALSE);
+ }
+ } else {
+ /*
+ * E -> F
+ */
+ CondPushBack(o);
+ }
+ }
+ return (l);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Cond_EvalExpression --
+ * Evaluate an expression in the passed line. The expression
+ * consists of &&, ||, !, make(target), defined(variable)
+ * and parenthetical groupings thereof.
+ *
+ * Results:
+ * COND_PARSE if the condition was valid grammatically
+ * COND_INVALID if not a valid conditional.
+ *
+ * (*value) is set to the boolean value of the condition
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Cond_EvalExpression(const struct If *info, char *line, Boolean *value, int eprint, Boolean strictLHS)
+{
+ static const struct If *dflt_info;
+ const struct If *sv_if_info = if_info;
+ char *sv_condExpr = condExpr;
+ Token sv_condPushBack = condPushBack;
+ int rval;
+
+ lhsStrict = strictLHS;
+
+ while (*line == ' ' || *line == '\t')
+ line++;
+
+ if (info == NULL && (info = dflt_info) == NULL) {
+ /* Scan for the entry for .if - it can't be first */
+ for (info = ifs; ; info++)
+ if (info->form[0] == 0)
+ break;
+ dflt_info = info;
+ }
+ assert(info != NULL);
+
+ if_info = info;
+ condExpr = line;
+ condPushBack = TOK_NONE;
+
+ rval = do_Cond_EvalExpression(value);
+
+ if (rval == COND_INVALID && eprint)
+ Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", line);
+
+ if_info = sv_if_info;
+ condExpr = sv_condExpr;
+ condPushBack = sv_condPushBack;
+
+ return rval;
+}
+
+static int
+do_Cond_EvalExpression(Boolean *value)
+{
+
+ switch (CondE(TRUE)) {
+ case TOK_TRUE:
+ if (CondToken(TRUE) == TOK_EOF) {
+ *value = TRUE;
+ return COND_PARSE;
+ }
+ break;
+ case TOK_FALSE:
+ if (CondToken(TRUE) == TOK_EOF) {
+ *value = FALSE;
+ return COND_PARSE;
+ }
+ break;
+ default:
+ case TOK_ERROR:
+ break;
+ }
+
+ return COND_INVALID;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Cond_Eval --
+ * Evaluate the conditional in the passed line. The line
+ * looks like this:
+ * .<cond-type> <expr>
+ * where <cond-type> is any of if, ifmake, ifnmake, ifdef,
+ * ifndef, elif, elifmake, elifnmake, elifdef, elifndef
+ * and <expr> consists of &&, ||, !, make(target), defined(variable)
+ * and parenthetical groupings thereof.
+ *
+ * Input:
+ * line Line to parse
+ *
+ * Results:
+ * COND_PARSE if should parse lines after the conditional
+ * COND_SKIP if should skip lines after the conditional
+ * COND_INVALID if not a valid conditional.
+ *
+ * Side Effects:
+ * None.
+ *
+ * Note that the states IF_ACTIVE and ELSE_ACTIVE are only different in order
+ * to detect splurious .else lines (as are SKIP_TO_ELSE and SKIP_TO_ENDIF)
+ * otherwise .else could be treated as '.elif 1'.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Cond_Eval(char *line)
+{
+#define MAXIF 128 /* maximum depth of .if'ing */
+#define MAXIF_BUMP 32 /* how much to grow by */
+ enum if_states {
+ IF_ACTIVE, /* .if or .elif part active */
+ ELSE_ACTIVE, /* .else part active */
+ SEARCH_FOR_ELIF, /* searching for .elif/else to execute */
+ SKIP_TO_ELSE, /* has been true, but not seen '.else' */
+ SKIP_TO_ENDIF /* nothing else to execute */
+ };
+ static enum if_states *cond_state = NULL;
+ static unsigned int max_if_depth = MAXIF;
+
+ const struct If *ifp;
+ Boolean isElif;
+ Boolean value;
+ int level; /* Level at which to report errors. */
+ enum if_states state;
+
+ level = PARSE_FATAL;
+ if (!cond_state) {
+ cond_state = bmake_malloc(max_if_depth * sizeof(*cond_state));
+ cond_state[0] = IF_ACTIVE;
+ }
+ /* skip leading character (the '.') and any whitespace */
+ for (line++; *line == ' ' || *line == '\t'; line++)
+ continue;
+
+ /* Find what type of if we're dealing with. */
+ if (line[0] == 'e') {
+ if (line[1] != 'l') {
+ if (!istoken(line + 1, "ndif", 4))
+ return COND_INVALID;
+ /* End of conditional section */
+ if (cond_depth == cond_min_depth) {
+ Parse_Error(level, "if-less endif");
+ return COND_PARSE;
+ }
+ /* Return state for previous conditional */
+ cond_depth--;
+ return cond_state[cond_depth] <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP;
+ }
+
+ /* Quite likely this is 'else' or 'elif' */
+ line += 2;
+ if (istoken(line, "se", 2)) {
+ /* It is else... */
+ if (cond_depth == cond_min_depth) {
+ Parse_Error(level, "if-less else");
+ return COND_PARSE;
+ }
+
+ state = cond_state[cond_depth];
+ switch (state) {
+ case SEARCH_FOR_ELIF:
+ state = ELSE_ACTIVE;
+ break;
+ case ELSE_ACTIVE:
+ case SKIP_TO_ENDIF:
+ Parse_Error(PARSE_WARNING, "extra else");
+ /* FALLTHROUGH */
+ default:
+ case IF_ACTIVE:
+ case SKIP_TO_ELSE:
+ state = SKIP_TO_ENDIF;
+ break;
+ }
+ cond_state[cond_depth] = state;
+ return state <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP;
+ }
+ /* Assume for now it is an elif */
+ isElif = TRUE;
+ } else
+ isElif = FALSE;
+
+ if (line[0] != 'i' || line[1] != 'f')
+ /* Not an ifxxx or elifxxx line */
+ return COND_INVALID;
+
+ /*
+ * Figure out what sort of conditional it is -- what its default
+ * function is, etc. -- by looking in the table of valid "ifs"
+ */
+ line += 2;
+ for (ifp = ifs; ; ifp++) {
+ if (ifp->form == NULL)
+ return COND_INVALID;
+ if (istoken(ifp->form, line, ifp->formlen)) {
+ line += ifp->formlen;
+ break;
+ }
+ }
+
+ /* Now we know what sort of 'if' it is... */
+
+ if (isElif) {
+ if (cond_depth == cond_min_depth) {
+ Parse_Error(level, "if-less elif");
+ return COND_PARSE;
+ }
+ state = cond_state[cond_depth];
+ if (state == SKIP_TO_ENDIF || state == ELSE_ACTIVE) {
+ Parse_Error(PARSE_WARNING, "extra elif");
+ cond_state[cond_depth] = SKIP_TO_ENDIF;
+ return COND_SKIP;
+ }
+ if (state != SEARCH_FOR_ELIF) {
+ /* Either just finished the 'true' block, or already SKIP_TO_ELSE */
+ cond_state[cond_depth] = SKIP_TO_ELSE;
+ return COND_SKIP;
+ }
+ } else {
+ /* Normal .if */
+ if (cond_depth + 1 >= max_if_depth) {
+ /*
+ * This is rare, but not impossible.
+ * In meta mode, dirdeps.mk (only runs at level 0)
+ * can need more than the default.
+ */
+ max_if_depth += MAXIF_BUMP;
+ cond_state = bmake_realloc(cond_state, max_if_depth *
+ sizeof(*cond_state));
+ }
+ state = cond_state[cond_depth];
+ cond_depth++;
+ if (state > ELSE_ACTIVE) {
+ /* If we aren't parsing the data, treat as always false */
+ cond_state[cond_depth] = SKIP_TO_ELSE;
+ return COND_SKIP;
+ }
+ }
+
+ /* And evaluate the conditional expresssion */
+ if (Cond_EvalExpression(ifp, line, &value, 1, TRUE) == COND_INVALID) {
+ /* Syntax error in conditional, error message already output. */
+ /* Skip everything to matching .endif */
+ cond_state[cond_depth] = SKIP_TO_ELSE;
+ return COND_SKIP;
+ }
+
+ if (!value) {
+ cond_state[cond_depth] = SEARCH_FOR_ELIF;
+ return COND_SKIP;
+ }
+ cond_state[cond_depth] = IF_ACTIVE;
+ return COND_PARSE;
+}
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Cond_End --
+ * Make sure everything's clean at the end of a makefile.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Parse_Error will be called if open conditionals are around.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Cond_restore_depth(unsigned int saved_depth)
+{
+ int open_conds = cond_depth - cond_min_depth;
+
+ if (open_conds != 0 || saved_depth > cond_depth) {
+ Parse_Error(PARSE_FATAL, "%d open conditional%s", open_conds,
+ open_conds == 1 ? "" : "s");
+ cond_depth = cond_min_depth;
+ }
+
+ cond_min_depth = saved_depth;
+}
+
+unsigned int
+Cond_save_depth(void)
+{
+ int depth = cond_min_depth;
+
+ cond_min_depth = cond_depth;
+ return depth;
+}
diff --git a/usr.bin/make/config.h b/usr.bin/make/config.h
new file mode 100644
index 0000000..06af09c
--- /dev/null
+++ b/usr.bin/make/config.h
@@ -0,0 +1,160 @@
+/* $NetBSD: config.h,v 1.21 2012/03/31 00:12:24 christos Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)config.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)config.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * DEFMAXJOBS
+ * DEFMAXLOCAL
+ * These control the default concurrency. On no occasion will more
+ * than DEFMAXJOBS targets be created at once (locally or remotely)
+ * DEFMAXLOCAL is the highest number of targets which will be
+ * created on the local machine at once. Note that if you set this
+ * to 0, nothing will ever happen...
+ */
+#define DEFMAXJOBS 4
+#define DEFMAXLOCAL 1
+
+/*
+ * INCLUDES
+ * LIBRARIES
+ * These control the handling of the .INCLUDES and .LIBS variables.
+ * If INCLUDES is defined, the .INCLUDES variable will be filled
+ * from the search paths of those suffixes which are marked by
+ * .INCLUDES dependency lines. Similarly for LIBRARIES and .LIBS
+ * See suff.c for more details.
+ */
+#define INCLUDES
+#define LIBRARIES
+
+/*
+ * LIBSUFF
+ * Is the suffix used to denote libraries and is used by the Suff module
+ * to find the search path on which to seek any -l<xx> targets.
+ *
+ * RECHECK
+ * If defined, Make_Update will check a target for its current
+ * modification time after it has been re-made, setting it to the
+ * starting time of the make only if the target still doesn't exist.
+ * Unfortunately, under NFS the modification time often doesn't
+ * get updated in time, so a target will appear to not have been
+ * re-made, causing later targets to appear up-to-date. On systems
+ * that don't have this problem, you should defined this. Under
+ * NFS you probably should not, unless you aren't exporting jobs.
+ */
+#define LIBSUFF ".a"
+#define RECHECK
+
+/*
+ * POSIX
+ * Adhere to the POSIX 1003.2 draft for the make(1) program.
+ * - Use MAKEFLAGS instead of MAKE to pick arguments from the
+ * environment.
+ * - Allow empty command lines if starting with tab.
+ */
+#define POSIX
+
+/*
+ * SYSVINCLUDE
+ * Recognize system V like include directives [include "filename"]
+ * SYSVVARSUB
+ * Recognize system V like ${VAR:x=y} variable substitutions
+ */
+#define SYSVINCLUDE
+#define SYSVVARSUB
+
+/*
+ * GMAKEEXPORT
+ * Recognize gmake like variable export directives [export <VAR>=<VALUE>]
+ */
+#define GMAKEEXPORT
+
+/*
+ * SUNSHCMD
+ * Recognize SunOS and Solaris:
+ * VAR :sh= CMD # Assign VAR to the command substitution of CMD
+ * ${VAR:sh} # Return the command substitution of the value
+ * # of ${VAR}
+ */
+#define SUNSHCMD
+
+/*
+ * USE_IOVEC
+ * We have writev(2)
+ */
+#define USE_IOVEC
+
+#if defined(MAKE_NATIVE) && !defined(__ELF__)
+# ifndef RANLIBMAG
+# define RANLIBMAG "__.SYMDEF"
+# endif
+#endif
diff --git a/usr.bin/make/dir.c b/usr.bin/make/dir.c
new file mode 100644
index 0000000..0062ac0
--- /dev/null
+++ b/usr.bin/make/dir.c
@@ -0,0 +1,1849 @@
+/* $NetBSD: dir.c,v 1.73 2018/07/12 18:03:31 christos Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: dir.c,v 1.73 2018/07/12 18:03:31 christos Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)dir.c 8.2 (Berkeley) 1/2/94";
+#else
+__RCSID("$NetBSD: dir.c,v 1.73 2018/07/12 18:03:31 christos Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * dir.c --
+ * Directory searching using wildcards and/or normal names...
+ * Used both for source wildcarding in the Makefile and for finding
+ * implicit sources.
+ *
+ * The interface for this module is:
+ * Dir_Init Initialize the module.
+ *
+ * Dir_InitCur Set the cur Path.
+ *
+ * Dir_InitDot Set the dot Path.
+ *
+ * Dir_End Cleanup the module.
+ *
+ * Dir_SetPATH Set ${.PATH} to reflect state of dirSearchPath.
+ *
+ * Dir_HasWildcards Returns TRUE if the name given it needs to
+ * be wildcard-expanded.
+ *
+ * Dir_Expand Given a pattern and a path, return a Lst of names
+ * which match the pattern on the search path.
+ *
+ * Dir_FindFile Searches for a file on a given search path.
+ * If it exists, the entire path is returned.
+ * Otherwise NULL is returned.
+ *
+ * Dir_FindHereOrAbove Search for a path in the current directory and
+ * then all the directories above it in turn until
+ * the path is found or we reach the root ("/").
+ *
+ * Dir_MTime Return the modification time of a node. The file
+ * is searched for along the default search path.
+ * The path and mtime fields of the node are filled
+ * in.
+ *
+ * Dir_AddDir Add a directory to a search path.
+ *
+ * Dir_MakeFlags Given a search path and a command flag, create
+ * a string with each of the directories in the path
+ * preceded by the command flag and all of them
+ * separated by a space.
+ *
+ * Dir_Destroy Destroy an element of a search path. Frees up all
+ * things that can be freed for the element as long
+ * as the element is no longer referenced by any other
+ * search path.
+ * Dir_ClearPath Resets a search path to the empty list.
+ *
+ * For debugging:
+ * Dir_PrintDirectories Print stats about the directory cache.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+
+/*
+ * A search path consists of a Lst of Path structures. A Path structure
+ * has in it the name of the directory and a hash table of all the files
+ * in the directory. This is used to cut down on the number of system
+ * calls necessary to find implicit dependents and their like. Since
+ * these searches are made before any actions are taken, we need not
+ * worry about the directory changing due to creation commands. If this
+ * hampers the style of some makefiles, they must be changed.
+ *
+ * A list of all previously-read directories is kept in the
+ * openDirectories Lst. This list is checked first before a directory
+ * is opened.
+ *
+ * The need for the caching of whole directories is brought about by
+ * the multi-level transformation code in suff.c, which tends to search
+ * for far more files than regular make does. In the initial
+ * implementation, the amount of time spent performing "stat" calls was
+ * truly astronomical. The problem with hashing at the start is,
+ * of course, that pmake doesn't then detect changes to these directories
+ * during the course of the make. Three possibilities suggest themselves:
+ *
+ * 1) just use stat to test for a file's existence. As mentioned
+ * above, this is very inefficient due to the number of checks
+ * engendered by the multi-level transformation code.
+ * 2) use readdir() and company to search the directories, keeping
+ * them open between checks. I have tried this and while it
+ * didn't slow down the process too much, it could severely
+ * affect the amount of parallelism available as each directory
+ * open would take another file descriptor out of play for
+ * handling I/O for another job. Given that it is only recently
+ * that UNIX OS's have taken to allowing more than 20 or 32
+ * file descriptors for a process, this doesn't seem acceptable
+ * to me.
+ * 3) record the mtime of the directory in the Path structure and
+ * verify the directory hasn't changed since the contents were
+ * hashed. This will catch the creation or deletion of files,
+ * but not the updating of files. However, since it is the
+ * creation and deletion that is the problem, this could be
+ * a good thing to do. Unfortunately, if the directory (say ".")
+ * were fairly large and changed fairly frequently, the constant
+ * rehashing could seriously degrade performance. It might be
+ * good in such cases to keep track of the number of rehashes
+ * and if the number goes over a (small) limit, resort to using
+ * stat in its place.
+ *
+ * An additional thing to consider is that pmake is used primarily
+ * to create C programs and until recently pcc-based compilers refused
+ * to allow you to specify where the resulting object file should be
+ * placed. This forced all objects to be created in the current
+ * directory. This isn't meant as a full excuse, just an explanation of
+ * some of the reasons for the caching used here.
+ *
+ * One more note: the location of a target's file is only performed
+ * on the downward traversal of the graph and then only for terminal
+ * nodes in the graph. This could be construed as wrong in some cases,
+ * but prevents inadvertent modification of files when the "installed"
+ * directory for a file is provided in the search path.
+ *
+ * Another data structure maintained by this module is an mtime
+ * cache used when the searching of cached directories fails to find
+ * a file. In the past, Dir_FindFile would simply perform an access()
+ * call in such a case to determine if the file could be found using
+ * just the name given. When this hit, however, all that was gained
+ * was the knowledge that the file existed. Given that an access() is
+ * essentially a stat() without the copyout() call, and that the same
+ * filesystem overhead would have to be incurred in Dir_MTime, it made
+ * sense to replace the access() with a stat() and record the mtime
+ * in a cache for when Dir_MTime was actually called.
+ */
+
+Lst dirSearchPath; /* main search path */
+
+static Lst openDirectories; /* the list of all open directories */
+
+/*
+ * Variables for gathering statistics on the efficiency of the hashing
+ * mechanism.
+ */
+static int hits, /* Found in directory cache */
+ misses, /* Sad, but not evil misses */
+ nearmisses, /* Found under search path */
+ bigmisses; /* Sought by itself */
+
+static Path *dot; /* contents of current directory */
+static Path *cur; /* contents of current directory, if not dot */
+static Path *dotLast; /* a fake path entry indicating we need to
+ * look for . last */
+static Hash_Table mtimes; /* Results of doing a last-resort stat in
+ * Dir_FindFile -- if we have to go to the
+ * system to find the file, we might as well
+ * have its mtime on record. XXX: If this is done
+ * way early, there's a chance other rules will
+ * have already updated the file, in which case
+ * we'll update it again. Generally, there won't
+ * be two rules to update a single file, so this
+ * should be ok, but... */
+
+static Hash_Table lmtimes; /* same as mtimes but for lstat */
+
+static int DirFindName(const void *, const void *);
+static int DirMatchFiles(const char *, Path *, Lst);
+static void DirExpandCurly(const char *, const char *, Lst, Lst);
+static void DirExpandInt(const char *, Lst, Lst);
+static int DirPrintWord(void *, void *);
+static int DirPrintDir(void *, void *);
+static char *DirLookup(Path *, const char *, const char *, Boolean);
+static char *DirLookupSubdir(Path *, const char *);
+static char *DirFindDot(Boolean, const char *, const char *);
+static char *DirLookupAbs(Path *, const char *, const char *);
+
+
+/*
+ * We use stat(2) a lot, cache the results
+ * mtime and mode are all we care about.
+ */
+struct cache_st {
+ time_t mtime;
+ mode_t mode;
+};
+
+/* minimize changes below */
+#define CST_LSTAT 1
+#define CST_UPDATE 2
+
+static int
+cached_stats(Hash_Table *htp, const char *pathname, struct stat *st, int flags)
+{
+ Hash_Entry *entry;
+ struct cache_st *cst;
+ int rc;
+
+ if (!pathname || !pathname[0])
+ return -1;
+
+ entry = Hash_FindEntry(htp, pathname);
+
+ if (entry && (flags & CST_UPDATE) == 0) {
+ cst = entry->clientPtr;
+
+ memset(st, 0, sizeof(*st));
+ st->st_mtime = cst->mtime;
+ st->st_mode = cst->mode;
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "Using cached time %s for %s\n",
+ Targ_FmtTime(st->st_mtime), pathname);
+ }
+ return 0;
+ }
+
+ rc = (flags & CST_LSTAT) ? lstat(pathname, st) : stat(pathname, st);
+ if (rc == -1)
+ return -1;
+
+ if (st->st_mtime == 0)
+ st->st_mtime = 1; /* avoid confusion with missing file */
+
+ if (!entry)
+ entry = Hash_CreateEntry(htp, pathname, NULL);
+ if (!entry->clientPtr)
+ entry->clientPtr = bmake_malloc(sizeof(*cst));
+ cst = entry->clientPtr;
+ cst->mtime = st->st_mtime;
+ cst->mode = st->st_mode;
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " Caching %s for %s\n",
+ Targ_FmtTime(st->st_mtime), pathname);
+ }
+
+ return 0;
+}
+
+int
+cached_stat(const char *pathname, void *st)
+{
+ return cached_stats(&mtimes, pathname, st, 0);
+}
+
+int
+cached_lstat(const char *pathname, void *st)
+{
+ return cached_stats(&lmtimes, pathname, st, CST_LSTAT);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Init --
+ * initialize things for this module
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * some directories may be opened.
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Init(const char *cdname)
+{
+ if (!cdname) {
+ dirSearchPath = Lst_Init(FALSE);
+ openDirectories = Lst_Init(FALSE);
+ Hash_InitTable(&mtimes, 0);
+ Hash_InitTable(&lmtimes, 0);
+ return;
+ }
+ Dir_InitCur(cdname);
+
+ dotLast = bmake_malloc(sizeof(Path));
+ dotLast->refCount = 1;
+ dotLast->hits = 0;
+ dotLast->name = bmake_strdup(".DOTLAST");
+ Hash_InitTable(&dotLast->files, -1);
+}
+
+/*
+ * Called by Dir_Init() and whenever .CURDIR is assigned to.
+ */
+void
+Dir_InitCur(const char *cdname)
+{
+ Path *p;
+
+ if (cdname != NULL) {
+ /*
+ * Our build directory is not the same as our source directory.
+ * Keep this one around too.
+ */
+ if ((p = Dir_AddDir(NULL, cdname))) {
+ p->refCount += 1;
+ if (cur && cur != p) {
+ /*
+ * We've been here before, cleanup.
+ */
+ cur->refCount -= 1;
+ Dir_Destroy(cur);
+ }
+ cur = p;
+ }
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_InitDot --
+ * (re)initialize "dot" (current/object directory) path hash
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * some directories may be opened.
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_InitDot(void)
+{
+ if (dot != NULL) {
+ LstNode ln;
+
+ /* Remove old entry from openDirectories, but do not destroy. */
+ ln = Lst_Member(openDirectories, dot);
+ (void)Lst_Remove(openDirectories, ln);
+ }
+
+ dot = Dir_AddDir(NULL, ".");
+
+ if (dot == NULL) {
+ Error("Cannot open `.' (%s)", strerror(errno));
+ exit(1);
+ }
+
+ /*
+ * We always need to have dot around, so we increment its reference count
+ * to make sure it's not destroyed.
+ */
+ dot->refCount += 1;
+ Dir_SetPATH(); /* initialize */
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_End --
+ * cleanup things for this module
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * none
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_End(void)
+{
+#ifdef CLEANUP
+ if (cur) {
+ cur->refCount -= 1;
+ Dir_Destroy(cur);
+ }
+ dot->refCount -= 1;
+ dotLast->refCount -= 1;
+ Dir_Destroy(dotLast);
+ Dir_Destroy(dot);
+ Dir_ClearPath(dirSearchPath);
+ Lst_Destroy(dirSearchPath, NULL);
+ Dir_ClearPath(openDirectories);
+ Lst_Destroy(openDirectories, NULL);
+ Hash_DeleteTable(&mtimes);
+#endif
+}
+
+/*
+ * We want ${.PATH} to indicate the order in which we will actually
+ * search, so we rebuild it after any .PATH: target.
+ * This is the simplest way to deal with the effect of .DOTLAST.
+ */
+void
+Dir_SetPATH(void)
+{
+ LstNode ln; /* a list element */
+ Path *p;
+ Boolean hasLastDot = FALSE; /* true we should search dot last */
+
+ Var_Delete(".PATH", VAR_GLOBAL);
+
+ if (Lst_Open(dirSearchPath) == SUCCESS) {
+ if ((ln = Lst_First(dirSearchPath)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ if (p == dotLast) {
+ hasLastDot = TRUE;
+ Var_Append(".PATH", dotLast->name, VAR_GLOBAL);
+ }
+ }
+
+ if (!hasLastDot) {
+ if (dot)
+ Var_Append(".PATH", dot->name, VAR_GLOBAL);
+ if (cur)
+ Var_Append(".PATH", cur->name, VAR_GLOBAL);
+ }
+
+ while ((ln = Lst_Next(dirSearchPath)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ if (p == dotLast)
+ continue;
+ if (p == dot && hasLastDot)
+ continue;
+ Var_Append(".PATH", p->name, VAR_GLOBAL);
+ }
+
+ if (hasLastDot) {
+ if (dot)
+ Var_Append(".PATH", dot->name, VAR_GLOBAL);
+ if (cur)
+ Var_Append(".PATH", cur->name, VAR_GLOBAL);
+ }
+ Lst_Close(dirSearchPath);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirFindName --
+ * See if the Path structure describes the same directory as the
+ * given one by comparing their names. Called from Dir_AddDir via
+ * Lst_Find when searching the list of open directories.
+ *
+ * Input:
+ * p Current name
+ * dname Desired name
+ *
+ * Results:
+ * 0 if it is the same. Non-zero otherwise
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static int
+DirFindName(const void *p, const void *dname)
+{
+ return (strcmp(((const Path *)p)->name, dname));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_HasWildcards --
+ * see if the given name has any wildcard characters in it
+ * be careful not to expand unmatching brackets or braces.
+ * XXX: This code is not 100% correct. ([^]] fails etc.)
+ * I really don't think that make(1) should be expanding
+ * patterns, because then you have to set a mechanism for
+ * escaping the expansion!
+ *
+ * Input:
+ * name name to check
+ *
+ * Results:
+ * returns TRUE if the word should be expanded, FALSE otherwise
+ *
+ * Side Effects:
+ * none
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Dir_HasWildcards(char *name)
+{
+ char *cp;
+ int wild = 0, brace = 0, bracket = 0;
+
+ for (cp = name; *cp; cp++) {
+ switch(*cp) {
+ case '{':
+ brace++;
+ wild = 1;
+ break;
+ case '}':
+ brace--;
+ break;
+ case '[':
+ bracket++;
+ wild = 1;
+ break;
+ case ']':
+ bracket--;
+ break;
+ case '?':
+ case '*':
+ wild = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ return wild && bracket == 0 && brace == 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirMatchFiles --
+ * Given a pattern and a Path structure, see if any files
+ * match the pattern and add their names to the 'expansions' list if
+ * any do. This is incomplete -- it doesn't take care of patterns like
+ * src / *src / *.c properly (just *.c on any of the directories), but it
+ * will do for now.
+ *
+ * Input:
+ * pattern Pattern to look for
+ * p Directory to search
+ * expansion Place to store the results
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * File names are added to the expansions lst. The directory will be
+ * fully hashed when this is done.
+ *-----------------------------------------------------------------------
+ */
+static int
+DirMatchFiles(const char *pattern, Path *p, Lst expansions)
+{
+ Hash_Search search; /* Index into the directory's table */
+ Hash_Entry *entry; /* Current entry in the table */
+ Boolean isDot; /* TRUE if the directory being searched is . */
+
+ isDot = (*p->name == '.' && p->name[1] == '\0');
+
+ for (entry = Hash_EnumFirst(&p->files, &search);
+ entry != NULL;
+ entry = Hash_EnumNext(&search))
+ {
+ /*
+ * See if the file matches the given pattern. Note we follow the UNIX
+ * convention that dot files will only be found if the pattern
+ * begins with a dot (note also that as a side effect of the hashing
+ * scheme, .* won't match . or .. since they aren't hashed).
+ */
+ if (Str_Match(entry->name, pattern) &&
+ ((entry->name[0] != '.') ||
+ (pattern[0] == '.')))
+ {
+ (void)Lst_AtEnd(expansions,
+ (isDot ? bmake_strdup(entry->name) :
+ str_concat(p->name, entry->name,
+ STR_ADDSLASH)));
+ }
+ }
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirExpandCurly --
+ * Expand curly braces like the C shell. Does this recursively.
+ * Note the special case: if after the piece of the curly brace is
+ * done there are no wildcard characters in the result, the result is
+ * placed on the list WITHOUT CHECKING FOR ITS EXISTENCE.
+ *
+ * Input:
+ * word Entire word to expand
+ * brace First curly brace in it
+ * path Search path to use
+ * expansions Place to store the expansions
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The given list is filled with the expansions...
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+DirExpandCurly(const char *word, const char *brace, Lst path, Lst expansions)
+{
+ const char *end; /* Character after the closing brace */
+ const char *cp; /* Current position in brace clause */
+ const char *start; /* Start of current piece of brace clause */
+ int bracelevel; /* Number of braces we've seen. If we see a
+ * right brace when this is 0, we've hit the
+ * end of the clause. */
+ char *file; /* Current expansion */
+ int otherLen; /* The length of the other pieces of the
+ * expansion (chars before and after the
+ * clause in 'word') */
+ char *cp2; /* Pointer for checking for wildcards in
+ * expansion before calling Dir_Expand */
+
+ start = brace+1;
+
+ /*
+ * Find the end of the brace clause first, being wary of nested brace
+ * clauses.
+ */
+ for (end = start, bracelevel = 0; *end != '\0'; end++) {
+ if (*end == '{') {
+ bracelevel++;
+ } else if ((*end == '}') && (bracelevel-- == 0)) {
+ break;
+ }
+ }
+ if (*end == '\0') {
+ Error("Unterminated {} clause \"%s\"", start);
+ return;
+ } else {
+ end++;
+ }
+ otherLen = brace - word + strlen(end);
+
+ for (cp = start; cp < end; cp++) {
+ /*
+ * Find the end of this piece of the clause.
+ */
+ bracelevel = 0;
+ while (*cp != ',') {
+ if (*cp == '{') {
+ bracelevel++;
+ } else if ((*cp == '}') && (bracelevel-- <= 0)) {
+ break;
+ }
+ cp++;
+ }
+ /*
+ * Allocate room for the combination and install the three pieces.
+ */
+ file = bmake_malloc(otherLen + cp - start + 1);
+ if (brace != word) {
+ strncpy(file, word, brace-word);
+ }
+ if (cp != start) {
+ strncpy(&file[brace-word], start, cp-start);
+ }
+ strcpy(&file[(brace-word)+(cp-start)], end);
+
+ /*
+ * See if the result has any wildcards in it. If we find one, call
+ * Dir_Expand right away, telling it to place the result on our list
+ * of expansions.
+ */
+ for (cp2 = file; *cp2 != '\0'; cp2++) {
+ switch(*cp2) {
+ case '*':
+ case '?':
+ case '{':
+ case '[':
+ Dir_Expand(file, path, expansions);
+ goto next;
+ }
+ }
+ if (*cp2 == '\0') {
+ /*
+ * Hit the end w/o finding any wildcards, so stick the expansion
+ * on the end of the list.
+ */
+ (void)Lst_AtEnd(expansions, file);
+ } else {
+ next:
+ free(file);
+ }
+ start = cp+1;
+ }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirExpandInt --
+ * Internal expand routine. Passes through the directories in the
+ * path one by one, calling DirMatchFiles for each. NOTE: This still
+ * doesn't handle patterns in directories...
+ *
+ * Input:
+ * word Word to expand
+ * path Path on which to look
+ * expansions Place to store the result
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Things are added to the expansions list.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+DirExpandInt(const char *word, Lst path, Lst expansions)
+{
+ LstNode ln; /* Current node */
+ Path *p; /* Directory in the node */
+
+ if (Lst_Open(path) == SUCCESS) {
+ while ((ln = Lst_Next(path)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ DirMatchFiles(word, p, expansions);
+ }
+ Lst_Close(path);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirPrintWord --
+ * Print a word in the list of expansions. Callback for Dir_Expand
+ * when DEBUG(DIR), via Lst_ForEach.
+ *
+ * Results:
+ * === 0
+ *
+ * Side Effects:
+ * The passed word is printed, followed by a space.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+DirPrintWord(void *word, void *dummy MAKE_ATTR_UNUSED)
+{
+ fprintf(debug_file, "%s ", (char *)word);
+
+ return 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Expand --
+ * Expand the given word into a list of words by globbing it looking
+ * in the directories on the given search path.
+ *
+ * Input:
+ * word the word to expand
+ * path the list of directories in which to find the
+ * resulting files
+ * expansions the list on which to place the results
+ *
+ * Results:
+ * A list of words consisting of the files which exist along the search
+ * path matching the given pattern.
+ *
+ * Side Effects:
+ * Directories may be opened. Who knows?
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Expand(const char *word, Lst path, Lst expansions)
+{
+ const char *cp;
+
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "Expanding \"%s\"... ", word);
+ }
+
+ cp = strchr(word, '{');
+ if (cp) {
+ DirExpandCurly(word, cp, path, expansions);
+ } else {
+ cp = strchr(word, '/');
+ if (cp) {
+ /*
+ * The thing has a directory component -- find the first wildcard
+ * in the string.
+ */
+ for (cp = word; *cp; cp++) {
+ if (*cp == '?' || *cp == '[' || *cp == '*' || *cp == '{') {
+ break;
+ }
+ }
+ if (*cp == '{') {
+ /*
+ * This one will be fun.
+ */
+ DirExpandCurly(word, cp, path, expansions);
+ return;
+ } else if (*cp != '\0') {
+ /*
+ * Back up to the start of the component
+ */
+ char *dirpath;
+
+ while (cp > word && *cp != '/') {
+ cp--;
+ }
+ if (cp != word) {
+ char sc;
+ /*
+ * If the glob isn't in the first component, try and find
+ * all the components up to the one with a wildcard.
+ */
+ sc = cp[1];
+ ((char *)UNCONST(cp))[1] = '\0';
+ dirpath = Dir_FindFile(word, path);
+ ((char *)UNCONST(cp))[1] = sc;
+ /*
+ * dirpath is null if can't find the leading component
+ * XXX: Dir_FindFile won't find internal components.
+ * i.e. if the path contains ../Etc/Object and we're
+ * looking for Etc, it won't be found. Ah well.
+ * Probably not important.
+ */
+ if (dirpath != NULL) {
+ char *dp = &dirpath[strlen(dirpath) - 1];
+ if (*dp == '/')
+ *dp = '\0';
+ path = Lst_Init(FALSE);
+ (void)Dir_AddDir(path, dirpath);
+ DirExpandInt(cp+1, path, expansions);
+ Lst_Destroy(path, NULL);
+ }
+ } else {
+ /*
+ * Start the search from the local directory
+ */
+ DirExpandInt(word, path, expansions);
+ }
+ } else {
+ /*
+ * Return the file -- this should never happen.
+ */
+ DirExpandInt(word, path, expansions);
+ }
+ } else {
+ /*
+ * First the files in dot
+ */
+ DirMatchFiles(word, dot, expansions);
+
+ /*
+ * Then the files in every other directory on the path.
+ */
+ DirExpandInt(word, path, expansions);
+ }
+ }
+ if (DEBUG(DIR)) {
+ Lst_ForEach(expansions, DirPrintWord, NULL);
+ fprintf(debug_file, "\n");
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirLookup --
+ * Find if the file with the given name exists in the given path.
+ *
+ * Results:
+ * The path to the file or NULL. This path is guaranteed to be in a
+ * different part of memory than name and so may be safely free'd.
+ *
+ * Side Effects:
+ * None.
+ *-----------------------------------------------------------------------
+ */
+static char *
+DirLookup(Path *p, const char *name MAKE_ATTR_UNUSED, const char *cp,
+ Boolean hasSlash MAKE_ATTR_UNUSED)
+{
+ char *file; /* the current filename to check */
+
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " %s ...\n", p->name);
+ }
+
+ if (Hash_FindEntry(&p->files, cp) == NULL)
+ return NULL;
+
+ file = str_concat(p->name, cp, STR_ADDSLASH);
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " returning %s\n", file);
+ }
+ p->hits += 1;
+ hits += 1;
+ return file;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirLookupSubdir --
+ * Find if the file with the given name exists in the given path.
+ *
+ * Results:
+ * The path to the file or NULL. This path is guaranteed to be in a
+ * different part of memory than name and so may be safely free'd.
+ *
+ * Side Effects:
+ * If the file is found, it is added in the modification times hash
+ * table.
+ *-----------------------------------------------------------------------
+ */
+static char *
+DirLookupSubdir(Path *p, const char *name)
+{
+ struct stat stb; /* Buffer for stat, if necessary */
+ char *file; /* the current filename to check */
+
+ if (p != dot) {
+ file = str_concat(p->name, name, STR_ADDSLASH);
+ } else {
+ /*
+ * Checking in dot -- DON'T put a leading ./ on the thing.
+ */
+ file = bmake_strdup(name);
+ }
+
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "checking %s ...\n", file);
+ }
+
+ if (cached_stat(file, &stb) == 0) {
+ nearmisses += 1;
+ return (file);
+ }
+ free(file);
+ return NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirLookupAbs --
+ * Find if the file with the given name exists in the given path.
+ *
+ * Results:
+ * The path to the file, the empty string or NULL. If the file is
+ * the empty string, the search should be terminated.
+ * This path is guaranteed to be in a different part of memory
+ * than name and so may be safely free'd.
+ *
+ * Side Effects:
+ * None.
+ *-----------------------------------------------------------------------
+ */
+static char *
+DirLookupAbs(Path *p, const char *name, const char *cp)
+{
+ char *p1; /* pointer into p->name */
+ const char *p2; /* pointer into name */
+
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " %s ...\n", p->name);
+ }
+
+ /*
+ * If the file has a leading path component and that component
+ * exactly matches the entire name of the current search
+ * directory, we can attempt another cache lookup. And if we don't
+ * have a hit, we can safely assume the file does not exist at all.
+ */
+ for (p1 = p->name, p2 = name; *p1 && *p1 == *p2; p1++, p2++) {
+ continue;
+ }
+ if (*p1 != '\0' || p2 != cp - 1) {
+ return NULL;
+ }
+
+ if (Hash_FindEntry(&p->files, cp) == NULL) {
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " must be here but isn't -- returning\n");
+ }
+ /* Return empty string: terminates search */
+ return bmake_strdup("");
+ }
+
+ p->hits += 1;
+ hits += 1;
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " returning %s\n", name);
+ }
+ return (bmake_strdup(name));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirFindDot --
+ * Find the file given on "." or curdir
+ *
+ * Results:
+ * The path to the file or NULL. This path is guaranteed to be in a
+ * different part of memory than name and so may be safely free'd.
+ *
+ * Side Effects:
+ * Hit counts change
+ *-----------------------------------------------------------------------
+ */
+static char *
+DirFindDot(Boolean hasSlash MAKE_ATTR_UNUSED, const char *name, const char *cp)
+{
+
+ if (Hash_FindEntry(&dot->files, cp) != NULL) {
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " in '.'\n");
+ }
+ hits += 1;
+ dot->hits += 1;
+ return (bmake_strdup(name));
+ }
+ if (cur &&
+ Hash_FindEntry(&cur->files, cp) != NULL) {
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " in ${.CURDIR} = %s\n", cur->name);
+ }
+ hits += 1;
+ cur->hits += 1;
+ return str_concat(cur->name, cp, STR_ADDSLASH);
+ }
+
+ return NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_FindFile --
+ * Find the file with the given name along the given search path.
+ *
+ * Input:
+ * name the file to find
+ * path the Lst of directories to search
+ *
+ * Results:
+ * The path to the file or NULL. This path is guaranteed to be in a
+ * different part of memory than name and so may be safely free'd.
+ *
+ * Side Effects:
+ * If the file is found in a directory which is not on the path
+ * already (either 'name' is absolute or it is a relative path
+ * [ dir1/.../dirn/file ] which exists below one of the directories
+ * already on the search path), its directory is added to the end
+ * of the path on the assumption that there will be more files in
+ * that directory later on. Sometimes this is true. Sometimes not.
+ *-----------------------------------------------------------------------
+ */
+char *
+Dir_FindFile(const char *name, Lst path)
+{
+ LstNode ln; /* a list element */
+ char *file; /* the current filename to check */
+ Path *p; /* current path member */
+ const char *cp; /* Terminal name of file */
+ Boolean hasLastDot = FALSE; /* true we should search dot last */
+ Boolean hasSlash; /* true if 'name' contains a / */
+ struct stat stb; /* Buffer for stat, if necessary */
+ const char *trailing_dot = ".";
+
+ /*
+ * Find the final component of the name and note whether it has a
+ * slash in it (the name, I mean)
+ */
+ cp = strrchr(name, '/');
+ if (cp) {
+ hasSlash = TRUE;
+ cp += 1;
+ } else {
+ hasSlash = FALSE;
+ cp = name;
+ }
+
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "Searching for %s ...", name);
+ }
+
+ if (Lst_Open(path) == FAILURE) {
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "couldn't open path, file not found\n");
+ }
+ misses += 1;
+ return NULL;
+ }
+
+ if ((ln = Lst_First(path)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ if (p == dotLast) {
+ hasLastDot = TRUE;
+ if (DEBUG(DIR))
+ fprintf(debug_file, "[dot last]...");
+ }
+ }
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "\n");
+ }
+
+ /*
+ * If there's no leading directory components or if the leading
+ * directory component is exactly `./', consult the cached contents
+ * of each of the directories on the search path.
+ */
+ if (!hasSlash || (cp - name == 2 && *name == '.')) {
+ /*
+ * We look through all the directories on the path seeking one which
+ * contains the final component of the given name. If such a beast
+ * is found, we concatenate the directory name and the final
+ * component and return the resulting string. If we don't find any
+ * such thing, we go on to phase two...
+ *
+ * No matter what, we always look for the file in the current
+ * directory before anywhere else (unless we found the magic
+ * DOTLAST path, in which case we search it last) and we *do not*
+ * add the ./ to it if it exists.
+ * This is so there are no conflicts between what the user
+ * specifies (fish.c) and what pmake finds (./fish.c).
+ */
+ if (!hasLastDot &&
+ (file = DirFindDot(hasSlash, name, cp)) != NULL) {
+ Lst_Close(path);
+ return file;
+ }
+
+ while ((ln = Lst_Next(path)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ if (p == dotLast)
+ continue;
+ if ((file = DirLookup(p, name, cp, hasSlash)) != NULL) {
+ Lst_Close(path);
+ return file;
+ }
+ }
+
+ if (hasLastDot &&
+ (file = DirFindDot(hasSlash, name, cp)) != NULL) {
+ Lst_Close(path);
+ return file;
+ }
+ }
+ Lst_Close(path);
+
+ /*
+ * We didn't find the file on any directory in the search path.
+ * If the name doesn't contain a slash, that means it doesn't exist.
+ * If it *does* contain a slash, however, there is still hope: it
+ * could be in a subdirectory of one of the members of the search
+ * path. (eg. /usr/include and sys/types.h. The above search would
+ * fail to turn up types.h in /usr/include, but it *is* in
+ * /usr/include/sys/types.h).
+ * [ This no longer applies: If we find such a beast, we assume there
+ * will be more (what else can we assume?) and add all but the last
+ * component of the resulting name onto the search path (at the
+ * end).]
+ * This phase is only performed if the file is *not* absolute.
+ */
+ if (!hasSlash) {
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " failed.\n");
+ }
+ misses += 1;
+ return NULL;
+ }
+
+ if (*cp == '\0') {
+ /* we were given a trailing "/" */
+ cp = trailing_dot;
+ }
+
+ if (name[0] != '/') {
+ Boolean checkedDot = FALSE;
+
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " Trying subdirectories...\n");
+ }
+
+ if (!hasLastDot) {
+ if (dot) {
+ checkedDot = TRUE;
+ if ((file = DirLookupSubdir(dot, name)) != NULL)
+ return file;
+ }
+ if (cur && (file = DirLookupSubdir(cur, name)) != NULL)
+ return file;
+ }
+
+ (void)Lst_Open(path);
+ while ((ln = Lst_Next(path)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ if (p == dotLast)
+ continue;
+ if (p == dot) {
+ if (checkedDot)
+ continue;
+ checkedDot = TRUE;
+ }
+ if ((file = DirLookupSubdir(p, name)) != NULL) {
+ Lst_Close(path);
+ return file;
+ }
+ }
+ Lst_Close(path);
+
+ if (hasLastDot) {
+ if (dot && !checkedDot) {
+ checkedDot = TRUE;
+ if ((file = DirLookupSubdir(dot, name)) != NULL)
+ return file;
+ }
+ if (cur && (file = DirLookupSubdir(cur, name)) != NULL)
+ return file;
+ }
+
+ if (checkedDot) {
+ /*
+ * Already checked by the given name, since . was in the path,
+ * so no point in proceeding...
+ */
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " Checked . already, returning NULL\n");
+ }
+ return NULL;
+ }
+
+ } else { /* name[0] == '/' */
+
+ /*
+ * For absolute names, compare directory path prefix against the
+ * the directory path of each member on the search path for an exact
+ * match. If we have an exact match on any member of the search path,
+ * use the cached contents of that member to lookup the final file
+ * component. If that lookup fails we can safely assume that the
+ * file does not exist at all. This is signified by DirLookupAbs()
+ * returning an empty string.
+ */
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " Trying exact path matches...\n");
+ }
+
+ if (!hasLastDot && cur && ((file = DirLookupAbs(cur, name, cp))
+ != NULL)) {
+ if (file[0] == '\0') {
+ free(file);
+ return NULL;
+ }
+ return file;
+ }
+
+ (void)Lst_Open(path);
+ while ((ln = Lst_Next(path)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ if (p == dotLast)
+ continue;
+ if ((file = DirLookupAbs(p, name, cp)) != NULL) {
+ Lst_Close(path);
+ if (file[0] == '\0') {
+ free(file);
+ return NULL;
+ }
+ return file;
+ }
+ }
+ Lst_Close(path);
+
+ if (hasLastDot && cur && ((file = DirLookupAbs(cur, name, cp))
+ != NULL)) {
+ if (file[0] == '\0') {
+ free(file);
+ return NULL;
+ }
+ return file;
+ }
+ }
+
+ /*
+ * Didn't find it that way, either. Sigh. Phase 3. Add its directory
+ * onto the search path in any case, just in case, then look for the
+ * thing in the hash table. If we find it, grand. We return a new
+ * copy of the name. Otherwise we sadly return a NULL pointer. Sigh.
+ * Note that if the directory holding the file doesn't exist, this will
+ * do an extra search of the final directory on the path. Unless something
+ * weird happens, this search won't succeed and life will be groovy.
+ *
+ * Sigh. We cannot add the directory onto the search path because
+ * of this amusing case:
+ * $(INSTALLDIR)/$(FILE): $(FILE)
+ *
+ * $(FILE) exists in $(INSTALLDIR) but not in the current one.
+ * When searching for $(FILE), we will find it in $(INSTALLDIR)
+ * b/c we added it here. This is not good...
+ */
+#ifdef notdef
+ if (cp == traling_dot) {
+ cp = strrchr(name, '/');
+ cp += 1;
+ }
+ cp[-1] = '\0';
+ (void)Dir_AddDir(path, name);
+ cp[-1] = '/';
+
+ bigmisses += 1;
+ ln = Lst_Last(path);
+ if (ln == NULL) {
+ return NULL;
+ } else {
+ p = (Path *)Lst_Datum(ln);
+ }
+
+ if (Hash_FindEntry(&p->files, cp) != NULL) {
+ return (bmake_strdup(name));
+ } else {
+ return NULL;
+ }
+#else /* !notdef */
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " Looking for \"%s\" ...\n", name);
+ }
+
+ bigmisses += 1;
+ if (cached_stat(name, &stb) == 0) {
+ return (bmake_strdup(name));
+ }
+
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, " failed. Returning NULL\n");
+ }
+ return NULL;
+#endif /* notdef */
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_FindHereOrAbove --
+ * search for a path starting at a given directory and then working
+ * our way up towards the root.
+ *
+ * Input:
+ * here starting directory
+ * search_path the path we are looking for
+ * result the result of a successful search is placed here
+ * rlen the length of the result buffer
+ * (typically MAXPATHLEN + 1)
+ *
+ * Results:
+ * 0 on failure, 1 on success [in which case the found path is put
+ * in the result buffer].
+ *
+ * Side Effects:
+ *-----------------------------------------------------------------------
+ */
+int
+Dir_FindHereOrAbove(char *here, char *search_path, char *result, int rlen) {
+
+ struct stat st;
+ char dirbase[MAXPATHLEN + 1], *db_end;
+ char try[MAXPATHLEN + 1], *try_end;
+
+ /* copy out our starting point */
+ snprintf(dirbase, sizeof(dirbase), "%s", here);
+ db_end = dirbase + strlen(dirbase);
+
+ /* loop until we determine a result */
+ while (1) {
+
+ /* try and stat(2) it ... */
+ snprintf(try, sizeof(try), "%s/%s", dirbase, search_path);
+ if (cached_stat(try, &st) != -1) {
+ /*
+ * success! if we found a file, chop off
+ * the filename so we return a directory.
+ */
+ if ((st.st_mode & S_IFMT) != S_IFDIR) {
+ try_end = try + strlen(try);
+ while (try_end > try && *try_end != '/')
+ try_end--;
+ if (try_end > try)
+ *try_end = 0; /* chop! */
+ }
+
+ /*
+ * done!
+ */
+ snprintf(result, rlen, "%s", try);
+ return(1);
+ }
+
+ /*
+ * nope, we didn't find it. if we used up dirbase we've
+ * reached the root and failed.
+ */
+ if (db_end == dirbase)
+ break; /* failed! */
+
+ /*
+ * truncate dirbase from the end to move up a dir
+ */
+ while (db_end > dirbase && *db_end != '/')
+ db_end--;
+ *db_end = 0; /* chop! */
+
+ } /* while (1) */
+
+ /*
+ * we failed...
+ */
+ return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_MTime --
+ * Find the modification time of the file described by gn along the
+ * search path dirSearchPath.
+ *
+ * Input:
+ * gn the file whose modification time is desired
+ *
+ * Results:
+ * The modification time or 0 if it doesn't exist
+ *
+ * Side Effects:
+ * The modification time is placed in the node's mtime slot.
+ * If the node didn't have a path entry before, and Dir_FindFile
+ * found one for it, the full name is placed in the path slot.
+ *-----------------------------------------------------------------------
+ */
+int
+Dir_MTime(GNode *gn, Boolean recheck)
+{
+ char *fullName; /* the full pathname of name */
+ struct stat stb; /* buffer for finding the mod time */
+
+ if (gn->type & OP_ARCHV) {
+ return Arch_MTime(gn);
+ } else if (gn->type & OP_PHONY) {
+ gn->mtime = 0;
+ return 0;
+ } else if (gn->path == NULL) {
+ if (gn->type & OP_NOPATH)
+ fullName = NULL;
+ else {
+ fullName = Dir_FindFile(gn->name, Suff_FindPath(gn));
+ if (fullName == NULL && gn->flags & FROM_DEPEND &&
+ !Lst_IsEmpty(gn->iParents)) {
+ char *cp;
+
+ cp = strrchr(gn->name, '/');
+ if (cp) {
+ /*
+ * This is an implied source, and it may have moved,
+ * see if we can find it via the current .PATH
+ */
+ cp++;
+
+ fullName = Dir_FindFile(cp, Suff_FindPath(gn));
+ if (fullName) {
+ /*
+ * Put the found file in gn->path
+ * so that we give that to the compiler.
+ */
+ gn->path = bmake_strdup(fullName);
+ if (!Job_RunTarget(".STALE", gn->fname))
+ fprintf(stdout,
+ "%s: %s, %d: ignoring stale %s for %s, "
+ "found %s\n", progname, gn->fname, gn->lineno,
+ makeDependfile, gn->name, fullName);
+ }
+ }
+ }
+ if (DEBUG(DIR))
+ fprintf(debug_file, "Found '%s' as '%s'\n",
+ gn->name, fullName ? fullName : "(not found)" );
+ }
+ } else {
+ fullName = gn->path;
+ }
+
+ if (fullName == NULL) {
+ fullName = bmake_strdup(gn->name);
+ }
+
+ if (cached_stats(&mtimes, fullName, &stb, recheck ? CST_UPDATE : 0) < 0) {
+ if (gn->type & OP_MEMBER) {
+ if (fullName != gn->path)
+ free(fullName);
+ return Arch_MemMTime(gn);
+ } else {
+ stb.st_mtime = 0;
+ }
+ }
+
+ if (fullName && gn->path == NULL) {
+ gn->path = fullName;
+ }
+
+ gn->mtime = stb.st_mtime;
+ return (gn->mtime);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_AddDir --
+ * Add the given name to the end of the given path. The order of
+ * the arguments is backwards so ParseDoDependency can do a
+ * Lst_ForEach of its list of paths...
+ *
+ * Input:
+ * path the path to which the directory should be
+ * added
+ * name the name of the directory to add
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * A structure is added to the list and the directory is
+ * read and hashed.
+ *-----------------------------------------------------------------------
+ */
+Path *
+Dir_AddDir(Lst path, const char *name)
+{
+ LstNode ln = NULL; /* node in case Path structure is found */
+ Path *p = NULL; /* pointer to new Path structure */
+ DIR *d; /* for reading directory */
+ struct dirent *dp; /* entry in directory */
+
+ if (strcmp(name, ".DOTLAST") == 0) {
+ ln = Lst_Find(path, name, DirFindName);
+ if (ln != NULL)
+ return (Path *)Lst_Datum(ln);
+ else {
+ dotLast->refCount += 1;
+ (void)Lst_AtFront(path, dotLast);
+ }
+ }
+
+ if (path)
+ ln = Lst_Find(openDirectories, name, DirFindName);
+ if (ln != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ if (path && Lst_Member(path, p) == NULL) {
+ p->refCount += 1;
+ (void)Lst_AtEnd(path, p);
+ }
+ } else {
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "Caching %s ...", name);
+ }
+
+ if ((d = opendir(name)) != NULL) {
+ p = bmake_malloc(sizeof(Path));
+ p->name = bmake_strdup(name);
+ p->hits = 0;
+ p->refCount = 1;
+ Hash_InitTable(&p->files, -1);
+
+ while ((dp = readdir(d)) != NULL) {
+#if defined(sun) && defined(d_ino) /* d_ino is a sunos4 #define for d_fileno */
+ /*
+ * The sun directory library doesn't check for a 0 inode
+ * (0-inode slots just take up space), so we have to do
+ * it ourselves.
+ */
+ if (dp->d_fileno == 0) {
+ continue;
+ }
+#endif /* sun && d_ino */
+ (void)Hash_CreateEntry(&p->files, dp->d_name, NULL);
+ }
+ (void)closedir(d);
+ (void)Lst_AtEnd(openDirectories, p);
+ if (path != NULL)
+ (void)Lst_AtEnd(path, p);
+ }
+ if (DEBUG(DIR)) {
+ fprintf(debug_file, "done\n");
+ }
+ }
+ return p;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_CopyDir --
+ * Callback function for duplicating a search path via Lst_Duplicate.
+ * Ups the reference count for the directory.
+ *
+ * Results:
+ * Returns the Path it was given.
+ *
+ * Side Effects:
+ * The refCount of the path is incremented.
+ *
+ *-----------------------------------------------------------------------
+ */
+void *
+Dir_CopyDir(void *p)
+{
+ ((Path *)p)->refCount += 1;
+
+ return (p);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_MakeFlags --
+ * Make a string by taking all the directories in the given search
+ * path and preceding them by the given flag. Used by the suffix
+ * module to create variables for compilers based on suffix search
+ * paths.
+ *
+ * Input:
+ * flag flag which should precede each directory
+ * path list of directories
+ *
+ * Results:
+ * The string mentioned above. Note that there is no space between
+ * the given flag and each directory. The empty string is returned if
+ * Things don't go well.
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+char *
+Dir_MakeFlags(const char *flag, Lst path)
+{
+ char *str; /* the string which will be returned */
+ char *s1, *s2;/* the current directory preceded by 'flag' */
+ LstNode ln; /* the node of the current directory */
+ Path *p; /* the structure describing the current directory */
+
+ str = bmake_strdup("");
+
+ if (Lst_Open(path) == SUCCESS) {
+ while ((ln = Lst_Next(path)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ s2 = str_concat(flag, p->name, 0);
+ str = str_concat(s1 = str, s2, STR_ADDSPACE);
+ free(s1);
+ free(s2);
+ }
+ Lst_Close(path);
+ }
+
+ return (str);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Destroy --
+ * Nuke a directory descriptor, if possible. Callback procedure
+ * for the suffixes module when destroying a search path.
+ *
+ * Input:
+ * pp The directory descriptor to nuke
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * If no other path references this directory (refCount == 0),
+ * the Path and all its data are freed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Destroy(void *pp)
+{
+ Path *p = (Path *)pp;
+ p->refCount -= 1;
+
+ if (p->refCount == 0) {
+ LstNode ln;
+
+ ln = Lst_Member(openDirectories, p);
+ (void)Lst_Remove(openDirectories, ln);
+
+ Hash_DeleteTable(&p->files);
+ free(p->name);
+ free(p);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_ClearPath --
+ * Clear out all elements of the given search path. This is different
+ * from destroying the list, notice.
+ *
+ * Input:
+ * path Path to clear
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The path is set to the empty list.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_ClearPath(Lst path)
+{
+ Path *p;
+ while (!Lst_IsEmpty(path)) {
+ p = (Path *)Lst_DeQueue(path);
+ Dir_Destroy(p);
+ }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Concat --
+ * Concatenate two paths, adding the second to the end of the first.
+ * Makes sure to avoid duplicates.
+ *
+ * Input:
+ * path1 Dest
+ * path2 Source
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Reference counts for added dirs are upped.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Concat(Lst path1, Lst path2)
+{
+ LstNode ln;
+ Path *p;
+
+ for (ln = Lst_First(path2); ln != NULL; ln = Lst_Succ(ln)) {
+ p = (Path *)Lst_Datum(ln);
+ if (Lst_Member(path1, p) == NULL) {
+ p->refCount += 1;
+ (void)Lst_AtEnd(path1, p);
+ }
+ }
+}
+
+/********** DEBUG INFO **********/
+void
+Dir_PrintDirectories(void)
+{
+ LstNode ln;
+ Path *p;
+
+ fprintf(debug_file, "#*** Directory Cache:\n");
+ fprintf(debug_file, "# Stats: %d hits %d misses %d near misses %d losers (%d%%)\n",
+ hits, misses, nearmisses, bigmisses,
+ (hits+bigmisses+nearmisses ?
+ hits * 100 / (hits + bigmisses + nearmisses) : 0));
+ fprintf(debug_file, "# %-20s referenced\thits\n", "directory");
+ if (Lst_Open(openDirectories) == SUCCESS) {
+ while ((ln = Lst_Next(openDirectories)) != NULL) {
+ p = (Path *)Lst_Datum(ln);
+ fprintf(debug_file, "# %-20s %10d\t%4d\n", p->name, p->refCount, p->hits);
+ }
+ Lst_Close(openDirectories);
+ }
+}
+
+static int
+DirPrintDir(void *p, void *dummy MAKE_ATTR_UNUSED)
+{
+ fprintf(debug_file, "%s ", ((Path *)p)->name);
+ return 0;
+}
+
+void
+Dir_PrintPath(Lst path)
+{
+ Lst_ForEach(path, DirPrintDir, NULL);
+}
diff --git a/usr.bin/make/dir.h b/usr.bin/make/dir.h
new file mode 100644
index 0000000..52ab35e
--- /dev/null
+++ b/usr.bin/make/dir.h
@@ -0,0 +1,108 @@
+/* $NetBSD: dir.h,v 1.18 2017/05/31 22:02:06 maya Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)dir.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)dir.h 8.1 (Berkeley) 6/6/93
+ */
+
+/* dir.h --
+ */
+
+#ifndef MAKE_DIR_H
+#define MAKE_DIR_H
+
+typedef struct Path {
+ char *name; /* Name of directory */
+ int refCount; /* Number of paths with this directory */
+ int hits; /* the number of times a file in this
+ * directory has been found */
+ Hash_Table files; /* Hash table of files in directory */
+} Path;
+
+void Dir_Init(const char *);
+void Dir_InitCur(const char *);
+void Dir_InitDot(void);
+void Dir_End(void);
+void Dir_SetPATH(void);
+Boolean Dir_HasWildcards(char *);
+void Dir_Expand(const char *, Lst, Lst);
+char *Dir_FindFile(const char *, Lst);
+int Dir_FindHereOrAbove(char *, char *, char *, int);
+int Dir_MTime(GNode *, Boolean);
+Path *Dir_AddDir(Lst, const char *);
+char *Dir_MakeFlags(const char *, Lst);
+void Dir_ClearPath(Lst);
+void Dir_Concat(Lst, Lst);
+void Dir_PrintDirectories(void);
+void Dir_PrintPath(Lst);
+void Dir_Destroy(void *);
+void * Dir_CopyDir(void *);
+
+#endif /* MAKE_DIR_H */
diff --git a/usr.bin/make/for.c b/usr.bin/make/for.c
new file mode 100644
index 0000000..fffedda
--- /dev/null
+++ b/usr.bin/make/for.c
@@ -0,0 +1,496 @@
+/* $NetBSD: for.c,v 1.53 2017/04/16 21:04:44 riastradh Exp $ */
+
+/*
+ * Copyright (c) 1992, The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: for.c,v 1.53 2017/04/16 21:04:44 riastradh Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)for.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: for.c,v 1.53 2017/04/16 21:04:44 riastradh Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * for.c --
+ * Functions to handle loops in a makefile.
+ *
+ * Interface:
+ * For_Eval Evaluate the loop in the passed line.
+ * For_Run Run accumulated loop
+ *
+ */
+
+#include <assert.h>
+#include <ctype.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "buf.h"
+#include "strlist.h"
+
+#define FOR_SUB_ESCAPE_CHAR 1
+#define FOR_SUB_ESCAPE_BRACE 2
+#define FOR_SUB_ESCAPE_PAREN 4
+
+/*
+ * For statements are of the form:
+ *
+ * .for <variable> in <varlist>
+ * ...
+ * .endfor
+ *
+ * The trick is to look for the matching end inside for for loop
+ * To do that, we count the current nesting level of the for loops.
+ * and the .endfor statements, accumulating all the statements between
+ * the initial .for loop and the matching .endfor;
+ * then we evaluate the for loop for each variable in the varlist.
+ *
+ * Note that any nested fors are just passed through; they get handled
+ * recursively in For_Eval when we're expanding the enclosing for in
+ * For_Run.
+ */
+
+static int forLevel = 0; /* Nesting level */
+
+/*
+ * State of a for loop.
+ */
+typedef struct _For {
+ Buffer buf; /* Body of loop */
+ strlist_t vars; /* Iteration variables */
+ strlist_t items; /* Substitution items */
+ char *parse_buf;
+ int short_var;
+ int sub_next;
+} For;
+
+static For *accumFor; /* Loop being accumulated */
+
+
+
+static char *
+make_str(const char *ptr, int len)
+{
+ char *new_ptr;
+
+ new_ptr = bmake_malloc(len + 1);
+ memcpy(new_ptr, ptr, len);
+ new_ptr[len] = 0;
+ return new_ptr;
+}
+
+static void
+For_Free(For *arg)
+{
+ Buf_Destroy(&arg->buf, TRUE);
+ strlist_clean(&arg->vars);
+ strlist_clean(&arg->items);
+ free(arg->parse_buf);
+
+ free(arg);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * For_Eval --
+ * Evaluate the for loop in the passed line. The line
+ * looks like this:
+ * .for <variable> in <varlist>
+ *
+ * Input:
+ * line Line to parse
+ *
+ * Results:
+ * 0: Not a .for statement, parse the line
+ * 1: We found a for loop
+ * -1: A .for statement with a bad syntax error, discard.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+For_Eval(char *line)
+{
+ For *new_for;
+ char *ptr = line, *sub;
+ int len;
+ int escapes;
+ unsigned char ch;
+ char **words, *word_buf;
+ int n, nwords;
+
+ /* Skip the '.' and any following whitespace */
+ for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
+ continue;
+
+ /*
+ * If we are not in a for loop quickly determine if the statement is
+ * a for.
+ */
+ if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' ||
+ !isspace((unsigned char) ptr[3])) {
+ if (ptr[0] == 'e' && strncmp(ptr+1, "ndfor", 5) == 0) {
+ Parse_Error(PARSE_FATAL, "for-less endfor");
+ return -1;
+ }
+ return 0;
+ }
+ ptr += 3;
+
+ /*
+ * we found a for loop, and now we are going to parse it.
+ */
+
+ new_for = bmake_malloc(sizeof *new_for);
+ memset(new_for, 0, sizeof *new_for);
+
+ /* Grab the variables. Terminate on "in". */
+ for (;; ptr += len) {
+ while (*ptr && isspace((unsigned char) *ptr))
+ ptr++;
+ if (*ptr == '\0') {
+ Parse_Error(PARSE_FATAL, "missing `in' in for");
+ For_Free(new_for);
+ return -1;
+ }
+ for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++)
+ continue;
+ if (len == 2 && ptr[0] == 'i' && ptr[1] == 'n') {
+ ptr += 2;
+ break;
+ }
+ if (len == 1)
+ new_for->short_var = 1;
+ strlist_add_str(&new_for->vars, make_str(ptr, len), len);
+ }
+
+ if (strlist_num(&new_for->vars) == 0) {
+ Parse_Error(PARSE_FATAL, "no iteration variables in for");
+ For_Free(new_for);
+ return -1;
+ }
+
+ while (*ptr && isspace((unsigned char) *ptr))
+ ptr++;
+
+ /*
+ * Make a list with the remaining words
+ * The values are substituted as ${:U<value>...} so we must \ escape
+ * characters that break that syntax.
+ * Variables are fully expanded - so it is safe for escape $.
+ * We can't do the escapes here - because we don't know whether
+ * we are substuting into ${...} or $(...).
+ */
+ sub = Var_Subst(NULL, ptr, VAR_GLOBAL, VARF_WANTRES);
+
+ /*
+ * Split into words allowing for quoted strings.
+ */
+ words = brk_string(sub, &nwords, FALSE, &word_buf);
+
+ free(sub);
+
+ if (words != NULL) {
+ for (n = 0; n < nwords; n++) {
+ ptr = words[n];
+ if (!*ptr)
+ continue;
+ escapes = 0;
+ while ((ch = *ptr++)) {
+ switch(ch) {
+ case ':':
+ case '$':
+ case '\\':
+ escapes |= FOR_SUB_ESCAPE_CHAR;
+ break;
+ case ')':
+ escapes |= FOR_SUB_ESCAPE_PAREN;
+ break;
+ case /*{*/ '}':
+ escapes |= FOR_SUB_ESCAPE_BRACE;
+ break;
+ }
+ }
+ /*
+ * We have to dup words[n] to maintain the semantics of
+ * strlist.
+ */
+ strlist_add_str(&new_for->items, bmake_strdup(words[n]), escapes);
+ }
+
+ free(words);
+ free(word_buf);
+
+ if ((len = strlist_num(&new_for->items)) > 0 &&
+ len % (n = strlist_num(&new_for->vars))) {
+ Parse_Error(PARSE_FATAL,
+ "Wrong number of words (%d) in .for substitution list"
+ " with %d vars", len, n);
+ /*
+ * Return 'success' so that the body of the .for loop is
+ * accumulated.
+ * Remove all items so that the loop doesn't iterate.
+ */
+ strlist_clean(&new_for->items);
+ }
+ }
+
+ Buf_Init(&new_for->buf, 0);
+ accumFor = new_for;
+ forLevel = 1;
+ return 1;
+}
+
+/*
+ * Add another line to a .for loop.
+ * Returns 0 when the matching .endfor is reached.
+ */
+
+int
+For_Accum(char *line)
+{
+ char *ptr = line;
+
+ if (*ptr == '.') {
+
+ for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
+ continue;
+
+ if (strncmp(ptr, "endfor", 6) == 0 &&
+ (isspace((unsigned char) ptr[6]) || !ptr[6])) {
+ if (DEBUG(FOR))
+ (void)fprintf(debug_file, "For: end for %d\n", forLevel);
+ if (--forLevel <= 0)
+ return 0;
+ } else if (strncmp(ptr, "for", 3) == 0 &&
+ isspace((unsigned char) ptr[3])) {
+ forLevel++;
+ if (DEBUG(FOR))
+ (void)fprintf(debug_file, "For: new loop %d\n", forLevel);
+ }
+ }
+
+ Buf_AddBytes(&accumFor->buf, strlen(line), line);
+ Buf_AddByte(&accumFor->buf, '\n');
+ return 1;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * For_Run --
+ * Run the for loop, imitating the actions of an include file
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+static int
+for_var_len(const char *var)
+{
+ char ch, var_start, var_end;
+ int depth;
+ int len;
+
+ var_start = *var;
+ if (var_start == 0)
+ /* just escape the $ */
+ return 0;
+
+ if (var_start == '(')
+ var_end = ')';
+ else if (var_start == '{')
+ var_end = '}';
+ else
+ /* Single char variable */
+ return 1;
+
+ depth = 1;
+ for (len = 1; (ch = var[len++]) != 0;) {
+ if (ch == var_start)
+ depth++;
+ else if (ch == var_end && --depth == 0)
+ return len;
+ }
+
+ /* Variable end not found, escape the $ */
+ return 0;
+}
+
+static void
+for_substitute(Buffer *cmds, strlist_t *items, unsigned int item_no, char ech)
+{
+ const char *item = strlist_str(items, item_no);
+ int len;
+ char ch;
+
+ /* If there were no escapes, or the only escape is the other variable
+ * terminator, then just substitute the full string */
+ if (!(strlist_info(items, item_no) &
+ (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) {
+ Buf_AddBytes(cmds, strlen(item), item);
+ return;
+ }
+
+ /* Escape ':', '$', '\\' and 'ech' - removed by :U processing */
+ while ((ch = *item++) != 0) {
+ if (ch == '$') {
+ len = for_var_len(item);
+ if (len != 0) {
+ Buf_AddBytes(cmds, len + 1, item - 1);
+ item += len;
+ continue;
+ }
+ Buf_AddByte(cmds, '\\');
+ } else if (ch == ':' || ch == '\\' || ch == ech)
+ Buf_AddByte(cmds, '\\');
+ Buf_AddByte(cmds, ch);
+ }
+}
+
+static char *
+For_Iterate(void *v_arg, size_t *ret_len)
+{
+ For *arg = v_arg;
+ int i, len;
+ char *var;
+ char *cp;
+ char *cmd_cp;
+ char *body_end;
+ char ch;
+ Buffer cmds;
+
+ if (arg->sub_next + strlist_num(&arg->vars) > strlist_num(&arg->items)) {
+ /* No more iterations */
+ For_Free(arg);
+ return NULL;
+ }
+
+ free(arg->parse_buf);
+ arg->parse_buf = NULL;
+
+ /*
+ * Scan the for loop body and replace references to the loop variables
+ * with variable references that expand to the required text.
+ * Using variable expansions ensures that the .for loop can't generate
+ * syntax, and that the later parsing will still see a variable.
+ * We assume that the null variable will never be defined.
+ *
+ * The detection of substitions of the loop control variable is naive.
+ * Many of the modifiers use \ to escape $ (not $) so it is possible
+ * to contrive a makefile where an unwanted substitution happens.
+ */
+
+ cmd_cp = Buf_GetAll(&arg->buf, &len);
+ body_end = cmd_cp + len;
+ Buf_Init(&cmds, len + 256);
+ for (cp = cmd_cp; (cp = strchr(cp, '$')) != NULL;) {
+ char ech;
+ ch = *++cp;
+ if ((ch == '(' && (ech = ')', 1)) || (ch == '{' && (ech = '}', 1))) {
+ cp++;
+ /* Check variable name against the .for loop variables */
+ STRLIST_FOREACH(var, &arg->vars, i) {
+ len = strlist_info(&arg->vars, i);
+ if (memcmp(cp, var, len) != 0)
+ continue;
+ if (cp[len] != ':' && cp[len] != ech && cp[len] != '\\')
+ continue;
+ /* Found a variable match. Replace with :U<value> */
+ Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp);
+ Buf_AddBytes(&cmds, 2, ":U");
+ cp += len;
+ cmd_cp = cp;
+ for_substitute(&cmds, &arg->items, arg->sub_next + i, ech);
+ break;
+ }
+ continue;
+ }
+ if (ch == 0)
+ break;
+ /* Probably a single character name, ignore $$ and stupid ones. {*/
+ if (!arg->short_var || strchr("}):$", ch) != NULL) {
+ cp++;
+ continue;
+ }
+ STRLIST_FOREACH(var, &arg->vars, i) {
+ if (var[0] != ch || var[1] != 0)
+ continue;
+ /* Found a variable match. Replace with ${:U<value>} */
+ Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp);
+ Buf_AddBytes(&cmds, 3, "{:U");
+ cmd_cp = ++cp;
+ for_substitute(&cmds, &arg->items, arg->sub_next + i, /*{*/ '}');
+ Buf_AddBytes(&cmds, 1, "}");
+ break;
+ }
+ }
+ Buf_AddBytes(&cmds, body_end - cmd_cp, cmd_cp);
+
+ cp = Buf_Destroy(&cmds, FALSE);
+ if (DEBUG(FOR))
+ (void)fprintf(debug_file, "For: loop body:\n%s", cp);
+
+ arg->sub_next += strlist_num(&arg->vars);
+
+ arg->parse_buf = cp;
+ *ret_len = strlen(cp);
+ return cp;
+}
+
+void
+For_Run(int lineno)
+{
+ For *arg;
+
+ arg = accumFor;
+ accumFor = NULL;
+
+ if (strlist_num(&arg->items) == 0) {
+ /* Nothing to expand - possibly due to an earlier syntax error. */
+ For_Free(arg);
+ return;
+ }
+
+ Parse_SetInput(NULL, lineno, -1, For_Iterate, arg);
+}
diff --git a/usr.bin/make/hash.c b/usr.bin/make/hash.c
new file mode 100644
index 0000000..ed23644
--- /dev/null
+++ b/usr.bin/make/hash.c
@@ -0,0 +1,466 @@
+/* $NetBSD: hash.c,v 1.20 2013/11/14 00:27:05 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: hash.c,v 1.20 2013/11/14 00:27:05 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)hash.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: hash.c,v 1.20 2013/11/14 00:27:05 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/* hash.c --
+ *
+ * This module contains routines to manipulate a hash table.
+ * See hash.h for a definition of the structure of the hash
+ * table. Hash tables grow automatically as the amount of
+ * information increases.
+ */
+#include "sprite.h"
+#include "make.h"
+#include "hash.h"
+
+/*
+ * Forward references to local procedures that are used before they're
+ * defined:
+ */
+
+static void RebuildTable(Hash_Table *);
+
+/*
+ * The following defines the ratio of # entries to # buckets
+ * at which we rebuild the table to make it larger.
+ */
+
+#define rebuildLimit 3
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_InitTable --
+ *
+ * This routine just sets up the hash table.
+ *
+ * Input:
+ * t Structure to to hold table.
+ * numBuckets How many buckets to create for starters. This
+ * number is rounded up to a power of two. If
+ * <= 0, a reasonable default is chosen. The
+ * table will grow in size later as needed.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Memory is allocated for the initial bucket area.
+ *
+ *---------------------------------------------------------
+ */
+
+void
+Hash_InitTable(Hash_Table *t, int numBuckets)
+{
+ int i;
+ struct Hash_Entry **hp;
+
+ /*
+ * Round up the size to a power of two.
+ */
+ if (numBuckets <= 0)
+ i = 16;
+ else {
+ for (i = 2; i < numBuckets; i <<= 1)
+ continue;
+ }
+ t->numEntries = 0;
+ t->size = i;
+ t->mask = i - 1;
+ t->bucketPtr = hp = bmake_malloc(sizeof(*hp) * i);
+ while (--i >= 0)
+ *hp++ = NULL;
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_DeleteTable --
+ *
+ * This routine removes everything from a hash table
+ * and frees up the memory space it occupied (except for
+ * the space in the Hash_Table structure).
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Lots of memory is freed up.
+ *
+ *---------------------------------------------------------
+ */
+
+void
+Hash_DeleteTable(Hash_Table *t)
+{
+ struct Hash_Entry **hp, *h, *nexth = NULL;
+ int i;
+
+ for (hp = t->bucketPtr, i = t->size; --i >= 0;) {
+ for (h = *hp++; h != NULL; h = nexth) {
+ nexth = h->next;
+ free(h);
+ }
+ }
+ free(t->bucketPtr);
+
+ /*
+ * Set up the hash table to cause memory faults on any future access
+ * attempts until re-initialization.
+ */
+ t->bucketPtr = NULL;
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_FindEntry --
+ *
+ * Searches a hash table for an entry corresponding to key.
+ *
+ * Input:
+ * t Hash table to search.
+ * key A hash key.
+ *
+ * Results:
+ * The return value is a pointer to the entry for key,
+ * if key was present in the table. If key was not
+ * present, NULL is returned.
+ *
+ * Side Effects:
+ * None.
+ *
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_FindEntry(Hash_Table *t, const char *key)
+{
+ Hash_Entry *e;
+ unsigned h;
+ const char *p;
+
+ if (t == NULL || t->bucketPtr == NULL) {
+ return NULL;
+ }
+ for (h = 0, p = key; *p;)
+ h = (h << 5) - h + *p++;
+ p = key;
+ for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next)
+ if (e->namehash == h && strcmp(e->name, p) == 0)
+ return (e);
+ return NULL;
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_CreateEntry --
+ *
+ * Searches a hash table for an entry corresponding to
+ * key. If no entry is found, then one is created.
+ *
+ * Input:
+ * t Hash table to search.
+ * key A hash key.
+ * newPtr Filled in with TRUE if new entry created,
+ * FALSE otherwise.
+ *
+ * Results:
+ * The return value is a pointer to the entry. If *newPtr
+ * isn't NULL, then *newPtr is filled in with TRUE if a
+ * new entry was created, and FALSE if an entry already existed
+ * with the given key.
+ *
+ * Side Effects:
+ * Memory may be allocated, and the hash buckets may be modified.
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_CreateEntry(Hash_Table *t, const char *key, Boolean *newPtr)
+{
+ Hash_Entry *e;
+ unsigned h;
+ const char *p;
+ int keylen;
+ struct Hash_Entry **hp;
+
+ /*
+ * Hash the key. As a side effect, save the length (strlen) of the
+ * key in case we need to create the entry.
+ */
+ for (h = 0, p = key; *p;)
+ h = (h << 5) - h + *p++;
+ keylen = p - key;
+ p = key;
+ for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next) {
+ if (e->namehash == h && strcmp(e->name, p) == 0) {
+ if (newPtr != NULL)
+ *newPtr = FALSE;
+ return (e);
+ }
+ }
+
+ /*
+ * The desired entry isn't there. Before allocating a new entry,
+ * expand the table if necessary (and this changes the resulting
+ * bucket chain).
+ */
+ if (t->numEntries >= rebuildLimit * t->size)
+ RebuildTable(t);
+ e = bmake_malloc(sizeof(*e) + keylen);
+ hp = &t->bucketPtr[h & t->mask];
+ e->next = *hp;
+ *hp = e;
+ Hash_SetValue(e, NULL);
+ e->namehash = h;
+ (void)strcpy(e->name, p);
+ t->numEntries++;
+
+ if (newPtr != NULL)
+ *newPtr = TRUE;
+ return (e);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_DeleteEntry --
+ *
+ * Delete the given hash table entry and free memory associated with
+ * it.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Hash chain that entry lives in is modified and memory is freed.
+ *
+ *---------------------------------------------------------
+ */
+
+void
+Hash_DeleteEntry(Hash_Table *t, Hash_Entry *e)
+{
+ Hash_Entry **hp, *p;
+
+ if (e == NULL)
+ return;
+ for (hp = &t->bucketPtr[e->namehash & t->mask];
+ (p = *hp) != NULL; hp = &p->next) {
+ if (p == e) {
+ *hp = p->next;
+ free(p);
+ t->numEntries--;
+ return;
+ }
+ }
+ (void)write(2, "bad call to Hash_DeleteEntry\n", 29);
+ abort();
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_EnumFirst --
+ * This procedure sets things up for a complete search
+ * of all entries recorded in the hash table.
+ *
+ * Input:
+ * t Table to be searched.
+ * searchPtr Area in which to keep state about search.
+ *
+ * Results:
+ * The return value is the address of the first entry in
+ * the hash table, or NULL if the table is empty.
+ *
+ * Side Effects:
+ * The information in searchPtr is initialized so that successive
+ * calls to Hash_Next will return successive HashEntry's
+ * from the table.
+ *
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_EnumFirst(Hash_Table *t, Hash_Search *searchPtr)
+{
+ searchPtr->tablePtr = t;
+ searchPtr->nextIndex = 0;
+ searchPtr->hashEntryPtr = NULL;
+ return Hash_EnumNext(searchPtr);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_EnumNext --
+ * This procedure returns successive entries in the hash table.
+ *
+ * Input:
+ * searchPtr Area used to keep state about search.
+ *
+ * Results:
+ * The return value is a pointer to the next HashEntry
+ * in the table, or NULL when the end of the table is
+ * reached.
+ *
+ * Side Effects:
+ * The information in searchPtr is modified to advance to the
+ * next entry.
+ *
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_EnumNext(Hash_Search *searchPtr)
+{
+ Hash_Entry *e;
+ Hash_Table *t = searchPtr->tablePtr;
+
+ /*
+ * The hashEntryPtr field points to the most recently returned
+ * entry, or is nil if we are starting up. If not nil, we have
+ * to start at the next one in the chain.
+ */
+ e = searchPtr->hashEntryPtr;
+ if (e != NULL)
+ e = e->next;
+ /*
+ * If the chain ran out, or if we are starting up, we need to
+ * find the next nonempty chain.
+ */
+ while (e == NULL) {
+ if (searchPtr->nextIndex >= t->size)
+ return NULL;
+ e = t->bucketPtr[searchPtr->nextIndex++];
+ }
+ searchPtr->hashEntryPtr = e;
+ return (e);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * RebuildTable --
+ * This local routine makes a new hash table that
+ * is larger than the old one.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The entire hash table is moved, so any bucket numbers
+ * from the old table are invalid.
+ *
+ *---------------------------------------------------------
+ */
+
+static void
+RebuildTable(Hash_Table *t)
+{
+ Hash_Entry *e, *next = NULL, **hp, **xp;
+ int i, mask;
+ Hash_Entry **oldhp;
+ int oldsize;
+
+ oldhp = t->bucketPtr;
+ oldsize = i = t->size;
+ i <<= 1;
+ t->size = i;
+ t->mask = mask = i - 1;
+ t->bucketPtr = hp = bmake_malloc(sizeof(*hp) * i);
+ while (--i >= 0)
+ *hp++ = NULL;
+ for (hp = oldhp, i = oldsize; --i >= 0;) {
+ for (e = *hp++; e != NULL; e = next) {
+ next = e->next;
+ xp = &t->bucketPtr[e->namehash & mask];
+ e->next = *xp;
+ *xp = e;
+ }
+ }
+ free(oldhp);
+}
diff --git a/usr.bin/make/hash.h b/usr.bin/make/hash.h
new file mode 100644
index 0000000..8ab6ffd
--- /dev/null
+++ b/usr.bin/make/hash.h
@@ -0,0 +1,149 @@
+/* $NetBSD: hash.h,v 1.12 2017/05/31 21:07:03 maya Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)hash.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)hash.h 8.1 (Berkeley) 6/6/93
+ */
+
+/* hash.h --
+ *
+ * This file contains definitions used by the hash module,
+ * which maintains hash tables.
+ */
+
+#ifndef _HASH_H
+#define _HASH_H
+
+/*
+ * The following defines one entry in the hash table.
+ */
+
+typedef struct Hash_Entry {
+ struct Hash_Entry *next; /* Used to link together all the
+ * entries associated with the same
+ * bucket. */
+ void *clientPtr; /* Arbitrary pointer */
+ unsigned namehash; /* hash value of key */
+ char name[1]; /* key string */
+} Hash_Entry;
+
+typedef struct Hash_Table {
+ struct Hash_Entry **bucketPtr;/* Pointers to Hash_Entry, one
+ * for each bucket in the table. */
+ int size; /* Actual size of array. */
+ int numEntries; /* Number of entries in the table. */
+ int mask; /* Used to select bits for hashing. */
+} Hash_Table;
+
+/*
+ * The following structure is used by the searching routines
+ * to record where we are in the search.
+ */
+
+typedef struct Hash_Search {
+ Hash_Table *tablePtr; /* Table being searched. */
+ int nextIndex; /* Next bucket to check (after current). */
+ Hash_Entry *hashEntryPtr; /* Next entry to check in current bucket. */
+} Hash_Search;
+
+/*
+ * Macros.
+ */
+
+/*
+ * void * Hash_GetValue(h)
+ * Hash_Entry *h;
+ */
+
+#define Hash_GetValue(h) ((h)->clientPtr)
+
+/*
+ * Hash_SetValue(h, val);
+ * Hash_Entry *h;
+ * char *val;
+ */
+
+#define Hash_SetValue(h, val) ((h)->clientPtr = (val))
+
+/*
+ * Hash_Size(n) returns the number of words in an object of n bytes
+ */
+
+#define Hash_Size(n) (((n) + sizeof (int) - 1) / sizeof (int))
+
+void Hash_InitTable(Hash_Table *, int);
+void Hash_DeleteTable(Hash_Table *);
+Hash_Entry *Hash_FindEntry(Hash_Table *, const char *);
+Hash_Entry *Hash_CreateEntry(Hash_Table *, const char *, Boolean *);
+void Hash_DeleteEntry(Hash_Table *, Hash_Entry *);
+Hash_Entry *Hash_EnumFirst(Hash_Table *, Hash_Search *);
+Hash_Entry *Hash_EnumNext(Hash_Search *);
+
+#endif /* _HASH_H */
diff --git a/usr.bin/make/job.c b/usr.bin/make/job.c
new file mode 100644
index 0000000..48ec284
--- /dev/null
+++ b/usr.bin/make/job.c
@@ -0,0 +1,3070 @@
+/* $NetBSD: job.c,v 1.195 2018/05/13 22:13:28 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: job.c,v 1.195 2018/05/13 22:13:28 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)job.c 8.2 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: job.c,v 1.195 2018/05/13 22:13:28 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * job.c --
+ * handle the creation etc. of our child processes.
+ *
+ * Interface:
+ * Job_Make Start the creation of the given target.
+ *
+ * Job_CatchChildren Check for and handle the termination of any
+ * children. This must be called reasonably
+ * frequently to keep the whole make going at
+ * a decent clip, since job table entries aren't
+ * removed until their process is caught this way.
+ *
+ * Job_CatchOutput Print any output our children have produced.
+ * Should also be called fairly frequently to
+ * keep the user informed of what's going on.
+ * If no output is waiting, it will block for
+ * a time given by the SEL_* constants, below,
+ * or until output is ready.
+ *
+ * Job_Init Called to initialize this module. in addition,
+ * any commands attached to the .BEGIN target
+ * are executed before this function returns.
+ * Hence, the makefile must have been parsed
+ * before this function is called.
+ *
+ * Job_End Cleanup any memory used.
+ *
+ * Job_ParseShell Given the line following a .SHELL target, parse
+ * the line as a shell specification. Returns
+ * FAILURE if the spec was incorrect.
+ *
+ * Job_Finish Perform any final processing which needs doing.
+ * This includes the execution of any commands
+ * which have been/were attached to the .END
+ * target. It should only be called when the
+ * job table is empty.
+ *
+ * Job_AbortAll Abort all currently running jobs. It doesn't
+ * handle output or do anything for the jobs,
+ * just kills them. It should only be called in
+ * an emergency, as it were.
+ *
+ * Job_CheckCommands Verify that the commands for a target are
+ * ok. Provide them if necessary and possible.
+ *
+ * Job_Touch Update a target without really updating it.
+ *
+ * Job_Wait Wait for all currently-running jobs to finish.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <errno.h>
+#ifndef USE_SELECT
+#include <poll.h>
+#endif
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <utime.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "pathnames.h"
+#include "trace.h"
+# define STATIC static
+
+/*
+ * error handling variables
+ */
+static int errors = 0; /* number of errors reported */
+static int aborting = 0; /* why is the make aborting? */
+#define ABORT_ERROR 1 /* Because of an error */
+#define ABORT_INTERRUPT 2 /* Because it was interrupted */
+#define ABORT_WAIT 3 /* Waiting for jobs to finish */
+#define JOB_TOKENS "+EI+" /* Token to requeue for each abort state */
+
+/*
+ * this tracks the number of tokens currently "out" to build jobs.
+ */
+int jobTokensRunning = 0;
+int not_parallel = 0; /* set if .NOT_PARALLEL */
+
+/*
+ * XXX: Avoid SunOS bug... FILENO() is fp->_file, and file
+ * is a char! So when we go above 127 we turn negative!
+ */
+#define FILENO(a) ((unsigned) fileno(a))
+
+/*
+ * post-make command processing. The node postCommands is really just the
+ * .END target but we keep it around to avoid having to search for it
+ * all the time.
+ */
+static GNode *postCommands = NULL;
+ /* node containing commands to execute when
+ * everything else is done */
+static int numCommands; /* The number of commands actually printed
+ * for a target. Should this number be
+ * 0, no shell will be executed. */
+
+/*
+ * Return values from JobStart.
+ */
+#define JOB_RUNNING 0 /* Job is running */
+#define JOB_ERROR 1 /* Error in starting the job */
+#define JOB_FINISHED 2 /* The job is already finished */
+
+/*
+ * Descriptions for various shells.
+ *
+ * The build environment may set DEFSHELL_INDEX to one of
+ * DEFSHELL_INDEX_SH, DEFSHELL_INDEX_KSH, or DEFSHELL_INDEX_CSH, to
+ * select one of the prefedined shells as the default shell.
+ *
+ * Alternatively, the build environment may set DEFSHELL_CUSTOM to the
+ * name or the full path of a sh-compatible shell, which will be used as
+ * the default shell.
+ *
+ * ".SHELL" lines in Makefiles can choose the default shell from the
+ # set defined here, or add additional shells.
+ */
+
+#ifdef DEFSHELL_CUSTOM
+#define DEFSHELL_INDEX_CUSTOM 0
+#define DEFSHELL_INDEX_SH 1
+#define DEFSHELL_INDEX_KSH 2
+#define DEFSHELL_INDEX_CSH 3
+#else /* !DEFSHELL_CUSTOM */
+#define DEFSHELL_INDEX_SH 0
+#define DEFSHELL_INDEX_KSH 1
+#define DEFSHELL_INDEX_CSH 2
+#endif /* !DEFSHELL_CUSTOM */
+
+#ifndef DEFSHELL_INDEX
+#define DEFSHELL_INDEX 0 /* DEFSHELL_INDEX_CUSTOM or DEFSHELL_INDEX_SH */
+#endif /* !DEFSHELL_INDEX */
+
+static Shell shells[] = {
+#ifdef DEFSHELL_CUSTOM
+ /*
+ * An sh-compatible shell with a non-standard name.
+ *
+ * Keep this in sync with the "sh" description below, but avoid
+ * non-portable features that might not be supplied by all
+ * sh-compatible shells.
+ */
+{
+ DEFSHELL_CUSTOM,
+ FALSE, "", "", "", 0,
+ FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#',
+ "",
+ "",
+},
+#endif /* DEFSHELL_CUSTOM */
+ /*
+ * SH description. Echo control is also possible and, under
+ * sun UNIX anyway, one can even control error checking.
+ */
+{
+ "sh",
+ FALSE, "", "", "", 0,
+ FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#',
+#if defined(MAKE_NATIVE) && defined(__NetBSD__)
+ "q",
+#else
+ "",
+#endif
+ "",
+},
+ /*
+ * KSH description.
+ */
+{
+ "ksh",
+ TRUE, "set +v", "set -v", "set +v", 6,
+ FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#',
+ "v",
+ "",
+},
+ /*
+ * CSH description. The csh can do echo control by playing
+ * with the setting of the 'echo' shell variable. Sadly,
+ * however, it is unable to do error control nicely.
+ */
+{
+ "csh",
+ TRUE, "unset verbose", "set verbose", "unset verbose", 10,
+ FALSE, "echo \"%s\"\n", "csh -c \"%s || exit 0\"\n", "", "'\\\n'", '#',
+ "v", "e",
+},
+ /*
+ * UNKNOWN.
+ */
+{
+ NULL,
+ FALSE, NULL, NULL, NULL, 0,
+ FALSE, NULL, NULL, NULL, NULL, 0,
+ NULL, NULL,
+}
+};
+static Shell *commandShell = &shells[DEFSHELL_INDEX]; /* this is the shell to
+ * which we pass all
+ * commands in the Makefile.
+ * It is set by the
+ * Job_ParseShell function */
+const char *shellPath = NULL, /* full pathname of
+ * executable image */
+ *shellName = NULL; /* last component of shell */
+char *shellErrFlag = NULL;
+static const char *shellArgv = NULL; /* Custom shell args */
+
+
+STATIC Job *job_table; /* The structures that describe them */
+STATIC Job *job_table_end; /* job_table + maxJobs */
+static int wantToken; /* we want a token */
+static int lurking_children = 0;
+static int make_suspended = 0; /* non-zero if we've seen a SIGTSTP (etc) */
+
+/*
+ * Set of descriptors of pipes connected to
+ * the output channels of children
+ */
+static struct pollfd *fds = NULL;
+static Job **jobfds = NULL;
+static int nfds = 0;
+static void watchfd(Job *);
+static void clearfd(Job *);
+static int readyfd(Job *);
+
+STATIC GNode *lastNode; /* The node for which output was most recently
+ * produced. */
+static char *targPrefix = NULL; /* What we print at the start of TARG_FMT */
+static Job tokenWaitJob; /* token wait pseudo-job */
+
+static Job childExitJob; /* child exit pseudo-job */
+#define CHILD_EXIT "."
+#define DO_JOB_RESUME "R"
+
+#define TARG_FMT "%s %s ---\n" /* Default format */
+#define MESSAGE(fp, gn) \
+ if (maxJobs != 1 && targPrefix && *targPrefix) \
+ (void)fprintf(fp, TARG_FMT, targPrefix, gn->name)
+
+static sigset_t caught_signals; /* Set of signals we handle */
+
+static void JobChildSig(int);
+static void JobContinueSig(int);
+static Job *JobFindPid(int, int, Boolean);
+static int JobPrintCommand(void *, void *);
+static int JobSaveCommand(void *, void *);
+static void JobClose(Job *);
+static void JobExec(Job *, char **);
+static void JobMakeArgv(Job *, char **);
+static int JobStart(GNode *, int);
+static char *JobOutput(Job *, char *, char *, int);
+static void JobDoOutput(Job *, Boolean);
+static Shell *JobMatchShell(const char *);
+static void JobInterrupt(int, int) MAKE_ATTR_DEAD;
+static void JobRestartJobs(void);
+static void JobTokenAdd(void);
+static void JobSigLock(sigset_t *);
+static void JobSigUnlock(sigset_t *);
+static void JobSigReset(void);
+
+const char *malloc_options="A";
+
+static void
+job_table_dump(const char *where)
+{
+ Job *job;
+
+ fprintf(debug_file, "job table @ %s\n", where);
+ for (job = job_table; job < job_table_end; job++) {
+ fprintf(debug_file, "job %d, status %d, flags %d, pid %d\n",
+ (int)(job - job_table), job->job_state, job->flags, job->pid);
+ }
+}
+
+/*
+ * Delete the target of a failed, interrupted, or otherwise
+ * unsuccessful job unless inhibited by .PRECIOUS.
+ */
+static void
+JobDeleteTarget(GNode *gn)
+{
+ if ((gn->type & (OP_JOIN|OP_PHONY)) == 0 && !Targ_Precious(gn)) {
+ char *file = (gn->path == NULL ? gn->name : gn->path);
+ if (!noExecute && eunlink(file) != -1) {
+ Error("*** %s removed", file);
+ }
+ }
+}
+
+/*
+ * JobSigLock/JobSigUnlock
+ *
+ * Signal lock routines to get exclusive access. Currently used to
+ * protect `jobs' and `stoppedJobs' list manipulations.
+ */
+static void JobSigLock(sigset_t *omaskp)
+{
+ if (sigprocmask(SIG_BLOCK, &caught_signals, omaskp) != 0) {
+ Punt("JobSigLock: sigprocmask: %s", strerror(errno));
+ sigemptyset(omaskp);
+ }
+}
+
+static void JobSigUnlock(sigset_t *omaskp)
+{
+ (void)sigprocmask(SIG_SETMASK, omaskp, NULL);
+}
+
+static void
+JobCreatePipe(Job *job, int minfd)
+{
+ int i, fd, flags;
+
+ if (pipe(job->jobPipe) == -1)
+ Punt("Cannot create pipe: %s", strerror(errno));
+
+ for (i = 0; i < 2; i++) {
+ /* Avoid using low numbered fds */
+ fd = fcntl(job->jobPipe[i], F_DUPFD, minfd);
+ if (fd != -1) {
+ close(job->jobPipe[i]);
+ job->jobPipe[i] = fd;
+ }
+ }
+
+ /* Set close-on-exec flag for both */
+ if (fcntl(job->jobPipe[0], F_SETFD, FD_CLOEXEC) == -1)
+ Punt("Cannot set close-on-exec: %s", strerror(errno));
+ if (fcntl(job->jobPipe[1], F_SETFD, FD_CLOEXEC) == -1)
+ Punt("Cannot set close-on-exec: %s", strerror(errno));
+
+ /*
+ * We mark the input side of the pipe non-blocking; we poll(2) the
+ * pipe when we're waiting for a job token, but we might lose the
+ * race for the token when a new one becomes available, so the read
+ * from the pipe should not block.
+ */
+ flags = fcntl(job->jobPipe[0], F_GETFL, 0);
+ if (flags == -1)
+ Punt("Cannot get flags: %s", strerror(errno));
+ flags |= O_NONBLOCK;
+ if (fcntl(job->jobPipe[0], F_SETFL, flags) == -1)
+ Punt("Cannot set flags: %s", strerror(errno));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobCondPassSig --
+ * Pass a signal to a job
+ *
+ * Input:
+ * signop Signal to send it
+ *
+ * Side Effects:
+ * None, except the job may bite it.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobCondPassSig(int signo)
+{
+ Job *job;
+
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file, "JobCondPassSig(%d) called.\n", signo);
+ }
+
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->job_state != JOB_ST_RUNNING)
+ continue;
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file,
+ "JobCondPassSig passing signal %d to child %d.\n",
+ signo, job->pid);
+ }
+ KILLPG(job->pid, signo);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobChldSig --
+ * SIGCHLD handler.
+ *
+ * Input:
+ * signo The signal number we've received
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Sends a token on the child exit pipe to wake us up from
+ * select()/poll().
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobChildSig(int signo MAKE_ATTR_UNUSED)
+{
+ while (write(childExitJob.outPipe, CHILD_EXIT, 1) == -1 && errno == EAGAIN)
+ continue;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobContinueSig --
+ * Resume all stopped jobs.
+ *
+ * Input:
+ * signo The signal number we've received
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Jobs start running again.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobContinueSig(int signo MAKE_ATTR_UNUSED)
+{
+ /*
+ * Defer sending to SIGCONT to our stopped children until we return
+ * from the signal handler.
+ */
+ while (write(childExitJob.outPipe, DO_JOB_RESUME, 1) == -1 &&
+ errno == EAGAIN)
+ continue;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobPassSig --
+ * Pass a signal on to all jobs, then resend to ourselves.
+ *
+ * Input:
+ * signo The signal number we've received
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * We die by the same signal.
+ *
+ *-----------------------------------------------------------------------
+ */
+MAKE_ATTR_DEAD static void
+JobPassSig_int(int signo)
+{
+ /* Run .INTERRUPT target then exit */
+ JobInterrupt(TRUE, signo);
+}
+
+MAKE_ATTR_DEAD static void
+JobPassSig_term(int signo)
+{
+ /* Dont run .INTERRUPT target then exit */
+ JobInterrupt(FALSE, signo);
+}
+
+static void
+JobPassSig_suspend(int signo)
+{
+ sigset_t nmask, omask;
+ struct sigaction act;
+
+ /* Suppress job started/continued messages */
+ make_suspended = 1;
+
+ /* Pass the signal onto every job */
+ JobCondPassSig(signo);
+
+ /*
+ * Send ourselves the signal now we've given the message to everyone else.
+ * Note we block everything else possible while we're getting the signal.
+ * This ensures that all our jobs get continued when we wake up before
+ * we take any other signal.
+ */
+ sigfillset(&nmask);
+ sigdelset(&nmask, signo);
+ (void)sigprocmask(SIG_SETMASK, &nmask, &omask);
+
+ act.sa_handler = SIG_DFL;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ (void)sigaction(signo, &act, NULL);
+
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file,
+ "JobPassSig passing signal %d to self.\n", signo);
+ }
+
+ (void)kill(getpid(), signo);
+
+ /*
+ * We've been continued.
+ *
+ * A whole host of signals continue to happen!
+ * SIGCHLD for any processes that actually suspended themselves.
+ * SIGCHLD for any processes that exited while we were alseep.
+ * The SIGCONT that actually caused us to wakeup.
+ *
+ * Since we defer passing the SIGCONT on to our children until
+ * the main processing loop, we can be sure that all the SIGCHLD
+ * events will have happened by then - and that the waitpid() will
+ * collect the child 'suspended' events.
+ * For correct sequencing we just need to ensure we process the
+ * waitpid() before passign on the SIGCONT.
+ *
+ * In any case nothing else is needed here.
+ */
+
+ /* Restore handler and signal mask */
+ act.sa_handler = JobPassSig_suspend;
+ (void)sigaction(signo, &act, NULL);
+ (void)sigprocmask(SIG_SETMASK, &omask, NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobFindPid --
+ * Compare the pid of the job with the given pid and return 0 if they
+ * are equal. This function is called from Job_CatchChildren
+ * to find the job descriptor of the finished job.
+ *
+ * Input:
+ * job job to examine
+ * pid process id desired
+ *
+ * Results:
+ * Job with matching pid
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static Job *
+JobFindPid(int pid, int status, Boolean isJobs)
+{
+ Job *job;
+
+ for (job = job_table; job < job_table_end; job++) {
+ if ((job->job_state == status) && job->pid == pid)
+ return job;
+ }
+ if (DEBUG(JOB) && isJobs)
+ job_table_dump("no pid");
+ return NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobPrintCommand --
+ * Put out another command for the given job. If the command starts
+ * with an @ or a - we process it specially. In the former case,
+ * so long as the -s and -n flags weren't given to make, we stick
+ * a shell-specific echoOff command in the script. In the latter,
+ * we ignore errors for the entire job, unless the shell has error
+ * control.
+ * If the command is just "..." we take all future commands for this
+ * job to be commands to be executed once the entire graph has been
+ * made and return non-zero to signal that the end of the commands
+ * was reached. These commands are later attached to the postCommands
+ * node and executed by Job_End when all things are done.
+ * This function is called from JobStart via Lst_ForEach.
+ *
+ * Input:
+ * cmdp command string to print
+ * jobp job for which to print it
+ *
+ * Results:
+ * Always 0, unless the command was "..."
+ *
+ * Side Effects:
+ * If the command begins with a '-' and the shell has no error control,
+ * the JOB_IGNERR flag is set in the job descriptor.
+ * If the command is "..." and we're not ignoring such things,
+ * tailCmds is set to the successor node of the cmd.
+ * numCommands is incremented if the command is actually printed.
+ *-----------------------------------------------------------------------
+ */
+static int
+JobPrintCommand(void *cmdp, void *jobp)
+{
+ Boolean noSpecials; /* true if we shouldn't worry about
+ * inserting special commands into
+ * the input stream. */
+ Boolean shutUp = FALSE; /* true if we put a no echo command
+ * into the command file */
+ Boolean errOff = FALSE; /* true if we turned error checking
+ * off before printing the command
+ * and need to turn it back on */
+ const char *cmdTemplate; /* Template to use when printing the
+ * command */
+ char *cmdStart; /* Start of expanded command */
+ char *escCmd = NULL; /* Command with quotes/backticks escaped */
+ char *cmd = (char *)cmdp;
+ Job *job = (Job *)jobp;
+ int i, j;
+
+ noSpecials = NoExecute(job->node);
+
+ if (strcmp(cmd, "...") == 0) {
+ job->node->type |= OP_SAVE_CMDS;
+ if ((job->flags & JOB_IGNDOTS) == 0) {
+ job->tailCmds = Lst_Succ(Lst_Member(job->node->commands,
+ cmd));
+ return 1;
+ }
+ return 0;
+ }
+
+#define DBPRINTF(fmt, arg) if (DEBUG(JOB)) { \
+ (void)fprintf(debug_file, fmt, arg); \
+ } \
+ (void)fprintf(job->cmdFILE, fmt, arg); \
+ (void)fflush(job->cmdFILE);
+
+ numCommands += 1;
+
+ cmdStart = cmd = Var_Subst(NULL, cmd, job->node, VARF_WANTRES);
+
+ cmdTemplate = "%s\n";
+
+ /*
+ * Check for leading @' and -'s to control echoing and error checking.
+ */
+ while (*cmd == '@' || *cmd == '-' || (*cmd == '+')) {
+ switch (*cmd) {
+ case '@':
+ shutUp = DEBUG(LOUD) ? FALSE : TRUE;
+ break;
+ case '-':
+ errOff = TRUE;
+ break;
+ case '+':
+ if (noSpecials) {
+ /*
+ * We're not actually executing anything...
+ * but this one needs to be - use compat mode just for it.
+ */
+ CompatRunCommand(cmdp, job->node);
+ free(cmdStart);
+ return 0;
+ }
+ break;
+ }
+ cmd++;
+ }
+
+ while (isspace((unsigned char) *cmd))
+ cmd++;
+
+ /*
+ * If the shell doesn't have error control the alternate echo'ing will
+ * be done (to avoid showing additional error checking code)
+ * and this will need the characters '$ ` \ "' escaped
+ */
+
+ if (!commandShell->hasErrCtl) {
+ /* Worst that could happen is every char needs escaping. */
+ escCmd = bmake_malloc((strlen(cmd) * 2) + 1);
+ for (i = 0, j= 0; cmd[i] != '\0'; i++, j++) {
+ if (cmd[i] == '$' || cmd[i] == '`' || cmd[i] == '\\' ||
+ cmd[i] == '"')
+ escCmd[j++] = '\\';
+ escCmd[j] = cmd[i];
+ }
+ escCmd[j] = 0;
+ }
+
+ if (shutUp) {
+ if (!(job->flags & JOB_SILENT) && !noSpecials &&
+ commandShell->hasEchoCtl) {
+ DBPRINTF("%s\n", commandShell->echoOff);
+ } else {
+ if (commandShell->hasErrCtl)
+ shutUp = FALSE;
+ }
+ }
+
+ if (errOff) {
+ if (!noSpecials) {
+ if (commandShell->hasErrCtl) {
+ /*
+ * we don't want the error-control commands showing
+ * up either, so we turn off echoing while executing
+ * them. We could put another field in the shell
+ * structure to tell JobDoOutput to look for this
+ * string too, but why make it any more complex than
+ * it already is?
+ */
+ if (!(job->flags & JOB_SILENT) && !shutUp &&
+ commandShell->hasEchoCtl) {
+ DBPRINTF("%s\n", commandShell->echoOff);
+ DBPRINTF("%s\n", commandShell->ignErr);
+ DBPRINTF("%s\n", commandShell->echoOn);
+ } else {
+ DBPRINTF("%s\n", commandShell->ignErr);
+ }
+ } else if (commandShell->ignErr &&
+ (*commandShell->ignErr != '\0'))
+ {
+ /*
+ * The shell has no error control, so we need to be
+ * weird to get it to ignore any errors from the command.
+ * If echoing is turned on, we turn it off and use the
+ * errCheck template to echo the command. Leave echoing
+ * off so the user doesn't see the weirdness we go through
+ * to ignore errors. Set cmdTemplate to use the weirdness
+ * instead of the simple "%s\n" template.
+ */
+ job->flags |= JOB_IGNERR;
+ if (!(job->flags & JOB_SILENT) && !shutUp) {
+ if (commandShell->hasEchoCtl) {
+ DBPRINTF("%s\n", commandShell->echoOff);
+ }
+ DBPRINTF(commandShell->errCheck, escCmd);
+ shutUp = TRUE;
+ } else {
+ if (!shutUp) {
+ DBPRINTF(commandShell->errCheck, escCmd);
+ }
+ }
+ cmdTemplate = commandShell->ignErr;
+ /*
+ * The error ignoration (hee hee) is already taken care
+ * of by the ignErr template, so pretend error checking
+ * is still on.
+ */
+ errOff = FALSE;
+ } else {
+ errOff = FALSE;
+ }
+ } else {
+ errOff = FALSE;
+ }
+ } else {
+
+ /*
+ * If errors are being checked and the shell doesn't have error control
+ * but does supply an errOut template, then setup commands to run
+ * through it.
+ */
+
+ if (!commandShell->hasErrCtl && commandShell->errOut &&
+ (*commandShell->errOut != '\0')) {
+ if (!(job->flags & JOB_SILENT) && !shutUp) {
+ if (commandShell->hasEchoCtl) {
+ DBPRINTF("%s\n", commandShell->echoOff);
+ }
+ DBPRINTF(commandShell->errCheck, escCmd);
+ shutUp = TRUE;
+ }
+ /* If it's a comment line or blank, treat as an ignored error */
+ if ((escCmd[0] == commandShell->commentChar) ||
+ (escCmd[0] == 0))
+ cmdTemplate = commandShell->ignErr;
+ else
+ cmdTemplate = commandShell->errOut;
+ errOff = FALSE;
+ }
+ }
+
+ if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0 &&
+ (job->flags & JOB_TRACED) == 0) {
+ DBPRINTF("set -%s\n", "x");
+ job->flags |= JOB_TRACED;
+ }
+
+ DBPRINTF(cmdTemplate, cmd);
+ free(cmdStart);
+ free(escCmd);
+ if (errOff) {
+ /*
+ * If echoing is already off, there's no point in issuing the
+ * echoOff command. Otherwise we issue it and pretend it was on
+ * for the whole command...
+ */
+ if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl){
+ DBPRINTF("%s\n", commandShell->echoOff);
+ shutUp = TRUE;
+ }
+ DBPRINTF("%s\n", commandShell->errCheck);
+ }
+ if (shutUp && commandShell->hasEchoCtl) {
+ DBPRINTF("%s\n", commandShell->echoOn);
+ }
+ return 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobSaveCommand --
+ * Save a command to be executed when everything else is done.
+ * Callback function for JobFinish...
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * The command is tacked onto the end of postCommands's commands list.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+JobSaveCommand(void *cmd, void *gn)
+{
+ cmd = Var_Subst(NULL, (char *)cmd, (GNode *)gn, VARF_WANTRES);
+ (void)Lst_AtEnd(postCommands->commands, cmd);
+ return(0);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobClose --
+ * Called to close both input and output pipes when a job is finished.
+ *
+ * Results:
+ * Nada
+ *
+ * Side Effects:
+ * The file descriptors associated with the job are closed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobClose(Job *job)
+{
+ clearfd(job);
+ (void)close(job->outPipe);
+ job->outPipe = -1;
+
+ JobDoOutput(job, TRUE);
+ (void)close(job->inPipe);
+ job->inPipe = -1;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobFinish --
+ * Do final processing for the given job including updating
+ * parents and starting new jobs as available/necessary. Note
+ * that we pay no attention to the JOB_IGNERR flag here.
+ * This is because when we're called because of a noexecute flag
+ * or something, jstat.w_status is 0 and when called from
+ * Job_CatchChildren, the status is zeroed if it s/b ignored.
+ *
+ * Input:
+ * job job to finish
+ * status sub-why job went away
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Final commands for the job are placed on postCommands.
+ *
+ * If we got an error and are aborting (aborting == ABORT_ERROR) and
+ * the job list is now empty, we are done for the day.
+ * If we recognized an error (errors !=0), we set the aborting flag
+ * to ABORT_ERROR so no more jobs will be started.
+ *-----------------------------------------------------------------------
+ */
+/*ARGSUSED*/
+static void
+JobFinish(Job *job, int status)
+{
+ Boolean done, return_job_token;
+
+ if (DEBUG(JOB)) {
+ fprintf(debug_file, "Jobfinish: %d [%s], status %d\n",
+ job->pid, job->node->name, status);
+ }
+
+ if ((WIFEXITED(status) &&
+ (((WEXITSTATUS(status) != 0) && !(job->flags & JOB_IGNERR)))) ||
+ WIFSIGNALED(status))
+ {
+ /*
+ * If it exited non-zero and either we're doing things our
+ * way or we're not ignoring errors, the job is finished.
+ * Similarly, if the shell died because of a signal
+ * the job is also finished. In these
+ * cases, finish out the job's output before printing the exit
+ * status...
+ */
+ JobClose(job);
+ if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
+ (void)fclose(job->cmdFILE);
+ job->cmdFILE = NULL;
+ }
+ done = TRUE;
+ } else if (WIFEXITED(status)) {
+ /*
+ * Deal with ignored errors in -B mode. We need to print a message
+ * telling of the ignored error as well as setting status.w_status
+ * to 0 so the next command gets run. To do this, we set done to be
+ * TRUE if in -B mode and the job exited non-zero.
+ */
+ done = WEXITSTATUS(status) != 0;
+ /*
+ * Old comment said: "Note we don't
+ * want to close down any of the streams until we know we're at the
+ * end."
+ * But we do. Otherwise when are we going to print the rest of the
+ * stuff?
+ */
+ JobClose(job);
+ } else {
+ /*
+ * No need to close things down or anything.
+ */
+ done = FALSE;
+ }
+
+ if (done) {
+ if (WIFEXITED(status)) {
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file, "Process %d [%s] exited.\n",
+ job->pid, job->node->name);
+ }
+ if (WEXITSTATUS(status) != 0) {
+ if (job->node != lastNode) {
+ MESSAGE(stdout, job->node);
+ lastNode = job->node;
+ }
+#ifdef USE_META
+ if (useMeta) {
+ meta_job_error(job, job->node, job->flags, WEXITSTATUS(status));
+ }
+#endif
+ (void)printf("*** [%s] Error code %d%s\n",
+ job->node->name,
+ WEXITSTATUS(status),
+ (job->flags & JOB_IGNERR) ? " (ignored)" : "");
+ if (job->flags & JOB_IGNERR) {
+ status = 0;
+ } else {
+ if (deleteOnError) {
+ JobDeleteTarget(job->node);
+ }
+ PrintOnError(job->node, NULL);
+ }
+ } else if (DEBUG(JOB)) {
+ if (job->node != lastNode) {
+ MESSAGE(stdout, job->node);
+ lastNode = job->node;
+ }
+ (void)printf("*** [%s] Completed successfully\n",
+ job->node->name);
+ }
+ } else {
+ if (job->node != lastNode) {
+ MESSAGE(stdout, job->node);
+ lastNode = job->node;
+ }
+ (void)printf("*** [%s] Signal %d\n",
+ job->node->name, WTERMSIG(status));
+ if (deleteOnError) {
+ JobDeleteTarget(job->node);
+ }
+ }
+ (void)fflush(stdout);
+ }
+
+#ifdef USE_META
+ if (useMeta) {
+ int x;
+
+ if ((x = meta_job_finish(job)) != 0 && status == 0) {
+ status = x;
+ }
+ }
+#endif
+
+ return_job_token = FALSE;
+
+ Trace_Log(JOBEND, job);
+ if (!(job->flags & JOB_SPECIAL)) {
+ if ((status != 0) ||
+ (aborting == ABORT_ERROR) ||
+ (aborting == ABORT_INTERRUPT))
+ return_job_token = TRUE;
+ }
+
+ if ((aborting != ABORT_ERROR) && (aborting != ABORT_INTERRUPT) && (status == 0)) {
+ /*
+ * As long as we aren't aborting and the job didn't return a non-zero
+ * status that we shouldn't ignore, we call Make_Update to update
+ * the parents. In addition, any saved commands for the node are placed
+ * on the .END target.
+ */
+ if (job->tailCmds != NULL) {
+ Lst_ForEachFrom(job->node->commands, job->tailCmds,
+ JobSaveCommand,
+ job->node);
+ }
+ job->node->made = MADE;
+ if (!(job->flags & JOB_SPECIAL))
+ return_job_token = TRUE;
+ Make_Update(job->node);
+ job->job_state = JOB_ST_FREE;
+ } else if (status != 0) {
+ errors += 1;
+ job->job_state = JOB_ST_FREE;
+ }
+
+ /*
+ * Set aborting if any error.
+ */
+ if (errors && !keepgoing && (aborting != ABORT_INTERRUPT)) {
+ /*
+ * If we found any errors in this batch of children and the -k flag
+ * wasn't given, we set the aborting flag so no more jobs get
+ * started.
+ */
+ aborting = ABORT_ERROR;
+ }
+
+ if (return_job_token)
+ Job_TokenReturn();
+
+ if (aborting == ABORT_ERROR && jobTokensRunning == 0) {
+ /*
+ * If we are aborting and the job table is now empty, we finish.
+ */
+ Finish(errors);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Touch --
+ * Touch the given target. Called by JobStart when the -t flag was
+ * given
+ *
+ * Input:
+ * gn the node of the file to touch
+ * silent TRUE if should not print message
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The data modification of the file is changed. In addition, if the
+ * file did not exist, it is created.
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Touch(GNode *gn, Boolean silent)
+{
+ int streamID; /* ID of stream opened to do the touch */
+ struct utimbuf times; /* Times for utime() call */
+
+ if (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC|OP_OPTIONAL|
+ OP_SPECIAL|OP_PHONY)) {
+ /*
+ * .JOIN, .USE, .ZEROTIME and .OPTIONAL targets are "virtual" targets
+ * and, as such, shouldn't really be created.
+ */
+ return;
+ }
+
+ if (!silent || NoExecute(gn)) {
+ (void)fprintf(stdout, "touch %s\n", gn->name);
+ (void)fflush(stdout);
+ }
+
+ if (NoExecute(gn)) {
+ return;
+ }
+
+ if (gn->type & OP_ARCHV) {
+ Arch_Touch(gn);
+ } else if (gn->type & OP_LIB) {
+ Arch_TouchLib(gn);
+ } else {
+ char *file = gn->path ? gn->path : gn->name;
+
+ times.actime = times.modtime = now;
+ if (utime(file, &times) < 0){
+ streamID = open(file, O_RDWR | O_CREAT, 0666);
+
+ if (streamID >= 0) {
+ char c;
+
+ /*
+ * Read and write a byte to the file to change the
+ * modification time, then close the file.
+ */
+ if (read(streamID, &c, 1) == 1) {
+ (void)lseek(streamID, (off_t)0, SEEK_SET);
+ while (write(streamID, &c, 1) == -1 && errno == EAGAIN)
+ continue;
+ }
+
+ (void)close(streamID);
+ } else {
+ (void)fprintf(stdout, "*** couldn't touch %s: %s",
+ file, strerror(errno));
+ (void)fflush(stdout);
+ }
+ }
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_CheckCommands --
+ * Make sure the given node has all the commands it needs.
+ *
+ * Input:
+ * gn The target whose commands need verifying
+ * abortProc Function to abort with message
+ *
+ * Results:
+ * TRUE if the commands list is/was ok.
+ *
+ * Side Effects:
+ * The node will have commands from the .DEFAULT rule added to it
+ * if it needs them.
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...))
+{
+ if (OP_NOP(gn->type) && Lst_IsEmpty(gn->commands) &&
+ ((gn->type & OP_LIB) == 0 || Lst_IsEmpty(gn->children))) {
+ /*
+ * No commands. Look for .DEFAULT rule from which we might infer
+ * commands
+ */
+ if ((DEFAULT != NULL) && !Lst_IsEmpty(DEFAULT->commands) &&
+ (gn->type & OP_SPECIAL) == 0) {
+ char *p1;
+ /*
+ * Make only looks for a .DEFAULT if the node was never the
+ * target of an operator, so that's what we do too. If
+ * a .DEFAULT was given, we substitute its commands for gn's
+ * commands and set the IMPSRC variable to be the target's name
+ * The DEFAULT node acts like a transformation rule, in that
+ * gn also inherits any attributes or sources attached to
+ * .DEFAULT itself.
+ */
+ Make_HandleUse(DEFAULT, gn);
+ Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), gn, 0);
+ free(p1);
+ } else if (Dir_MTime(gn, 0) == 0 && (gn->type & OP_SPECIAL) == 0) {
+ /*
+ * The node wasn't the target of an operator we have no .DEFAULT
+ * rule to go on and the target doesn't already exist. There's
+ * nothing more we can do for this branch. If the -k flag wasn't
+ * given, we stop in our tracks, otherwise we just don't update
+ * this node's parents so they never get examined.
+ */
+ static const char msg[] = ": don't know how to make";
+
+ if (gn->flags & FROM_DEPEND) {
+ if (!Job_RunTarget(".STALE", gn->fname))
+ fprintf(stdout, "%s: %s, %d: ignoring stale %s for %s\n",
+ progname, gn->fname, gn->lineno, makeDependfile,
+ gn->name);
+ return TRUE;
+ }
+
+ if (gn->type & OP_OPTIONAL) {
+ (void)fprintf(stdout, "%s%s %s (ignored)\n", progname,
+ msg, gn->name);
+ (void)fflush(stdout);
+ } else if (keepgoing) {
+ (void)fprintf(stdout, "%s%s %s (continuing)\n", progname,
+ msg, gn->name);
+ (void)fflush(stdout);
+ return FALSE;
+ } else {
+ (*abortProc)("%s%s %s. Stop", progname, msg, gn->name);
+ return FALSE;
+ }
+ }
+ }
+ return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobExec --
+ * Execute the shell for the given job. Called from JobStart
+ *
+ * Input:
+ * job Job to execute
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * A shell is executed, outputs is altered and the Job structure added
+ * to the job table.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobExec(Job *job, char **argv)
+{
+ int cpid; /* ID of new child */
+ sigset_t mask;
+
+ job->flags &= ~JOB_TRACED;
+
+ if (DEBUG(JOB)) {
+ int i;
+
+ (void)fprintf(debug_file, "Running %s %sly\n", job->node->name, "local");
+ (void)fprintf(debug_file, "\tCommand: ");
+ for (i = 0; argv[i] != NULL; i++) {
+ (void)fprintf(debug_file, "%s ", argv[i]);
+ }
+ (void)fprintf(debug_file, "\n");
+ }
+
+ /*
+ * Some jobs produce no output and it's disconcerting to have
+ * no feedback of their running (since they produce no output, the
+ * banner with their name in it never appears). This is an attempt to
+ * provide that feedback, even if nothing follows it.
+ */
+ if ((lastNode != job->node) && !(job->flags & JOB_SILENT)) {
+ MESSAGE(stdout, job->node);
+ lastNode = job->node;
+ }
+
+ /* No interruptions until this job is on the `jobs' list */
+ JobSigLock(&mask);
+
+ /* Pre-emptively mark job running, pid still zero though */
+ job->job_state = JOB_ST_RUNNING;
+
+ cpid = vFork();
+ if (cpid == -1)
+ Punt("Cannot vfork: %s", strerror(errno));
+
+ if (cpid == 0) {
+ /* Child */
+ sigset_t tmask;
+
+#ifdef USE_META
+ if (useMeta) {
+ meta_job_child(job);
+ }
+#endif
+ /*
+ * Reset all signal handlers; this is necessary because we also
+ * need to unblock signals before we exec(2).
+ */
+ JobSigReset();
+
+ /* Now unblock signals */
+ sigemptyset(&tmask);
+ JobSigUnlock(&tmask);
+
+ /*
+ * Must duplicate the input stream down to the child's input and
+ * reset it to the beginning (again). Since the stream was marked
+ * close-on-exec, we must clear that bit in the new input.
+ */
+ if (dup2(FILENO(job->cmdFILE), 0) == -1) {
+ execError("dup2", "job->cmdFILE");
+ _exit(1);
+ }
+ if (fcntl(0, F_SETFD, 0) == -1) {
+ execError("fcntl clear close-on-exec", "stdin");
+ _exit(1);
+ }
+ if (lseek(0, (off_t)0, SEEK_SET) == -1) {
+ execError("lseek to 0", "stdin");
+ _exit(1);
+ }
+
+ if (job->node->type & (OP_MAKE | OP_SUBMAKE)) {
+ /*
+ * Pass job token pipe to submakes.
+ */
+ if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1) {
+ execError("clear close-on-exec", "tokenWaitJob.inPipe");
+ _exit(1);
+ }
+ if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1) {
+ execError("clear close-on-exec", "tokenWaitJob.outPipe");
+ _exit(1);
+ }
+ }
+
+ /*
+ * Set up the child's output to be routed through the pipe
+ * we've created for it.
+ */
+ if (dup2(job->outPipe, 1) == -1) {
+ execError("dup2", "job->outPipe");
+ _exit(1);
+ }
+ /*
+ * The output channels are marked close on exec. This bit was
+ * duplicated by the dup2(on some systems), so we have to clear
+ * it before routing the shell's error output to the same place as
+ * its standard output.
+ */
+ if (fcntl(1, F_SETFD, 0) == -1) {
+ execError("clear close-on-exec", "stdout");
+ _exit(1);
+ }
+ if (dup2(1, 2) == -1) {
+ execError("dup2", "1, 2");
+ _exit(1);
+ }
+
+ /*
+ * We want to switch the child into a different process family so
+ * we can kill it and all its descendants in one fell swoop,
+ * by killing its process family, but not commit suicide.
+ */
+#if defined(MAKE_NATIVE) || defined(HAVE_SETPGID)
+#if defined(SYSV)
+ /* XXX: dsl - I'm sure this should be setpgrp()... */
+ (void)setsid();
+#else
+ (void)setpgid(0, getpid());
+#endif
+#endif
+
+ Var_ExportVars();
+
+ (void)execv(shellPath, argv);
+ execError("exec", shellPath);
+ _exit(1);
+ }
+
+ /* Parent, continuing after the child exec */
+ job->pid = cpid;
+
+ Trace_Log(JOBSTART, job);
+
+ /*
+ * Set the current position in the buffer to the beginning
+ * and mark another stream to watch in the outputs mask
+ */
+ job->curPos = 0;
+
+ watchfd(job);
+
+ if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
+ (void)fclose(job->cmdFILE);
+ job->cmdFILE = NULL;
+ }
+
+ /*
+ * Now the job is actually running, add it to the table.
+ */
+ if (DEBUG(JOB)) {
+ fprintf(debug_file, "JobExec(%s): pid %d added to jobs table\n",
+ job->node->name, job->pid);
+ job_table_dump("job started");
+ }
+ JobSigUnlock(&mask);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobMakeArgv --
+ * Create the argv needed to execute the shell for a given job.
+ *
+ *
+ * Results:
+ *
+ * Side Effects:
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobMakeArgv(Job *job, char **argv)
+{
+ int argc;
+ static char args[10]; /* For merged arguments */
+
+ argv[0] = UNCONST(shellName);
+ argc = 1;
+
+ if ((commandShell->exit && (*commandShell->exit != '-')) ||
+ (commandShell->echo && (*commandShell->echo != '-')))
+ {
+ /*
+ * At least one of the flags doesn't have a minus before it, so
+ * merge them together. Have to do this because the *(&(@*#*&#$#
+ * Bourne shell thinks its second argument is a file to source.
+ * Grrrr. Note the ten-character limitation on the combined arguments.
+ */
+ (void)snprintf(args, sizeof(args), "-%s%s",
+ ((job->flags & JOB_IGNERR) ? "" :
+ (commandShell->exit ? commandShell->exit : "")),
+ ((job->flags & JOB_SILENT) ? "" :
+ (commandShell->echo ? commandShell->echo : "")));
+
+ if (args[1]) {
+ argv[argc] = args;
+ argc++;
+ }
+ } else {
+ if (!(job->flags & JOB_IGNERR) && commandShell->exit) {
+ argv[argc] = UNCONST(commandShell->exit);
+ argc++;
+ }
+ if (!(job->flags & JOB_SILENT) && commandShell->echo) {
+ argv[argc] = UNCONST(commandShell->echo);
+ argc++;
+ }
+ }
+ argv[argc] = NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobStart --
+ * Start a target-creation process going for the target described
+ * by the graph node gn.
+ *
+ * Input:
+ * gn target to create
+ * flags flags for the job to override normal ones.
+ * e.g. JOB_SPECIAL or JOB_IGNDOTS
+ * previous The previous Job structure for this node, if any.
+ *
+ * Results:
+ * JOB_ERROR if there was an error in the commands, JOB_FINISHED
+ * if there isn't actually anything left to do for the job and
+ * JOB_RUNNING if the job has been started.
+ *
+ * Side Effects:
+ * A new Job node is created and added to the list of running
+ * jobs. PMake is forked and a child shell created.
+ *
+ * NB: I'm fairly sure that this code is never called with JOB_SPECIAL set
+ * JOB_IGNDOTS is never set (dsl)
+ * Also the return value is ignored by everyone.
+ *-----------------------------------------------------------------------
+ */
+static int
+JobStart(GNode *gn, int flags)
+{
+ Job *job; /* new job descriptor */
+ char *argv[10]; /* Argument vector to shell */
+ Boolean cmdsOK; /* true if the nodes commands were all right */
+ Boolean noExec; /* Set true if we decide not to run the job */
+ int tfd; /* File descriptor to the temp file */
+
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->job_state == JOB_ST_FREE)
+ break;
+ }
+ if (job >= job_table_end)
+ Punt("JobStart no job slots vacant");
+
+ memset(job, 0, sizeof *job);
+ job->job_state = JOB_ST_SETUP;
+ if (gn->type & OP_SPECIAL)
+ flags |= JOB_SPECIAL;
+
+ job->node = gn;
+ job->tailCmds = NULL;
+
+ /*
+ * Set the initial value of the flags for this job based on the global
+ * ones and the node's attributes... Any flags supplied by the caller
+ * are also added to the field.
+ */
+ job->flags = 0;
+ if (Targ_Ignore(gn)) {
+ job->flags |= JOB_IGNERR;
+ }
+ if (Targ_Silent(gn)) {
+ job->flags |= JOB_SILENT;
+ }
+ job->flags |= flags;
+
+ /*
+ * Check the commands now so any attributes from .DEFAULT have a chance
+ * to migrate to the node
+ */
+ cmdsOK = Job_CheckCommands(gn, Error);
+
+ job->inPollfd = NULL;
+ /*
+ * If the -n flag wasn't given, we open up OUR (not the child's)
+ * temporary file to stuff commands in it. The thing is rd/wr so we don't
+ * need to reopen it to feed it to the shell. If the -n flag *was* given,
+ * we just set the file to be stdout. Cute, huh?
+ */
+ if (((gn->type & OP_MAKE) && !(noRecursiveExecute)) ||
+ (!noExecute && !touchFlag)) {
+ /*
+ * tfile is the name of a file into which all shell commands are
+ * put. It is removed before the child shell is executed, unless
+ * DEBUG(SCRIPT) is set.
+ */
+ char *tfile;
+ sigset_t mask;
+ /*
+ * We're serious here, but if the commands were bogus, we're
+ * also dead...
+ */
+ if (!cmdsOK) {
+ PrintOnError(gn, NULL); /* provide some clue */
+ DieHorribly();
+ }
+
+ JobSigLock(&mask);
+ tfd = mkTempFile(TMPPAT, &tfile);
+ if (!DEBUG(SCRIPT))
+ (void)eunlink(tfile);
+ JobSigUnlock(&mask);
+
+ job->cmdFILE = fdopen(tfd, "w+");
+ if (job->cmdFILE == NULL) {
+ Punt("Could not fdopen %s", tfile);
+ }
+ (void)fcntl(FILENO(job->cmdFILE), F_SETFD, FD_CLOEXEC);
+ /*
+ * Send the commands to the command file, flush all its buffers then
+ * rewind and remove the thing.
+ */
+ noExec = FALSE;
+
+#ifdef USE_META
+ if (useMeta) {
+ meta_job_start(job, gn);
+ if (Targ_Silent(gn)) { /* might have changed */
+ job->flags |= JOB_SILENT;
+ }
+ }
+#endif
+ /*
+ * We can do all the commands at once. hooray for sanity
+ */
+ numCommands = 0;
+ Lst_ForEach(gn->commands, JobPrintCommand, job);
+
+ /*
+ * If we didn't print out any commands to the shell script,
+ * there's not much point in executing the shell, is there?
+ */
+ if (numCommands == 0) {
+ noExec = TRUE;
+ }
+
+ free(tfile);
+ } else if (NoExecute(gn)) {
+ /*
+ * Not executing anything -- just print all the commands to stdout
+ * in one fell swoop. This will still set up job->tailCmds correctly.
+ */
+ if (lastNode != gn) {
+ MESSAGE(stdout, gn);
+ lastNode = gn;
+ }
+ job->cmdFILE = stdout;
+ /*
+ * Only print the commands if they're ok, but don't die if they're
+ * not -- just let the user know they're bad and keep going. It
+ * doesn't do any harm in this case and may do some good.
+ */
+ if (cmdsOK) {
+ Lst_ForEach(gn->commands, JobPrintCommand, job);
+ }
+ /*
+ * Don't execute the shell, thank you.
+ */
+ noExec = TRUE;
+ } else {
+ /*
+ * Just touch the target and note that no shell should be executed.
+ * Set cmdFILE to stdout to make life easier. Check the commands, too,
+ * but don't die if they're no good -- it does no harm to keep working
+ * up the graph.
+ */
+ job->cmdFILE = stdout;
+ Job_Touch(gn, job->flags&JOB_SILENT);
+ noExec = TRUE;
+ }
+ /* Just in case it isn't already... */
+ (void)fflush(job->cmdFILE);
+
+ /*
+ * If we're not supposed to execute a shell, don't.
+ */
+ if (noExec) {
+ if (!(job->flags & JOB_SPECIAL))
+ Job_TokenReturn();
+ /*
+ * Unlink and close the command file if we opened one
+ */
+ if (job->cmdFILE != stdout) {
+ if (job->cmdFILE != NULL) {
+ (void)fclose(job->cmdFILE);
+ job->cmdFILE = NULL;
+ }
+ }
+
+ /*
+ * We only want to work our way up the graph if we aren't here because
+ * the commands for the job were no good.
+ */
+ if (cmdsOK && aborting == 0) {
+ if (job->tailCmds != NULL) {
+ Lst_ForEachFrom(job->node->commands, job->tailCmds,
+ JobSaveCommand,
+ job->node);
+ }
+ job->node->made = MADE;
+ Make_Update(job->node);
+ }
+ job->job_state = JOB_ST_FREE;
+ return cmdsOK ? JOB_FINISHED : JOB_ERROR;
+ }
+
+ /*
+ * Set up the control arguments to the shell. This is based on the flags
+ * set earlier for this job.
+ */
+ JobMakeArgv(job, argv);
+
+ /* Create the pipe by which we'll get the shell's output. */
+ JobCreatePipe(job, 3);
+
+ JobExec(job, argv);
+ return(JOB_RUNNING);
+}
+
+static char *
+JobOutput(Job *job, char *cp, char *endp, int msg)
+{
+ char *ecp;
+
+ if (commandShell->noPrint) {
+ ecp = Str_FindSubstring(cp, commandShell->noPrint);
+ while (ecp != NULL) {
+ if (cp != ecp) {
+ *ecp = '\0';
+ if (!beSilent && msg && job->node != lastNode) {
+ MESSAGE(stdout, job->node);
+ lastNode = job->node;
+ }
+ /*
+ * The only way there wouldn't be a newline after
+ * this line is if it were the last in the buffer.
+ * however, since the non-printable comes after it,
+ * there must be a newline, so we don't print one.
+ */
+ (void)fprintf(stdout, "%s", cp);
+ (void)fflush(stdout);
+ }
+ cp = ecp + commandShell->noPLen;
+ if (cp != endp) {
+ /*
+ * Still more to print, look again after skipping
+ * the whitespace following the non-printable
+ * command....
+ */
+ cp++;
+ while (*cp == ' ' || *cp == '\t' || *cp == '\n') {
+ cp++;
+ }
+ ecp = Str_FindSubstring(cp, commandShell->noPrint);
+ } else {
+ return cp;
+ }
+ }
+ }
+ return cp;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobDoOutput --
+ * This function is called at different times depending on
+ * whether the user has specified that output is to be collected
+ * via pipes or temporary files. In the former case, we are called
+ * whenever there is something to read on the pipe. We collect more
+ * output from the given job and store it in the job's outBuf. If
+ * this makes up a line, we print it tagged by the job's identifier,
+ * as necessary.
+ * If output has been collected in a temporary file, we open the
+ * file and read it line by line, transfering it to our own
+ * output channel until the file is empty. At which point we
+ * remove the temporary file.
+ * In both cases, however, we keep our figurative eye out for the
+ * 'noPrint' line for the shell from which the output came. If
+ * we recognize a line, we don't print it. If the command is not
+ * alone on the line (the character after it is not \0 or \n), we
+ * do print whatever follows it.
+ *
+ * Input:
+ * job the job whose output needs printing
+ * finish TRUE if this is the last time we'll be called
+ * for this job
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * curPos may be shifted as may the contents of outBuf.
+ *-----------------------------------------------------------------------
+ */
+STATIC void
+JobDoOutput(Job *job, Boolean finish)
+{
+ Boolean gotNL = FALSE; /* true if got a newline */
+ Boolean fbuf; /* true if our buffer filled up */
+ int nr; /* number of bytes read */
+ int i; /* auxiliary index into outBuf */
+ int max; /* limit for i (end of current data) */
+ int nRead; /* (Temporary) number of bytes read */
+
+ /*
+ * Read as many bytes as will fit in the buffer.
+ */
+end_loop:
+ gotNL = FALSE;
+ fbuf = FALSE;
+
+ nRead = read(job->inPipe, &job->outBuf[job->curPos],
+ JOB_BUFSIZE - job->curPos);
+ if (nRead < 0) {
+ if (errno == EAGAIN)
+ return;
+ if (DEBUG(JOB)) {
+ perror("JobDoOutput(piperead)");
+ }
+ nr = 0;
+ } else {
+ nr = nRead;
+ }
+
+ /*
+ * If we hit the end-of-file (the job is dead), we must flush its
+ * remaining output, so pretend we read a newline if there's any
+ * output remaining in the buffer.
+ * Also clear the 'finish' flag so we stop looping.
+ */
+ if ((nr == 0) && (job->curPos != 0)) {
+ job->outBuf[job->curPos] = '\n';
+ nr = 1;
+ finish = FALSE;
+ } else if (nr == 0) {
+ finish = FALSE;
+ }
+
+ /*
+ * Look for the last newline in the bytes we just got. If there is
+ * one, break out of the loop with 'i' as its index and gotNL set
+ * TRUE.
+ */
+ max = job->curPos + nr;
+ for (i = job->curPos + nr - 1; i >= job->curPos; i--) {
+ if (job->outBuf[i] == '\n') {
+ gotNL = TRUE;
+ break;
+ } else if (job->outBuf[i] == '\0') {
+ /*
+ * Why?
+ */
+ job->outBuf[i] = ' ';
+ }
+ }
+
+ if (!gotNL) {
+ job->curPos += nr;
+ if (job->curPos == JOB_BUFSIZE) {
+ /*
+ * If we've run out of buffer space, we have no choice
+ * but to print the stuff. sigh.
+ */
+ fbuf = TRUE;
+ i = job->curPos;
+ }
+ }
+ if (gotNL || fbuf) {
+ /*
+ * Need to send the output to the screen. Null terminate it
+ * first, overwriting the newline character if there was one.
+ * So long as the line isn't one we should filter (according
+ * to the shell description), we print the line, preceded
+ * by a target banner if this target isn't the same as the
+ * one for which we last printed something.
+ * The rest of the data in the buffer are then shifted down
+ * to the start of the buffer and curPos is set accordingly.
+ */
+ job->outBuf[i] = '\0';
+ if (i >= job->curPos) {
+ char *cp;
+
+ cp = JobOutput(job, job->outBuf, &job->outBuf[i], FALSE);
+
+ /*
+ * There's still more in that thar buffer. This time, though,
+ * we know there's no newline at the end, so we add one of
+ * our own free will.
+ */
+ if (*cp != '\0') {
+ if (!beSilent && job->node != lastNode) {
+ MESSAGE(stdout, job->node);
+ lastNode = job->node;
+ }
+#ifdef USE_META
+ if (useMeta) {
+ meta_job_output(job, cp, gotNL ? "\n" : "");
+ }
+#endif
+ (void)fprintf(stdout, "%s%s", cp, gotNL ? "\n" : "");
+ (void)fflush(stdout);
+ }
+ }
+ /*
+ * max is the last offset still in the buffer. Move any remaining
+ * characters to the start of the buffer and update the end marker
+ * curPos.
+ */
+ if (i < max) {
+ (void)memmove(job->outBuf, &job->outBuf[i + 1], max - (i + 1));
+ job->curPos = max - (i + 1);
+ } else {
+ assert(i == max);
+ job->curPos = 0;
+ }
+ }
+ if (finish) {
+ /*
+ * If the finish flag is true, we must loop until we hit
+ * end-of-file on the pipe. This is guaranteed to happen
+ * eventually since the other end of the pipe is now closed
+ * (we closed it explicitly and the child has exited). When
+ * we do get an EOF, finish will be set FALSE and we'll fall
+ * through and out.
+ */
+ goto end_loop;
+ }
+}
+
+static void
+JobRun(GNode *targ)
+{
+#ifdef notyet
+ /*
+ * Unfortunately it is too complicated to run .BEGIN, .END,
+ * and .INTERRUPT job in the parallel job module. This has
+ * the nice side effect that it avoids a lot of other problems.
+ */
+ Lst lst = Lst_Init(FALSE);
+ Lst_AtEnd(lst, targ);
+ (void)Make_Run(lst);
+ Lst_Destroy(lst, NULL);
+ JobStart(targ, JOB_SPECIAL);
+ while (jobTokensRunning) {
+ Job_CatchOutput();
+ }
+#else
+ Compat_Make(targ, targ);
+ if (targ->made == ERROR) {
+ PrintOnError(targ, "\n\nStop.");
+ exit(1);
+ }
+#endif
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_CatchChildren --
+ * Handle the exit of a child. Called from Make_Make.
+ *
+ * Input:
+ * block TRUE if should block on the wait
+ *
+ * Results:
+ * none.
+ *
+ * Side Effects:
+ * The job descriptor is removed from the list of children.
+ *
+ * Notes:
+ * We do waits, blocking or not, according to the wisdom of our
+ * caller, until there are no more children to report. For each
+ * job, call JobFinish to finish things off.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+void
+Job_CatchChildren(void)
+{
+ int pid; /* pid of dead child */
+ int status; /* Exit/termination status */
+
+ /*
+ * Don't even bother if we know there's no one around.
+ */
+ if (jobTokensRunning == 0)
+ return;
+
+ while ((pid = waitpid((pid_t) -1, &status, WNOHANG | WUNTRACED)) > 0) {
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file, "Process %d exited/stopped status %x.\n", pid,
+ status);
+ }
+ JobReapChild(pid, status, TRUE);
+ }
+}
+
+/*
+ * It is possible that wait[pid]() was called from elsewhere,
+ * this lets us reap jobs regardless.
+ */
+void
+JobReapChild(pid_t pid, int status, Boolean isJobs)
+{
+ Job *job; /* job descriptor for dead child */
+
+ /*
+ * Don't even bother if we know there's no one around.
+ */
+ if (jobTokensRunning == 0)
+ return;
+
+ job = JobFindPid(pid, JOB_ST_RUNNING, isJobs);
+ if (job == NULL) {
+ if (isJobs) {
+ if (!lurking_children)
+ Error("Child (%d) status %x not in table?", pid, status);
+ }
+ return; /* not ours */
+ }
+ if (WIFSTOPPED(status)) {
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file, "Process %d (%s) stopped.\n",
+ job->pid, job->node->name);
+ }
+ if (!make_suspended) {
+ switch (WSTOPSIG(status)) {
+ case SIGTSTP:
+ (void)printf("*** [%s] Suspended\n", job->node->name);
+ break;
+ case SIGSTOP:
+ (void)printf("*** [%s] Stopped\n", job->node->name);
+ break;
+ default:
+ (void)printf("*** [%s] Stopped -- signal %d\n",
+ job->node->name, WSTOPSIG(status));
+ }
+ job->job_suspended = 1;
+ }
+ (void)fflush(stdout);
+ return;
+ }
+
+ job->job_state = JOB_ST_FINISHED;
+ job->exit_status = status;
+
+ JobFinish(job, status);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_CatchOutput --
+ * Catch the output from our children, if we're using
+ * pipes do so. Otherwise just block time until we get a
+ * signal(most likely a SIGCHLD) since there's no point in
+ * just spinning when there's nothing to do and the reaping
+ * of a child can wait for a while.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Output is read from pipes if we're piping.
+ * -----------------------------------------------------------------------
+ */
+void
+Job_CatchOutput(void)
+{
+ int nready;
+ Job *job;
+ int i;
+
+ (void)fflush(stdout);
+
+ /* The first fd in the list is the job token pipe */
+ do {
+ nready = poll(fds + 1 - wantToken, nfds - 1 + wantToken, POLL_MSEC);
+ } while (nready < 0 && errno == EINTR);
+
+ if (nready < 0)
+ Punt("poll: %s", strerror(errno));
+
+ if (nready > 0 && readyfd(&childExitJob)) {
+ char token = 0;
+ ssize_t count;
+ count = read(childExitJob.inPipe, &token, 1);
+ switch (count) {
+ case 0:
+ Punt("unexpected eof on token pipe");
+ case -1:
+ Punt("token pipe read: %s", strerror(errno));
+ case 1:
+ if (token == DO_JOB_RESUME[0])
+ /* Complete relay requested from our SIGCONT handler */
+ JobRestartJobs();
+ break;
+ default:
+ abort();
+ }
+ --nready;
+ }
+
+ Job_CatchChildren();
+ if (nready == 0)
+ return;
+
+ for (i = 2; i < nfds; i++) {
+ if (!fds[i].revents)
+ continue;
+ job = jobfds[i];
+ if (job->job_state == JOB_ST_RUNNING)
+ JobDoOutput(job, FALSE);
+ if (--nready == 0)
+ return;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Make --
+ * Start the creation of a target. Basically a front-end for
+ * JobStart used by the Make module.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Another job is started.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Make(GNode *gn)
+{
+ (void)JobStart(gn, 0);
+}
+
+void
+Shell_Init(void)
+{
+ if (shellPath == NULL) {
+ /*
+ * We are using the default shell, which may be an absolute
+ * path if DEFSHELL_CUSTOM is defined.
+ */
+ shellName = commandShell->name;
+#ifdef DEFSHELL_CUSTOM
+ if (*shellName == '/') {
+ shellPath = shellName;
+ shellName = strrchr(shellPath, '/');
+ shellName++;
+ } else
+#endif
+ shellPath = str_concat(_PATH_DEFSHELLDIR, shellName, STR_ADDSLASH);
+ }
+ if (commandShell->exit == NULL) {
+ commandShell->exit = "";
+ }
+ if (commandShell->echo == NULL) {
+ commandShell->echo = "";
+ }
+ if (commandShell->hasErrCtl && *commandShell->exit) {
+ if (shellErrFlag &&
+ strcmp(commandShell->exit, &shellErrFlag[1]) != 0) {
+ free(shellErrFlag);
+ shellErrFlag = NULL;
+ }
+ if (!shellErrFlag) {
+ int n = strlen(commandShell->exit) + 2;
+
+ shellErrFlag = bmake_malloc(n);
+ if (shellErrFlag) {
+ snprintf(shellErrFlag, n, "-%s", commandShell->exit);
+ }
+ }
+ } else if (shellErrFlag) {
+ free(shellErrFlag);
+ shellErrFlag = NULL;
+ }
+}
+
+/*-
+ * Returns the string literal that is used in the current command shell
+ * to produce a newline character.
+ */
+const char *
+Shell_GetNewline(void)
+{
+
+ return commandShell->newline;
+}
+
+void
+Job_SetPrefix(void)
+{
+
+ if (targPrefix) {
+ free(targPrefix);
+ } else if (!Var_Exists(MAKE_JOB_PREFIX, VAR_GLOBAL)) {
+ Var_Set(MAKE_JOB_PREFIX, "---", VAR_GLOBAL, 0);
+ }
+
+ targPrefix = Var_Subst(NULL, "${" MAKE_JOB_PREFIX "}",
+ VAR_GLOBAL, VARF_WANTRES);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Init --
+ * Initialize the process module
+ *
+ * Input:
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * lists and counters are initialized
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Init(void)
+{
+ Job_SetPrefix();
+ /* Allocate space for all the job info */
+ job_table = bmake_malloc(maxJobs * sizeof *job_table);
+ memset(job_table, 0, maxJobs * sizeof *job_table);
+ job_table_end = job_table + maxJobs;
+ wantToken = 0;
+
+ aborting = 0;
+ errors = 0;
+
+ lastNode = NULL;
+
+ /*
+ * There is a non-zero chance that we already have children.
+ * eg after 'make -f- <<EOF'
+ * Since their termination causes a 'Child (pid) not in table' message,
+ * Collect the status of any that are already dead, and suppress the
+ * error message if there are any undead ones.
+ */
+ for (;;) {
+ int rval, status;
+ rval = waitpid((pid_t) -1, &status, WNOHANG);
+ if (rval > 0)
+ continue;
+ if (rval == 0)
+ lurking_children = 1;
+ break;
+ }
+
+ Shell_Init();
+
+ JobCreatePipe(&childExitJob, 3);
+
+ /* We can only need to wait for tokens, children and output from each job */
+ fds = bmake_malloc(sizeof (*fds) * (2 + maxJobs));
+ jobfds = bmake_malloc(sizeof (*jobfds) * (2 + maxJobs));
+
+ /* These are permanent entries and take slots 0 and 1 */
+ watchfd(&tokenWaitJob);
+ watchfd(&childExitJob);
+
+ sigemptyset(&caught_signals);
+ /*
+ * Install a SIGCHLD handler.
+ */
+ (void)bmake_signal(SIGCHLD, JobChildSig);
+ sigaddset(&caught_signals, SIGCHLD);
+
+#define ADDSIG(s,h) \
+ if (bmake_signal(s, SIG_IGN) != SIG_IGN) { \
+ sigaddset(&caught_signals, s); \
+ (void)bmake_signal(s, h); \
+ }
+
+ /*
+ * Catch the four signals that POSIX specifies if they aren't ignored.
+ * JobPassSig will take care of calling JobInterrupt if appropriate.
+ */
+ ADDSIG(SIGINT, JobPassSig_int)
+ ADDSIG(SIGHUP, JobPassSig_term)
+ ADDSIG(SIGTERM, JobPassSig_term)
+ ADDSIG(SIGQUIT, JobPassSig_term)
+
+ /*
+ * There are additional signals that need to be caught and passed if
+ * either the export system wants to be told directly of signals or if
+ * we're giving each job its own process group (since then it won't get
+ * signals from the terminal driver as we own the terminal)
+ */
+ ADDSIG(SIGTSTP, JobPassSig_suspend)
+ ADDSIG(SIGTTOU, JobPassSig_suspend)
+ ADDSIG(SIGTTIN, JobPassSig_suspend)
+ ADDSIG(SIGWINCH, JobCondPassSig)
+ ADDSIG(SIGCONT, JobContinueSig)
+#undef ADDSIG
+
+ (void)Job_RunTarget(".BEGIN", NULL);
+ postCommands = Targ_FindNode(".END", TARG_CREATE);
+}
+
+static void JobSigReset(void)
+{
+#define DELSIG(s) \
+ if (sigismember(&caught_signals, s)) { \
+ (void)bmake_signal(s, SIG_DFL); \
+ }
+
+ DELSIG(SIGINT)
+ DELSIG(SIGHUP)
+ DELSIG(SIGQUIT)
+ DELSIG(SIGTERM)
+ DELSIG(SIGTSTP)
+ DELSIG(SIGTTOU)
+ DELSIG(SIGTTIN)
+ DELSIG(SIGWINCH)
+ DELSIG(SIGCONT)
+#undef DELSIG
+ (void)bmake_signal(SIGCHLD, SIG_DFL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobMatchShell --
+ * Find a shell in 'shells' given its name.
+ *
+ * Results:
+ * A pointer to the Shell structure.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Shell *
+JobMatchShell(const char *name)
+{
+ Shell *sh;
+
+ for (sh = shells; sh->name != NULL; sh++) {
+ if (strcmp(name, sh->name) == 0)
+ return (sh);
+ }
+ return NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_ParseShell --
+ * Parse a shell specification and set up commandShell, shellPath
+ * and shellName appropriately.
+ *
+ * Input:
+ * line The shell spec
+ *
+ * Results:
+ * FAILURE if the specification was incorrect.
+ *
+ * Side Effects:
+ * commandShell points to a Shell structure (either predefined or
+ * created from the shell spec), shellPath is the full path of the
+ * shell described by commandShell, while shellName is just the
+ * final component of shellPath.
+ *
+ * Notes:
+ * A shell specification consists of a .SHELL target, with dependency
+ * operator, followed by a series of blank-separated words. Double
+ * quotes can be used to use blanks in words. A backslash escapes
+ * anything (most notably a double-quote and a space) and
+ * provides the functionality it does in C. Each word consists of
+ * keyword and value separated by an equal sign. There should be no
+ * unnecessary spaces in the word. The keywords are as follows:
+ * name Name of shell.
+ * path Location of shell.
+ * quiet Command to turn off echoing.
+ * echo Command to turn echoing on
+ * filter Result of turning off echoing that shouldn't be
+ * printed.
+ * echoFlag Flag to turn echoing on at the start
+ * errFlag Flag to turn error checking on at the start
+ * hasErrCtl True if shell has error checking control
+ * newline String literal to represent a newline char
+ * check Command to turn on error checking if hasErrCtl
+ * is TRUE or template of command to echo a command
+ * for which error checking is off if hasErrCtl is
+ * FALSE.
+ * ignore Command to turn off error checking if hasErrCtl
+ * is TRUE or template of command to execute a
+ * command so as to ignore any errors it returns if
+ * hasErrCtl is FALSE.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Job_ParseShell(char *line)
+{
+ char **words;
+ char **argv;
+ int argc;
+ char *path;
+ Shell newShell;
+ Boolean fullSpec = FALSE;
+ Shell *sh;
+
+ while (isspace((unsigned char)*line)) {
+ line++;
+ }
+
+ free(UNCONST(shellArgv));
+
+ memset(&newShell, 0, sizeof(newShell));
+
+ /*
+ * Parse the specification by keyword
+ */
+ words = brk_string(line, &argc, TRUE, &path);
+ if (words == NULL) {
+ Error("Unterminated quoted string [%s]", line);
+ return FAILURE;
+ }
+ shellArgv = path;
+
+ for (path = NULL, argv = words; argc != 0; argc--, argv++) {
+ if (strncmp(*argv, "path=", 5) == 0) {
+ path = &argv[0][5];
+ } else if (strncmp(*argv, "name=", 5) == 0) {
+ newShell.name = &argv[0][5];
+ } else {
+ if (strncmp(*argv, "quiet=", 6) == 0) {
+ newShell.echoOff = &argv[0][6];
+ } else if (strncmp(*argv, "echo=", 5) == 0) {
+ newShell.echoOn = &argv[0][5];
+ } else if (strncmp(*argv, "filter=", 7) == 0) {
+ newShell.noPrint = &argv[0][7];
+ newShell.noPLen = strlen(newShell.noPrint);
+ } else if (strncmp(*argv, "echoFlag=", 9) == 0) {
+ newShell.echo = &argv[0][9];
+ } else if (strncmp(*argv, "errFlag=", 8) == 0) {
+ newShell.exit = &argv[0][8];
+ } else if (strncmp(*argv, "hasErrCtl=", 10) == 0) {
+ char c = argv[0][10];
+ newShell.hasErrCtl = !((c != 'Y') && (c != 'y') &&
+ (c != 'T') && (c != 't'));
+ } else if (strncmp(*argv, "newline=", 8) == 0) {
+ newShell.newline = &argv[0][8];
+ } else if (strncmp(*argv, "check=", 6) == 0) {
+ newShell.errCheck = &argv[0][6];
+ } else if (strncmp(*argv, "ignore=", 7) == 0) {
+ newShell.ignErr = &argv[0][7];
+ } else if (strncmp(*argv, "errout=", 7) == 0) {
+ newShell.errOut = &argv[0][7];
+ } else if (strncmp(*argv, "comment=", 8) == 0) {
+ newShell.commentChar = argv[0][8];
+ } else {
+ Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"",
+ *argv);
+ free(words);
+ return(FAILURE);
+ }
+ fullSpec = TRUE;
+ }
+ }
+
+ if (path == NULL) {
+ /*
+ * If no path was given, the user wants one of the pre-defined shells,
+ * yes? So we find the one s/he wants with the help of JobMatchShell
+ * and set things up the right way. shellPath will be set up by
+ * Shell_Init.
+ */
+ if (newShell.name == NULL) {
+ Parse_Error(PARSE_FATAL, "Neither path nor name specified");
+ free(words);
+ return(FAILURE);
+ } else {
+ if ((sh = JobMatchShell(newShell.name)) == NULL) {
+ Parse_Error(PARSE_WARNING, "%s: No matching shell",
+ newShell.name);
+ free(words);
+ return(FAILURE);
+ }
+ commandShell = sh;
+ shellName = newShell.name;
+ if (shellPath) {
+ /* Shell_Init has already been called! Do it again. */
+ free(UNCONST(shellPath));
+ shellPath = NULL;
+ Shell_Init();
+ }
+ }
+ } else {
+ /*
+ * The user provided a path. If s/he gave nothing else (fullSpec is
+ * FALSE), try and find a matching shell in the ones we know of.
+ * Else we just take the specification at its word and copy it
+ * to a new location. In either case, we need to record the
+ * path the user gave for the shell.
+ */
+ shellPath = path;
+ path = strrchr(path, '/');
+ if (path == NULL) {
+ path = UNCONST(shellPath);
+ } else {
+ path += 1;
+ }
+ if (newShell.name != NULL) {
+ shellName = newShell.name;
+ } else {
+ shellName = path;
+ }
+ if (!fullSpec) {
+ if ((sh = JobMatchShell(shellName)) == NULL) {
+ Parse_Error(PARSE_WARNING, "%s: No matching shell",
+ shellName);
+ free(words);
+ return(FAILURE);
+ }
+ commandShell = sh;
+ } else {
+ commandShell = bmake_malloc(sizeof(Shell));
+ *commandShell = newShell;
+ }
+ /* this will take care of shellErrFlag */
+ Shell_Init();
+ }
+
+ if (commandShell->echoOn && commandShell->echoOff) {
+ commandShell->hasEchoCtl = TRUE;
+ }
+
+ if (!commandShell->hasErrCtl) {
+ if (commandShell->errCheck == NULL) {
+ commandShell->errCheck = "";
+ }
+ if (commandShell->ignErr == NULL) {
+ commandShell->ignErr = "%s\n";
+ }
+ }
+
+ /*
+ * Do not free up the words themselves, since they might be in use by the
+ * shell specification.
+ */
+ free(words);
+ return SUCCESS;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobInterrupt --
+ * Handle the receipt of an interrupt.
+ *
+ * Input:
+ * runINTERRUPT Non-zero if commands for the .INTERRUPT target
+ * should be executed
+ * signo signal received
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * All children are killed. Another job will be started if the
+ * .INTERRUPT target was given.
+ *-----------------------------------------------------------------------
+ */
+static void
+JobInterrupt(int runINTERRUPT, int signo)
+{
+ Job *job; /* job descriptor in that element */
+ GNode *interrupt; /* the node describing the .INTERRUPT target */
+ sigset_t mask;
+ GNode *gn;
+
+ aborting = ABORT_INTERRUPT;
+
+ JobSigLock(&mask);
+
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->job_state != JOB_ST_RUNNING)
+ continue;
+
+ gn = job->node;
+
+ JobDeleteTarget(gn);
+ if (job->pid) {
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file,
+ "JobInterrupt passing signal %d to child %d.\n",
+ signo, job->pid);
+ }
+ KILLPG(job->pid, signo);
+ }
+ }
+
+ JobSigUnlock(&mask);
+
+ if (runINTERRUPT && !touchFlag) {
+ interrupt = Targ_FindNode(".INTERRUPT", TARG_NOCREATE);
+ if (interrupt != NULL) {
+ ignoreErrors = FALSE;
+ JobRun(interrupt);
+ }
+ }
+ Trace_Log(MAKEINTR, 0);
+ exit(signo);
+}
+
+/*
+ *-----------------------------------------------------------------------
+ * Job_Finish --
+ * Do final processing such as the running of the commands
+ * attached to the .END target.
+ *
+ * Results:
+ * Number of errors reported.
+ *
+ * Side Effects:
+ * None.
+ *-----------------------------------------------------------------------
+ */
+int
+Job_Finish(void)
+{
+ if (postCommands != NULL &&
+ (!Lst_IsEmpty(postCommands->commands) ||
+ !Lst_IsEmpty(postCommands->children))) {
+ if (errors) {
+ Error("Errors reported so .END ignored");
+ } else {
+ JobRun(postCommands);
+ }
+ }
+ return(errors);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_End --
+ * Cleanup any memory used by the jobs module
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Memory is freed
+ *-----------------------------------------------------------------------
+ */
+void
+Job_End(void)
+{
+#ifdef CLEANUP
+ free(shellArgv);
+#endif
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Wait --
+ * Waits for all running jobs to finish and returns. Sets 'aborting'
+ * to ABORT_WAIT to prevent other jobs from starting.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Currently running jobs finish.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Wait(void)
+{
+ aborting = ABORT_WAIT;
+ while (jobTokensRunning != 0) {
+ Job_CatchOutput();
+ }
+ aborting = 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_AbortAll --
+ * Abort all currently running jobs without handling output or anything.
+ * This function is to be called only in the event of a major
+ * error. Most definitely NOT to be called from JobInterrupt.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * All children are killed, not just the firstborn
+ *-----------------------------------------------------------------------
+ */
+void
+Job_AbortAll(void)
+{
+ Job *job; /* the job descriptor in that element */
+ int foo;
+
+ aborting = ABORT_ERROR;
+
+ if (jobTokensRunning) {
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->job_state != JOB_ST_RUNNING)
+ continue;
+ /*
+ * kill the child process with increasingly drastic signals to make
+ * darn sure it's dead.
+ */
+ KILLPG(job->pid, SIGINT);
+ KILLPG(job->pid, SIGKILL);
+ }
+ }
+
+ /*
+ * Catch as many children as want to report in at first, then give up
+ */
+ while (waitpid((pid_t) -1, &foo, WNOHANG) > 0)
+ continue;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobRestartJobs --
+ * Tries to restart stopped jobs if there are slots available.
+ * Called in process context in response to a SIGCONT.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Resumes jobs.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobRestartJobs(void)
+{
+ Job *job;
+
+ for (job = job_table; job < job_table_end; job++) {
+ if (job->job_state == JOB_ST_RUNNING &&
+ (make_suspended || job->job_suspended)) {
+ if (DEBUG(JOB)) {
+ (void)fprintf(debug_file, "Restarting stopped job pid %d.\n",
+ job->pid);
+ }
+ if (job->job_suspended) {
+ (void)printf("*** [%s] Continued\n", job->node->name);
+ (void)fflush(stdout);
+ }
+ job->job_suspended = 0;
+ if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) {
+ fprintf(debug_file, "Failed to send SIGCONT to %d\n", job->pid);
+ }
+ }
+ if (job->job_state == JOB_ST_FINISHED)
+ /* Job exit deferred after calling waitpid() in a signal handler */
+ JobFinish(job, job->exit_status);
+ }
+ make_suspended = 0;
+}
+
+static void
+watchfd(Job *job)
+{
+ if (job->inPollfd != NULL)
+ Punt("Watching watched job");
+
+ fds[nfds].fd = job->inPipe;
+ fds[nfds].events = POLLIN;
+ jobfds[nfds] = job;
+ job->inPollfd = &fds[nfds];
+ nfds++;
+}
+
+static void
+clearfd(Job *job)
+{
+ int i;
+ if (job->inPollfd == NULL)
+ Punt("Unwatching unwatched job");
+ i = job->inPollfd - fds;
+ nfds--;
+ /*
+ * Move last job in table into hole made by dead job.
+ */
+ if (nfds != i) {
+ fds[i] = fds[nfds];
+ jobfds[i] = jobfds[nfds];
+ jobfds[i]->inPollfd = &fds[i];
+ }
+ job->inPollfd = NULL;
+}
+
+static int
+readyfd(Job *job)
+{
+ if (job->inPollfd == NULL)
+ Punt("Polling unwatched job");
+ return (job->inPollfd->revents & POLLIN) != 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobTokenAdd --
+ * Put a token into the job pipe so that some make process can start
+ * another job.
+ *
+ * Side Effects:
+ * Allows more build jobs to be spawned somewhere.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+static void
+JobTokenAdd(void)
+{
+ char tok = JOB_TOKENS[aborting], tok1;
+
+ /* If we are depositing an error token flush everything else */
+ while (tok != '+' && read(tokenWaitJob.inPipe, &tok1, 1) == 1)
+ continue;
+
+ if (DEBUG(JOB))
+ fprintf(debug_file, "(%d) aborting %d, deposit token %c\n",
+ getpid(), aborting, JOB_TOKENS[aborting]);
+ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN)
+ continue;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_ServerStartTokenAdd --
+ * Prep the job token pipe in the root make process.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+void
+Job_ServerStart(int max_tokens, int jp_0, int jp_1)
+{
+ int i;
+ char jobarg[64];
+
+ if (jp_0 >= 0 && jp_1 >= 0) {
+ /* Pipe passed in from parent */
+ tokenWaitJob.inPipe = jp_0;
+ tokenWaitJob.outPipe = jp_1;
+ (void)fcntl(jp_0, F_SETFD, FD_CLOEXEC);
+ (void)fcntl(jp_1, F_SETFD, FD_CLOEXEC);
+ return;
+ }
+
+ JobCreatePipe(&tokenWaitJob, 15);
+
+ snprintf(jobarg, sizeof(jobarg), "%d,%d",
+ tokenWaitJob.inPipe, tokenWaitJob.outPipe);
+
+ Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, jobarg, VAR_GLOBAL);
+
+ /*
+ * Preload the job pipe with one token per job, save the one
+ * "extra" token for the primary job.
+ *
+ * XXX should clip maxJobs against PIPE_BUF -- if max_tokens is
+ * larger than the write buffer size of the pipe, we will
+ * deadlock here.
+ */
+ for (i = 1; i < max_tokens; i++)
+ JobTokenAdd();
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_TokenReturn --
+ * Return a withdrawn token to the pool.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+void
+Job_TokenReturn(void)
+{
+ jobTokensRunning--;
+ if (jobTokensRunning < 0)
+ Punt("token botch");
+ if (jobTokensRunning || JOB_TOKENS[aborting] != '+')
+ JobTokenAdd();
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_TokenWithdraw --
+ * Attempt to withdraw a token from the pool.
+ *
+ * Results:
+ * Returns TRUE if a token was withdrawn, and FALSE if the pool
+ * is currently empty.
+ *
+ * Side Effects:
+ * If pool is empty, set wantToken so that we wake up
+ * when a token is released.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+
+Boolean
+Job_TokenWithdraw(void)
+{
+ char tok, tok1;
+ int count;
+
+ wantToken = 0;
+ if (DEBUG(JOB))
+ fprintf(debug_file, "Job_TokenWithdraw(%d): aborting %d, running %d\n",
+ getpid(), aborting, jobTokensRunning);
+
+ if (aborting || (jobTokensRunning >= maxJobs))
+ return FALSE;
+
+ count = read(tokenWaitJob.inPipe, &tok, 1);
+ if (count == 0)
+ Fatal("eof on job pipe!");
+ if (count < 0 && jobTokensRunning != 0) {
+ if (errno != EAGAIN) {
+ Fatal("job pipe read: %s", strerror(errno));
+ }
+ if (DEBUG(JOB))
+ fprintf(debug_file, "(%d) blocked for token\n", getpid());
+ return FALSE;
+ }
+
+ if (count == 1 && tok != '+') {
+ /* make being abvorted - remove any other job tokens */
+ if (DEBUG(JOB))
+ fprintf(debug_file, "(%d) aborted by token %c\n", getpid(), tok);
+ while (read(tokenWaitJob.inPipe, &tok1, 1) == 1)
+ continue;
+ /* And put the stopper back */
+ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN)
+ continue;
+ Fatal("A failure has been detected in another branch of the parallel make");
+ }
+
+ if (count == 1 && jobTokensRunning == 0)
+ /* We didn't want the token really */
+ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN)
+ continue;
+
+ jobTokensRunning++;
+ if (DEBUG(JOB))
+ fprintf(debug_file, "(%d) withdrew token\n", getpid());
+ return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_RunTarget --
+ * Run the named target if found. If a filename is specified, then
+ * set that to the sources.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * exits if the target fails.
+ *
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Job_RunTarget(const char *target, const char *fname) {
+ GNode *gn = Targ_FindNode(target, TARG_NOCREATE);
+
+ if (gn == NULL)
+ return FALSE;
+
+ if (fname)
+ Var_Set(ALLSRC, fname, gn, 0);
+
+ JobRun(gn);
+ if (gn->made == ERROR) {
+ PrintOnError(gn, "\n\nStop.");
+ exit(1);
+ }
+ return TRUE;
+}
+
+#ifdef USE_SELECT
+int
+emul_poll(struct pollfd *fd, int nfd, int timeout)
+{
+ fd_set rfds, wfds;
+ int i, maxfd, nselect, npoll;
+ struct timeval tv, *tvp;
+ long usecs;
+
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+
+ maxfd = -1;
+ for (i = 0; i < nfd; i++) {
+ fd[i].revents = 0;
+
+ if (fd[i].events & POLLIN)
+ FD_SET(fd[i].fd, &rfds);
+
+ if (fd[i].events & POLLOUT)
+ FD_SET(fd[i].fd, &wfds);
+
+ if (fd[i].fd > maxfd)
+ maxfd = fd[i].fd;
+ }
+
+ if (maxfd >= FD_SETSIZE) {
+ Punt("Ran out of fd_set slots; "
+ "recompile with a larger FD_SETSIZE.");
+ }
+
+ if (timeout < 0) {
+ tvp = NULL;
+ } else {
+ usecs = timeout * 1000;
+ tv.tv_sec = usecs / 1000000;
+ tv.tv_usec = usecs % 1000000;
+ tvp = &tv;
+ }
+
+ nselect = select(maxfd + 1, &rfds, &wfds, 0, tvp);
+
+ if (nselect <= 0)
+ return nselect;
+
+ npoll = 0;
+ for (i = 0; i < nfd; i++) {
+ if (FD_ISSET(fd[i].fd, &rfds))
+ fd[i].revents |= POLLIN;
+
+ if (FD_ISSET(fd[i].fd, &wfds))
+ fd[i].revents |= POLLOUT;
+
+ if (fd[i].revents)
+ npoll++;
+ }
+
+ return npoll;
+}
+#endif /* USE_SELECT */
diff --git a/usr.bin/make/job.h b/usr.bin/make/job.h
new file mode 100644
index 0000000..91e2c87
--- /dev/null
+++ b/usr.bin/make/job.h
@@ -0,0 +1,274 @@
+/* $NetBSD: job.h,v 1.42 2013/07/05 22:14:56 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)job.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)job.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*-
+ * job.h --
+ * Definitions pertaining to the running of jobs in parallel mode.
+ */
+#ifndef _JOB_H_
+#define _JOB_H_
+
+#define TMPPAT "makeXXXXXX" /* relative to tmpdir */
+
+#ifdef USE_SELECT
+/*
+ * Emulate poll() in terms of select(). This is not a complete
+ * emulation but it is sufficient for make's purposes.
+ */
+
+#define poll emul_poll
+#define pollfd emul_pollfd
+
+struct emul_pollfd {
+ int fd;
+ short events;
+ short revents;
+};
+
+#define POLLIN 0x0001
+#define POLLOUT 0x0004
+
+int
+emul_poll(struct pollfd *fd, int nfd, int timeout);
+#endif
+
+/*
+ * The POLL_MSEC constant determines the maximum number of milliseconds spent
+ * in poll before coming out to see if a child has finished.
+ */
+#define POLL_MSEC 5000
+
+
+/*-
+ * Job Table definitions.
+ *
+ * Each job has several things associated with it:
+ * 1) The process id of the child shell
+ * 2) The graph node describing the target being made by this job
+ * 3) A LstNode for the first command to be saved after the job
+ * completes. This is NULL if there was no "..." in the job's
+ * commands.
+ * 4) An FILE* for writing out the commands. This is only
+ * used before the job is actually started.
+ * 5) The output is being caught via a pipe and
+ * the descriptors of our pipe, an array in which output is line
+ * buffered and the current position in that buffer are all
+ * maintained for each job.
+ * 6) A word of flags which determine how the module handles errors,
+ * echoing, etc. for the job
+ *
+ * When a job is finished, the Make_Update function is called on each of the
+ * parents of the node which was just remade. This takes care of the upward
+ * traversal of the dependency graph.
+ */
+struct pollfd;
+
+
+#ifdef USE_META
+# include "meta.h"
+#endif
+
+#define JOB_BUFSIZE 1024
+typedef struct Job {
+ int pid; /* The child's process ID */
+ GNode *node; /* The target the child is making */
+ LstNode tailCmds; /* The node of the first command to be
+ * saved when the job has been run */
+ FILE *cmdFILE; /* When creating the shell script, this is
+ * where the commands go */
+ int exit_status; /* from wait4() in signal handler */
+ char job_state; /* status of the job entry */
+#define JOB_ST_FREE 0 /* Job is available */
+#define JOB_ST_SETUP 1 /* Job is allocated but otherwise invalid */
+#define JOB_ST_RUNNING 3 /* Job is running, pid valid */
+#define JOB_ST_FINISHED 4 /* Job is done (ie after SIGCHILD) */
+ char job_suspended;
+ short flags; /* Flags to control treatment of job */
+#define JOB_IGNERR 0x001 /* Ignore non-zero exits */
+#define JOB_SILENT 0x002 /* no output */
+#define JOB_SPECIAL 0x004 /* Target is a special one. i.e. run it locally
+ * if we can't export it and maxLocal is 0 */
+#define JOB_IGNDOTS 0x008 /* Ignore "..." lines when processing
+ * commands */
+#define JOB_TRACED 0x400 /* we've sent 'set -x' */
+
+ int jobPipe[2]; /* Pipe for readind output from job */
+ struct pollfd *inPollfd; /* pollfd associated with inPipe */
+ char outBuf[JOB_BUFSIZE + 1];
+ /* Buffer for storing the output of the
+ * job, line by line */
+ int curPos; /* Current position in op_outBuf */
+
+#ifdef USE_META
+ struct BuildMon bm;
+#endif
+} Job;
+
+#define inPipe jobPipe[0]
+#define outPipe jobPipe[1]
+
+
+/*-
+ * Shell Specifications:
+ * Each shell type has associated with it the following information:
+ * 1) The string which must match the last character of the shell name
+ * for the shell to be considered of this type. The longest match
+ * wins.
+ * 2) A command to issue to turn off echoing of command lines
+ * 3) A command to issue to turn echoing back on again
+ * 4) What the shell prints, and its length, when given the echo-off
+ * command. This line will not be printed when received from the shell
+ * 5) A boolean to tell if the shell has the ability to control
+ * error checking for individual commands.
+ * 6) The string to turn this checking on.
+ * 7) The string to turn it off.
+ * 8) The command-flag to give to cause the shell to start echoing
+ * commands right away.
+ * 9) The command-flag to cause the shell to Lib_Exit when an error is
+ * detected in one of the commands.
+ *
+ * Some special stuff goes on if a shell doesn't have error control. In such
+ * a case, errCheck becomes a printf template for echoing the command,
+ * should echoing be on and ignErr becomes another printf template for
+ * executing the command while ignoring the return status. Finally errOut
+ * is a printf template for running the command and causing the shell to
+ * exit on error. If any of these strings are empty when hasErrCtl is FALSE,
+ * the command will be executed anyway as is and if it causes an error, so be
+ * it. Any templates setup to echo the command will escape any '$ ` \ "'i
+ * characters in the command string to avoid common problems with
+ * echo "%s\n" as a template.
+ */
+typedef struct Shell {
+ const char *name; /* the name of the shell. For Bourne and C
+ * shells, this is used only to find the
+ * shell description when used as the single
+ * source of a .SHELL target. For user-defined
+ * shells, this is the full path of the shell.
+ */
+ Boolean hasEchoCtl; /* True if both echoOff and echoOn defined */
+ const char *echoOff; /* command to turn off echo */
+ const char *echoOn; /* command to turn it back on again */
+ const char *noPrint; /* command to skip when printing output from
+ * shell. This is usually the command which
+ * was executed to turn off echoing */
+ int noPLen; /* length of noPrint command */
+ Boolean hasErrCtl; /* set if can control error checking for
+ * individual commands */
+ const char *errCheck; /* string to turn error checking on */
+ const char *ignErr; /* string to turn off error checking */
+ const char *errOut; /* string to use for testing exit code */
+ const char *newline; /* string literal that results in a newline
+ * character when it appears outside of any
+ * 'quote' or "quote" characters */
+ char commentChar; /* character used by shell for comment lines */
+
+ /*
+ * command-line flags
+ */
+ const char *echo; /* echo commands */
+ const char *exit; /* exit on error */
+} Shell;
+
+extern const char *shellPath;
+extern const char *shellName;
+extern char *shellErrFlag;
+
+extern int jobTokensRunning; /* tokens currently "out" */
+extern int maxJobs; /* Max jobs we can run */
+
+void Shell_Init(void);
+const char *Shell_GetNewline(void);
+void Job_Touch(GNode *, Boolean);
+Boolean Job_CheckCommands(GNode *, void (*abortProc )(const char *, ...));
+#define CATCH_BLOCK 1
+void Job_CatchChildren(void);
+void Job_CatchOutput(void);
+void Job_Make(GNode *);
+void Job_Init(void);
+Boolean Job_Full(void);
+Boolean Job_Empty(void);
+ReturnStatus Job_ParseShell(char *);
+int Job_Finish(void);
+void Job_End(void);
+void Job_Wait(void);
+void Job_AbortAll(void);
+void JobFlagForMigration(int);
+void Job_TokenReturn(void);
+Boolean Job_TokenWithdraw(void);
+void Job_ServerStart(int, int, int);
+void Job_SetPrefix(void);
+Boolean Job_RunTarget(const char *, const char *);
+
+#endif /* _JOB_H_ */
diff --git a/usr.bin/make/lst.h b/usr.bin/make/lst.h
new file mode 100644
index 0000000..e207bc8
--- /dev/null
+++ b/usr.bin/make/lst.h
@@ -0,0 +1,189 @@
+/* $NetBSD: lst.h,v 1.20 2014/09/07 20:55:34 joerg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)lst.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)lst.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*-
+ * lst.h --
+ * Header for using the list library
+ */
+#ifndef _LST_H_
+#define _LST_H_
+
+#include <sys/param.h>
+#include <stdlib.h>
+
+#include "sprite.h"
+
+/*
+ * basic typedef. This is what the Lst_ functions handle
+ */
+
+typedef struct List *Lst;
+typedef struct ListNode *LstNode;
+
+typedef void *DuplicateProc(void *);
+typedef void FreeProc(void *);
+
+#define LST_CONCNEW 0 /* create new LstNode's when using Lst_Concat */
+#define LST_CONCLINK 1 /* relink LstNode's when using Lst_Concat */
+
+/*
+ * Creation/destruction functions
+ */
+/* Create a new list */
+Lst Lst_Init(Boolean);
+/* Duplicate an existing list */
+Lst Lst_Duplicate(Lst, DuplicateProc *);
+/* Destroy an old one */
+void Lst_Destroy(Lst, FreeProc *);
+/* True if list is empty */
+Boolean Lst_IsEmpty(Lst);
+
+/*
+ * Functions to modify a list
+ */
+/* Insert an element before another */
+ReturnStatus Lst_InsertBefore(Lst, LstNode, void *);
+/* Insert an element after another */
+ReturnStatus Lst_InsertAfter(Lst, LstNode, void *);
+/* Place an element at the front of a lst. */
+ReturnStatus Lst_AtFront(Lst, void *);
+/* Place an element at the end of a lst. */
+ReturnStatus Lst_AtEnd(Lst, void *);
+/* Remove an element */
+ReturnStatus Lst_Remove(Lst, LstNode);
+/* Replace a node with a new value */
+ReturnStatus Lst_Replace(LstNode, void *);
+/* Concatenate two lists */
+ReturnStatus Lst_Concat(Lst, Lst, int);
+
+/*
+ * Node-specific functions
+ */
+/* Return first element in list */
+LstNode Lst_First(Lst);
+/* Return last element in list */
+LstNode Lst_Last(Lst);
+/* Return successor to given element */
+LstNode Lst_Succ(LstNode);
+/* Return predecessor to given element */
+LstNode Lst_Prev(LstNode);
+/* Get datum from LstNode */
+void *Lst_Datum(LstNode);
+
+/*
+ * Functions for entire lists
+ */
+/* Find an element in a list */
+LstNode Lst_Find(Lst, const void *, int (*)(const void *, const void *));
+/* Find an element starting from somewhere */
+LstNode Lst_FindFrom(Lst, LstNode, const void *,
+ int (*cProc)(const void *, const void *));
+/*
+ * See if the given datum is on the list. Returns the LstNode containing
+ * the datum
+ */
+LstNode Lst_Member(Lst, void *);
+/* Apply a function to all elements of a lst */
+int Lst_ForEach(Lst, int (*)(void *, void *), void *);
+/*
+ * Apply a function to all elements of a lst starting from a certain point.
+ * If the list is circular, the application will wrap around to the
+ * beginning of the list again.
+ */
+int Lst_ForEachFrom(Lst, LstNode, int (*)(void *, void *),
+ void *);
+/*
+ * these functions are for dealing with a list as a table, of sorts.
+ * An idea of the "current element" is kept and used by all the functions
+ * between Lst_Open() and Lst_Close().
+ */
+/* Open the list */
+ReturnStatus Lst_Open(Lst);
+/* Next element please */
+LstNode Lst_Next(Lst);
+/* Done yet? */
+Boolean Lst_IsAtEnd(Lst);
+/* Finish table access */
+void Lst_Close(Lst);
+
+/*
+ * for using the list as a queue
+ */
+/* Place an element at tail of queue */
+ReturnStatus Lst_EnQueue(Lst, void *);
+/* Remove an element from head of queue */
+void *Lst_DeQueue(Lst);
+
+#endif /* _LST_H_ */
diff --git a/usr.bin/make/lst.lib/Makefile b/usr.bin/make/lst.lib/Makefile
new file mode 100644
index 0000000..5b33a50
--- /dev/null
+++ b/usr.bin/make/lst.lib/Makefile
@@ -0,0 +1,10 @@
+# $NetBSD: Makefile,v 1.6 2006/11/11 21:23:36 dsl Exp $
+
+OBJ=lstAppend.o lstDupl.o lstInit.o lstOpen.o lstAtEnd.o lstEnQueue.o \
+ lstInsert.o lstAtFront.o lstIsAtEnd.o lstClose.o lstFind.o lstIsEmpty.o \
+ lstRemove.o lstConcat.o lstFindFrom.o lstLast.o lstReplace.o lstFirst.o \
+ lstDatum.o lstForEach.o lstMember.o lstSucc.o lstDeQueue.o \
+ lstForEachFrom.o lstDestroy.o lstNext.o lstPrev.o
+
+CPPFLAGS=-I${.CURDIR}/..
+all: ${OBJ}
diff --git a/usr.bin/make/lst.lib/lstAppend.c b/usr.bin/make/lst.lib/lstAppend.c
new file mode 100644
index 0000000..4dafe83
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstAppend.c
@@ -0,0 +1,122 @@
+/* $NetBSD: lstAppend.c,v 1.14 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstAppend.c,v 1.14 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstAppend.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstAppend.c,v 1.14 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstAppend.c --
+ * Add a new node with a new datum after an existing node
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_InsertAfter --
+ * Create a new node and add it to the given list after the given node.
+ *
+ * Input:
+ * l affected list
+ * ln node after which to append the datum
+ * d said datum
+ *
+ * Results:
+ * SUCCESS if all went well.
+ *
+ * Side Effects:
+ * A new ListNode is created and linked in to the List. The lastPtr
+ * field of the List will be altered if ln is the last node in the
+ * list. lastPtr and firstPtr will alter if the list was empty and
+ * ln was NULL.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_InsertAfter(Lst l, LstNode ln, void *d)
+{
+ List list;
+ ListNode lNode;
+ ListNode nLNode;
+
+ if (LstValid (l) && (ln == NULL && LstIsEmpty (l))) {
+ goto ok;
+ }
+
+ if (!LstValid (l) || LstIsEmpty (l) || ! LstNodeValid (ln, l)) {
+ return (FAILURE);
+ }
+ ok:
+
+ list = l;
+ lNode = ln;
+
+ PAlloc (nLNode, ListNode);
+ nLNode->datum = d;
+ nLNode->useCount = nLNode->flags = 0;
+
+ if (lNode == NULL) {
+ if (list->isCirc) {
+ nLNode->nextPtr = nLNode->prevPtr = nLNode;
+ } else {
+ nLNode->nextPtr = nLNode->prevPtr = NULL;
+ }
+ list->firstPtr = list->lastPtr = nLNode;
+ } else {
+ nLNode->prevPtr = lNode;
+ nLNode->nextPtr = lNode->nextPtr;
+
+ lNode->nextPtr = nLNode;
+ if (nLNode->nextPtr != NULL) {
+ nLNode->nextPtr->prevPtr = nLNode;
+ }
+
+ if (lNode == list->lastPtr) {
+ list->lastPtr = nLNode;
+ }
+ }
+
+ return (SUCCESS);
+}
+
diff --git a/usr.bin/make/lst.lib/lstAtEnd.c b/usr.bin/make/lst.lib/lstAtEnd.c
new file mode 100644
index 0000000..10f191a
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstAtEnd.c
@@ -0,0 +1,79 @@
+/* $NetBSD: lstAtEnd.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstAtEnd.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstAtEnd.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstAtEnd.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstAtEnd.c --
+ * Add a node at the end of the list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_AtEnd --
+ * Add a node to the end of the given list
+ *
+ * Input:
+ * l List to which to add the datum
+ * d Datum to add
+ *
+ * Results:
+ * SUCCESS if life is good.
+ *
+ * Side Effects:
+ * A new ListNode is created and added to the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_AtEnd(Lst l, void *d)
+{
+ LstNode end;
+
+ end = Lst_Last(l);
+ return (Lst_InsertAfter(l, end, d));
+}
diff --git a/usr.bin/make/lst.lib/lstAtFront.c b/usr.bin/make/lst.lib/lstAtFront.c
new file mode 100644
index 0000000..d8be166
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstAtFront.c
@@ -0,0 +1,76 @@
+/* $NetBSD: lstAtFront.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstAtFront.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstAtFront.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstAtFront.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstAtFront.c --
+ * Add a node at the front of the list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_AtFront --
+ * Place a piece of data at the front of a list
+ *
+ * Results:
+ * SUCCESS or FAILURE
+ *
+ * Side Effects:
+ * A new ListNode is created and stuck at the front of the list.
+ * hence, firstPtr (and possible lastPtr) in the list are altered.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_AtFront(Lst l, void *d)
+{
+ LstNode front;
+
+ front = Lst_First(l);
+ return (Lst_InsertBefore(l, front, d));
+}
diff --git a/usr.bin/make/lst.lib/lstClose.c b/usr.bin/make/lst.lib/lstClose.c
new file mode 100644
index 0000000..06b68c5
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstClose.c
@@ -0,0 +1,86 @@
+/* $NetBSD: lstClose.c,v 1.11 2006/10/27 21:37:25 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstClose.c,v 1.11 2006/10/27 21:37:25 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstClose.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstClose.c,v 1.11 2006/10/27 21:37:25 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstClose.c --
+ * Close a list for sequential access.
+ * The sequential functions access the list in a slightly different way.
+ * CurPtr points to their idea of the current node in the list and they
+ * access the list based on it. Because the list is circular, Lst_Next
+ * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be
+ * used to determine when to stop.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Close --
+ * Close a list which was opened for sequential access.
+ *
+ * Input:
+ * l The list to close
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The list is closed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Lst_Close(Lst l)
+{
+ List list = l;
+
+ if (LstValid(l) == TRUE) {
+ list->isOpen = FALSE;
+ list->atEnd = Unknown;
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstConcat.c b/usr.bin/make/lst.lib/lstConcat.c
new file mode 100644
index 0000000..534d34e
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstConcat.c
@@ -0,0 +1,185 @@
+/* $NetBSD: lstConcat.c,v 1.16 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstConcat.c,v 1.16 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstConcat.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstConcat.c,v 1.16 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * listConcat.c --
+ * Function to concatentate two lists.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Concat --
+ * Concatenate two lists. New elements are created to hold the data
+ * elements, if specified, but the elements themselves are not copied.
+ * If the elements should be duplicated to avoid confusion with another
+ * list, the Lst_Duplicate function should be called first.
+ * If LST_CONCLINK is specified, the second list is destroyed since
+ * its pointers have been corrupted and the list is no longer useable.
+ *
+ * Input:
+ * l1 The list to which l2 is to be appended
+ * l2 The list to append to l1
+ * flags LST_CONCNEW if LstNode's should be duplicated
+ * LST_CONCLINK if should just be relinked
+ *
+ * Results:
+ * SUCCESS if all went well. FAILURE otherwise.
+ *
+ * Side Effects:
+ * New elements are created and appended the first list.
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Concat(Lst l1, Lst l2, int flags)
+{
+ ListNode ln; /* original LstNode */
+ ListNode nln; /* new LstNode */
+ ListNode last; /* the last element in the list. Keeps
+ * bookkeeping until the end */
+ List list1 = l1;
+ List list2 = l2;
+
+ if (!LstValid (l1) || !LstValid (l2)) {
+ return (FAILURE);
+ }
+
+ if (flags == LST_CONCLINK) {
+ if (list2->firstPtr != NULL) {
+ /*
+ * We set the nextPtr of the
+ * last element of list two to be NIL to make the loop easier and
+ * so we don't need an extra case should the first list turn
+ * out to be non-circular -- the final element will already point
+ * to NIL space and the first element will be untouched if it
+ * existed before and will also point to NIL space if it didn't.
+ */
+ list2->lastPtr->nextPtr = NULL;
+ /*
+ * So long as the second list isn't empty, we just link the
+ * first element of the second list to the last element of the
+ * first list. If the first list isn't empty, we then link the
+ * last element of the list to the first element of the second list
+ * The last element of the second list, if it exists, then becomes
+ * the last element of the first list.
+ */
+ list2->firstPtr->prevPtr = list1->lastPtr;
+ if (list1->lastPtr != NULL) {
+ list1->lastPtr->nextPtr = list2->firstPtr;
+ } else {
+ list1->firstPtr = list2->firstPtr;
+ }
+ list1->lastPtr = list2->lastPtr;
+ }
+ if (list1->isCirc && list1->firstPtr != NULL) {
+ /*
+ * If the first list is supposed to be circular and it is (now)
+ * non-empty, we must make sure it's circular by linking the
+ * first element to the last and vice versa
+ */
+ list1->firstPtr->prevPtr = list1->lastPtr;
+ list1->lastPtr->nextPtr = list1->firstPtr;
+ }
+ free(l2);
+ } else if (list2->firstPtr != NULL) {
+ /*
+ * We set the nextPtr of the last element of list 2 to be nil to make
+ * the loop less difficult. The loop simply goes through the entire
+ * second list creating new LstNodes and filling in the nextPtr, and
+ * prevPtr to fit into l1 and its datum field from the
+ * datum field of the corresponding element in l2. The 'last' node
+ * follows the last of the new nodes along until the entire l2 has
+ * been appended. Only then does the bookkeeping catch up with the
+ * changes. During the first iteration of the loop, if 'last' is nil,
+ * the first list must have been empty so the newly-created node is
+ * made the first node of the list.
+ */
+ list2->lastPtr->nextPtr = NULL;
+ for (last = list1->lastPtr, ln = list2->firstPtr;
+ ln != NULL;
+ ln = ln->nextPtr)
+ {
+ PAlloc (nln, ListNode);
+ nln->datum = ln->datum;
+ if (last != NULL) {
+ last->nextPtr = nln;
+ } else {
+ list1->firstPtr = nln;
+ }
+ nln->prevPtr = last;
+ nln->flags = nln->useCount = 0;
+ last = nln;
+ }
+
+ /*
+ * Finish bookkeeping. The last new element becomes the last element
+ * of list one.
+ */
+ list1->lastPtr = last;
+
+ /*
+ * The circularity of both list one and list two must be corrected
+ * for -- list one because of the new nodes added to it; list two
+ * because of the alteration of list2->lastPtr's nextPtr to ease the
+ * above for loop.
+ */
+ if (list1->isCirc) {
+ list1->lastPtr->nextPtr = list1->firstPtr;
+ list1->firstPtr->prevPtr = list1->lastPtr;
+ } else {
+ last->nextPtr = NULL;
+ }
+
+ if (list2->isCirc) {
+ list2->lastPtr->nextPtr = list2->firstPtr;
+ }
+ }
+
+ return (SUCCESS);
+}
+
diff --git a/usr.bin/make/lst.lib/lstDatum.c b/usr.bin/make/lst.lib/lstDatum.c
new file mode 100644
index 0000000..6e2d9ad
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstDatum.c
@@ -0,0 +1,77 @@
+/* $NetBSD: lstDatum.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstDatum.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstDatum.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstDatum.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstDatum.c --
+ * Return the datum associated with a list node.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Datum --
+ * Return the datum stored in the given node.
+ *
+ * Results:
+ * The datum or NULL if the node is invalid.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+void *
+Lst_Datum(LstNode ln)
+{
+ if (ln != NULL) {
+ return ((ln)->datum);
+ } else {
+ return NULL;
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstDeQueue.c b/usr.bin/make/lst.lib/lstDeQueue.c
new file mode 100644
index 0000000..bdb05cc
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstDeQueue.c
@@ -0,0 +1,87 @@
+/* $NetBSD: lstDeQueue.c,v 1.14 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstDeQueue.c,v 1.14 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstDeQueue.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstDeQueue.c,v 1.14 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstDeQueue.c --
+ * Remove the node and return its datum from the head of the list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_DeQueue --
+ * Remove and return the datum at the head of the given list.
+ *
+ * Results:
+ * The datum in the node at the head or NULL if the list
+ * is empty.
+ *
+ * Side Effects:
+ * The head node is removed from the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+void *
+Lst_DeQueue(Lst l)
+{
+ void *rd;
+ ListNode tln;
+
+ tln = Lst_First(l);
+ if (tln == NULL) {
+ return NULL;
+ }
+
+ rd = tln->datum;
+ if (Lst_Remove(l, tln) == FAILURE) {
+ return NULL;
+ } else {
+ return (rd);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstDestroy.c b/usr.bin/make/lst.lib/lstDestroy.c
new file mode 100644
index 0000000..92c5b2b
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstDestroy.c
@@ -0,0 +1,101 @@
+/* $NetBSD: lstDestroy.c,v 1.16 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstDestroy.c,v 1.16 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstDestroy.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstDestroy.c,v 1.16 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstDestroy.c --
+ * Nuke a list and all its resources
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Destroy --
+ * Destroy a list and free all its resources. If the freeProc is
+ * given, it is called with the datum from each node in turn before
+ * the node is freed.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The given list is freed in its entirety.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Lst_Destroy(Lst list, FreeProc *freeProc)
+{
+ ListNode ln;
+ ListNode tln = NULL;
+
+ if (list == NULL)
+ return;
+
+ /* To ease scanning */
+ if (list->lastPtr != NULL)
+ list->lastPtr->nextPtr = NULL;
+ else {
+ free(list);
+ return;
+ }
+
+ if (freeProc) {
+ for (ln = list->firstPtr; ln != NULL; ln = tln) {
+ tln = ln->nextPtr;
+ freeProc(ln->datum);
+ free(ln);
+ }
+ } else {
+ for (ln = list->firstPtr; ln != NULL; ln = tln) {
+ tln = ln->nextPtr;
+ free(ln);
+ }
+ }
+
+ free(list);
+}
diff --git a/usr.bin/make/lst.lib/lstDupl.c b/usr.bin/make/lst.lib/lstDupl.c
new file mode 100644
index 0000000..2174ff7
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstDupl.c
@@ -0,0 +1,107 @@
+/* $NetBSD: lstDupl.c,v 1.16 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstDupl.c,v 1.16 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstDupl.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstDupl.c,v 1.16 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * listDupl.c --
+ * Duplicate a list. This includes duplicating the individual
+ * elements.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Duplicate --
+ * Duplicate an entire list. If a function to copy a void *is
+ * given, the individual client elements will be duplicated as well.
+ *
+ * Input:
+ * l the list to duplicate
+ * copyProc A function to duplicate each void *
+ *
+ * Results:
+ * The new Lst structure or NULL if failure.
+ *
+ * Side Effects:
+ * A new list is created.
+ *-----------------------------------------------------------------------
+ */
+Lst
+Lst_Duplicate(Lst l, DuplicateProc *copyProc)
+{
+ Lst nl;
+ ListNode ln;
+ List list = l;
+
+ if (!LstValid (l)) {
+ return NULL;
+ }
+
+ nl = Lst_Init(list->isCirc);
+ if (nl == NULL) {
+ return NULL;
+ }
+
+ ln = list->firstPtr;
+ while (ln != NULL) {
+ if (copyProc != NULL) {
+ if (Lst_AtEnd(nl, copyProc(ln->datum)) == FAILURE) {
+ return NULL;
+ }
+ } else if (Lst_AtEnd(nl, ln->datum) == FAILURE) {
+ return NULL;
+ }
+
+ if (list->isCirc && ln == list->lastPtr) {
+ ln = NULL;
+ } else {
+ ln = ln->nextPtr;
+ }
+ }
+
+ return (nl);
+}
diff --git a/usr.bin/make/lst.lib/lstEnQueue.c b/usr.bin/make/lst.lib/lstEnQueue.c
new file mode 100644
index 0000000..be386c9
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstEnQueue.c
@@ -0,0 +1,78 @@
+/* $NetBSD: lstEnQueue.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstEnQueue.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstEnQueue.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstEnQueue.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstEnQueue.c--
+ * Treat the list as a queue and place a datum at its end
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_EnQueue --
+ * Add the datum to the tail of the given list.
+ *
+ * Results:
+ * SUCCESS or FAILURE as returned by Lst_InsertAfter.
+ *
+ * Side Effects:
+ * the lastPtr field is altered all the time and the firstPtr field
+ * will be altered if the list used to be empty.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_EnQueue(Lst l, void *d)
+{
+ if (LstValid (l) == FALSE) {
+ return (FAILURE);
+ }
+
+ return (Lst_InsertAfter(l, Lst_Last(l), d));
+}
+
diff --git a/usr.bin/make/lst.lib/lstFind.c b/usr.bin/make/lst.lib/lstFind.c
new file mode 100644
index 0000000..d07dbe7
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstFind.c
@@ -0,0 +1,74 @@
+/* $NetBSD: lstFind.c,v 1.15 2009/01/23 21:58:28 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstFind.c,v 1.15 2009/01/23 21:58:28 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstFind.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstFind.c,v 1.15 2009/01/23 21:58:28 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstFind.c --
+ * Find a node on a list.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Find --
+ * Find a node on the given list using the given comparison function
+ * and the given datum.
+ *
+ * Results:
+ * The found node or NULL if none matches.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Find(Lst l, const void *d, int (*cProc)(const void *, const void *))
+{
+ return (Lst_FindFrom(l, Lst_First(l), d, cProc));
+}
+
diff --git a/usr.bin/make/lst.lib/lstFindFrom.c b/usr.bin/make/lst.lib/lstFindFrom.c
new file mode 100644
index 0000000..e2beab6
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstFindFrom.c
@@ -0,0 +1,90 @@
+/* $NetBSD: lstFindFrom.c,v 1.15 2009/01/23 21:58:28 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstFindFrom.c,v 1.15 2009/01/23 21:58:28 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstFindFrom.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstFindFrom.c,v 1.15 2009/01/23 21:58:28 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstFindFrom.c --
+ * Find a node on a list from a given starting point. Used by Lst_Find.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_FindFrom --
+ * Search for a node starting and ending with the given one on the
+ * given list using the passed datum and comparison function to
+ * determine when it has been found.
+ *
+ * Results:
+ * The found node or NULL
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_FindFrom(Lst l, LstNode ln, const void *d,
+ int (*cProc)(const void *, const void *))
+{
+ ListNode tln;
+
+ if (!LstValid (l) || LstIsEmpty (l) || !LstNodeValid (ln, l)) {
+ return NULL;
+ }
+
+ tln = ln;
+
+ do {
+ if ((*cProc)(tln->datum, d) == 0)
+ return (tln);
+ tln = tln->nextPtr;
+ } while (tln != ln && tln != NULL);
+
+ return NULL;
+}
+
diff --git a/usr.bin/make/lst.lib/lstFirst.c b/usr.bin/make/lst.lib/lstFirst.c
new file mode 100644
index 0000000..4e8334f
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstFirst.c
@@ -0,0 +1,77 @@
+/* $NetBSD: lstFirst.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstFirst.c,v 1.12 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstFirst.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstFirst.c,v 1.12 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstFirst.c --
+ * Return the first node of a list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_First --
+ * Return the first node on the given list.
+ *
+ * Results:
+ * The first node or NULL if the list is empty.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_First(Lst l)
+{
+ if (!LstValid (l) || LstIsEmpty (l)) {
+ return NULL;
+ } else {
+ return (l->firstPtr);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstForEach.c b/usr.bin/make/lst.lib/lstForEach.c
new file mode 100644
index 0000000..917e4ea
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstForEach.c
@@ -0,0 +1,76 @@
+/* $NetBSD: lstForEach.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstForEach.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstForEach.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstForEach.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstForeach.c --
+ * Perform a given function on all elements of a list.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_ForEach --
+ * Apply the given function to each element of the given list. The
+ * function should return 0 if Lst_ForEach should continue and non-
+ * zero if it should abort.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Only those created by the passed-in function.
+ *
+ *-----------------------------------------------------------------------
+ */
+/*VARARGS2*/
+int
+Lst_ForEach(Lst l, int (*proc)(void *, void *), void *d)
+{
+ return Lst_ForEachFrom(l, Lst_First(l), proc, d);
+}
+
diff --git a/usr.bin/make/lst.lib/lstForEachFrom.c b/usr.bin/make/lst.lib/lstForEachFrom.c
new file mode 100644
index 0000000..c7f44ad
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstForEachFrom.c
@@ -0,0 +1,125 @@
+/* $NetBSD: lstForEachFrom.c,v 1.17 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstForEachFrom.c,v 1.17 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstForEachFrom.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstForEachFrom.c,v 1.17 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * lstForEachFrom.c --
+ * Perform a given function on all elements of a list starting from
+ * a given point.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_ForEachFrom --
+ * Apply the given function to each element of the given list. The
+ * function should return 0 if traversal should continue and non-
+ * zero if it should abort.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Only those created by the passed-in function.
+ *
+ *-----------------------------------------------------------------------
+ */
+/*VARARGS2*/
+int
+Lst_ForEachFrom(Lst l, LstNode ln, int (*proc)(void *, void *),
+ void *d)
+{
+ ListNode tln = ln;
+ List list = l;
+ ListNode next;
+ Boolean done;
+ int result;
+
+ if (!LstValid (list) || LstIsEmpty (list)) {
+ return 0;
+ }
+
+ do {
+ /*
+ * Take care of having the current element deleted out from under
+ * us.
+ */
+
+ next = tln->nextPtr;
+
+ /*
+ * We're done with the traversal if
+ * - the next node to examine is the first in the queue or
+ * doesn't exist and
+ * - nothing's been added after the current node (check this
+ * after proc() has been called).
+ */
+ done = (next == NULL || next == list->firstPtr);
+
+ (void) tln->useCount++;
+ result = (*proc) (tln->datum, d);
+ (void) tln->useCount--;
+
+ /*
+ * Now check whether a node has been added.
+ * Note: this doesn't work if this node was deleted before
+ * the new node was added.
+ */
+ if (next != tln->nextPtr) {
+ next = tln->nextPtr;
+ done = 0;
+ }
+
+ if (tln->flags & LN_DELETED) {
+ free((char *)tln);
+ }
+ tln = next;
+ } while (!result && !LstIsEmpty(list) && !done);
+
+ return result;
+}
+
diff --git a/usr.bin/make/lst.lib/lstInit.c b/usr.bin/make/lst.lib/lstInit.c
new file mode 100644
index 0000000..f98ac42
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstInit.c
@@ -0,0 +1,85 @@
+/* $NetBSD: lstInit.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstInit.c,v 1.12 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstInit.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstInit.c,v 1.12 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * init.c --
+ * Initialize a new linked list.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Init --
+ * Create and initialize a new list.
+ *
+ * Input:
+ * circ TRUE if the list should be made circular
+ *
+ * Results:
+ * The created list.
+ *
+ * Side Effects:
+ * A list is created, what else?
+ *
+ *-----------------------------------------------------------------------
+ */
+Lst
+Lst_Init(Boolean circ)
+{
+ List nList;
+
+ PAlloc (nList, List);
+
+ nList->firstPtr = NULL;
+ nList->lastPtr = NULL;
+ nList->isOpen = FALSE;
+ nList->isCirc = circ;
+ nList->atEnd = Unknown;
+
+ return (nList);
+}
diff --git a/usr.bin/make/lst.lib/lstInsert.c b/usr.bin/make/lst.lib/lstInsert.c
new file mode 100644
index 0000000..77187bb
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstInsert.c
@@ -0,0 +1,122 @@
+/* $NetBSD: lstInsert.c,v 1.14 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstInsert.c,v 1.14 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstInsert.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstInsert.c,v 1.14 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstInsert.c --
+ * Insert a new datum before an old one
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_InsertBefore --
+ * Insert a new node with the given piece of data before the given
+ * node in the given list.
+ *
+ * Input:
+ * l list to manipulate
+ * ln node before which to insert d
+ * d datum to be inserted
+ *
+ * Results:
+ * SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ * the firstPtr field will be changed if ln is the first node in the
+ * list.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_InsertBefore(Lst l, LstNode ln, void *d)
+{
+ ListNode nLNode; /* new lnode for d */
+ ListNode lNode = ln;
+ List list = l;
+
+
+ /*
+ * check validity of arguments
+ */
+ if (LstValid (l) && (LstIsEmpty (l) && ln == NULL))
+ goto ok;
+
+ if (!LstValid (l) || LstIsEmpty (l) || !LstNodeValid (ln, l)) {
+ return (FAILURE);
+ }
+
+ ok:
+ PAlloc (nLNode, ListNode);
+
+ nLNode->datum = d;
+ nLNode->useCount = nLNode->flags = 0;
+
+ if (ln == NULL) {
+ if (list->isCirc) {
+ nLNode->prevPtr = nLNode->nextPtr = nLNode;
+ } else {
+ nLNode->prevPtr = nLNode->nextPtr = NULL;
+ }
+ list->firstPtr = list->lastPtr = nLNode;
+ } else {
+ nLNode->prevPtr = lNode->prevPtr;
+ nLNode->nextPtr = lNode;
+
+ if (nLNode->prevPtr != NULL) {
+ nLNode->prevPtr->nextPtr = nLNode;
+ }
+ lNode->prevPtr = nLNode;
+
+ if (lNode == list->firstPtr) {
+ list->firstPtr = nLNode;
+ }
+ }
+
+ return (SUCCESS);
+}
+
diff --git a/usr.bin/make/lst.lib/lstInt.h b/usr.bin/make/lst.lib/lstInt.h
new file mode 100644
index 0000000..ac53dcb
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstInt.h
@@ -0,0 +1,105 @@
+/* $NetBSD: lstInt.h,v 1.22 2014/09/07 20:55:34 joerg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)lstInt.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*-
+ * lstInt.h --
+ * Internals for the list library
+ */
+#ifndef _LSTINT_H_
+#define _LSTINT_H_
+
+#include "../lst.h"
+#include "../make_malloc.h"
+
+typedef struct ListNode {
+ struct ListNode *prevPtr; /* previous element in list */
+ struct ListNode *nextPtr; /* next in list */
+ unsigned int useCount:8, /* Count of functions using the node.
+ * node may not be deleted until count
+ * goes to 0 */
+ flags:8; /* Node status flags */
+ void *datum; /* datum associated with this element */
+} *ListNode;
+/*
+ * Flags required for synchronization
+ */
+#define LN_DELETED 0x0001 /* List node should be removed when done */
+
+typedef enum {
+ Head, Middle, Tail, Unknown
+} Where;
+
+typedef struct List {
+ ListNode firstPtr; /* first node in list */
+ ListNode lastPtr; /* last node in list */
+ Boolean isCirc; /* true if the list should be considered
+ * circular */
+/*
+ * fields for sequential access
+ */
+ Where atEnd; /* Where in the list the last access was */
+ Boolean isOpen; /* true if list has been Lst_Open'ed */
+ ListNode curPtr; /* current node, if open. NULL if
+ * *just* opened */
+ ListNode prevPtr; /* Previous node, if open. Used by
+ * Lst_Remove */
+} *List;
+
+/*
+ * PAlloc (var, ptype) --
+ * Allocate a pointer-typedef structure 'ptype' into the variable 'var'
+ */
+#define PAlloc(var,ptype) var = (ptype) bmake_malloc(sizeof *(var))
+
+/*
+ * LstValid (l) --
+ * Return TRUE if the list l is valid
+ */
+#define LstValid(l) ((Lst)(l) != NULL)
+
+/*
+ * LstNodeValid (ln, l) --
+ * Return TRUE if the LstNode ln is valid with respect to l
+ */
+#define LstNodeValid(ln, l) ((ln) != NULL)
+
+/*
+ * LstIsEmpty (l) --
+ * TRUE if the list l is empty.
+ */
+#define LstIsEmpty(l) (((List)(l))->firstPtr == NULL)
+
+#endif /* _LSTINT_H_ */
diff --git a/usr.bin/make/lst.lib/lstIsAtEnd.c b/usr.bin/make/lst.lib/lstIsAtEnd.c
new file mode 100644
index 0000000..70270d2
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstIsAtEnd.c
@@ -0,0 +1,87 @@
+/* $NetBSD: lstIsAtEnd.c,v 1.13 2008/02/15 21:29:50 christos Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstIsAtEnd.c,v 1.13 2008/02/15 21:29:50 christos Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstIsAtEnd.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstIsAtEnd.c,v 1.13 2008/02/15 21:29:50 christos Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstIsAtEnd.c --
+ * Tell if the current node is at the end of the list.
+ * The sequential functions access the list in a slightly different way.
+ * CurPtr points to their idea of the current node in the list and they
+ * access the list based on it. Because the list is circular, Lst_Next
+ * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be
+ * used to determine when to stop.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_IsAtEnd --
+ * Return true if have reached the end of the given list.
+ *
+ * Results:
+ * TRUE if at the end of the list (this includes the list not being
+ * open or being invalid) or FALSE if not. We return TRUE if the list
+ * is invalid or unopend so as to cause the caller to exit its loop
+ * asap, the assumption being that the loop is of the form
+ * while (!Lst_IsAtEnd (l)) {
+ * ...
+ * }
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Lst_IsAtEnd(Lst l)
+{
+ List list = l;
+
+ return (!LstValid (l) || !list->isOpen ||
+ (list->atEnd == Head) || (list->atEnd == Tail));
+}
+
diff --git a/usr.bin/make/lst.lib/lstIsEmpty.c b/usr.bin/make/lst.lib/lstIsEmpty.c
new file mode 100644
index 0000000..8b1d6ed
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstIsEmpty.c
@@ -0,0 +1,75 @@
+/* $NetBSD: lstIsEmpty.c,v 1.11 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstIsEmpty.c,v 1.11 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstIsEmpty.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstIsEmpty.c,v 1.11 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstIsEmpty.c --
+ * A single function to decide if a list is empty
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_IsEmpty --
+ * Return TRUE if the given list is empty.
+ *
+ * Results:
+ * TRUE if the list is empty, FALSE otherwise.
+ *
+ * Side Effects:
+ * None.
+ *
+ * A list is considered empty if its firstPtr == NULL (or if
+ * the list itself is NULL).
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Lst_IsEmpty(Lst l)
+{
+ return ( ! LstValid (l) || LstIsEmpty(l));
+}
+
diff --git a/usr.bin/make/lst.lib/lstLast.c b/usr.bin/make/lst.lib/lstLast.c
new file mode 100644
index 0000000..096ca24
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstLast.c
@@ -0,0 +1,77 @@
+/* $NetBSD: lstLast.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstLast.c,v 1.12 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstLast.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstLast.c,v 1.12 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstLast.c --
+ * Return the last element of a list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Last --
+ * Return the last node on the list l.
+ *
+ * Results:
+ * The requested node or NULL if the list is empty.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Last(Lst l)
+{
+ if (!LstValid(l) || LstIsEmpty (l)) {
+ return NULL;
+ } else {
+ return (l->lastPtr);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstMember.c b/usr.bin/make/lst.lib/lstMember.c
new file mode 100644
index 0000000..e9046ac
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstMember.c
@@ -0,0 +1,77 @@
+/* $NetBSD: lstMember.c,v 1.14 2013/11/14 00:01:28 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstMember.c,v 1.14 2013/11/14 00:01:28 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstMember.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstMember.c,v 1.14 2013/11/14 00:01:28 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * lstMember.c --
+ * See if a given datum is on a given list.
+ */
+
+#include "lstInt.h"
+
+LstNode
+Lst_Member(Lst l, void *d)
+{
+ List list = l;
+ ListNode lNode;
+
+ if (list == NULL) {
+ return NULL;
+ }
+ lNode = list->firstPtr;
+ if (lNode == NULL) {
+ return NULL;
+ }
+
+ do {
+ if (lNode->datum == d) {
+ return lNode;
+ }
+ lNode = lNode->nextPtr;
+ } while (lNode != NULL && lNode != list->firstPtr);
+
+ return NULL;
+}
diff --git a/usr.bin/make/lst.lib/lstNext.c b/usr.bin/make/lst.lib/lstNext.c
new file mode 100644
index 0000000..5c2e0ee
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstNext.c
@@ -0,0 +1,120 @@
+/* $NetBSD: lstNext.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstNext.c,v 1.12 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstNext.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstNext.c,v 1.12 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstNext.c --
+ * Return the next node for a list.
+ * The sequential functions access the list in a slightly different way.
+ * CurPtr points to their idea of the current node in the list and they
+ * access the list based on it. Because the list is circular, Lst_Next
+ * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be
+ * used to determine when to stop.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Next --
+ * Return the next node for the given list.
+ *
+ * Results:
+ * The next node or NULL if the list has yet to be opened. Also
+ * if the list is non-circular and the end has been reached, NULL
+ * is returned.
+ *
+ * Side Effects:
+ * the curPtr field is updated.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Next(Lst l)
+{
+ ListNode tln;
+ List list = l;
+
+ if ((LstValid (l) == FALSE) ||
+ (list->isOpen == FALSE)) {
+ return NULL;
+ }
+
+ list->prevPtr = list->curPtr;
+
+ if (list->curPtr == NULL) {
+ if (list->atEnd == Unknown) {
+ /*
+ * If we're just starting out, atEnd will be Unknown.
+ * Then we want to start this thing off in the right
+ * direction -- at the start with atEnd being Middle.
+ */
+ list->curPtr = tln = list->firstPtr;
+ list->atEnd = Middle;
+ } else {
+ tln = NULL;
+ list->atEnd = Tail;
+ }
+ } else {
+ tln = list->curPtr->nextPtr;
+ list->curPtr = tln;
+
+ if (tln == list->firstPtr || tln == NULL) {
+ /*
+ * If back at the front, then we've hit the end...
+ */
+ list->atEnd = Tail;
+ } else {
+ /*
+ * Reset to Middle if gone past first.
+ */
+ list->atEnd = Middle;
+ }
+ }
+
+ return (tln);
+}
+
diff --git a/usr.bin/make/lst.lib/lstOpen.c b/usr.bin/make/lst.lib/lstOpen.c
new file mode 100644
index 0000000..941293e
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstOpen.c
@@ -0,0 +1,87 @@
+/* $NetBSD: lstOpen.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstOpen.c,v 1.12 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstOpen.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstOpen.c,v 1.12 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstOpen.c --
+ * Open a list for sequential access. The sequential functions access the
+ * list in a slightly different way. CurPtr points to their idea of the
+ * current node in the list and they access the list based on it.
+ * If the list is circular, Lst_Next and Lst_Prev will go around
+ * the list forever. Lst_IsAtEnd must be used to determine when to stop.
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Open --
+ * Open a list for sequential access. A list can still be searched,
+ * etc., without confusing these functions.
+ *
+ * Results:
+ * SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ * isOpen is set TRUE and curPtr is set to NULL so the
+ * other sequential functions no it was just opened and can choose
+ * the first element accessed based on this.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Open(Lst l)
+{
+ if (LstValid (l) == FALSE) {
+ return (FAILURE);
+ }
+ (l)->isOpen = TRUE;
+ (l)->atEnd = LstIsEmpty (l) ? Head : Unknown;
+ (l)->curPtr = NULL;
+
+ return (SUCCESS);
+}
+
diff --git a/usr.bin/make/lst.lib/lstPrev.c b/usr.bin/make/lst.lib/lstPrev.c
new file mode 100644
index 0000000..0ec865d
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstPrev.c
@@ -0,0 +1,79 @@
+/* $NetBSD: lstPrev.c,v 1.3 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstPrev.c,v 1.3 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstSucc.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstPrev.c,v 1.3 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstPrev.c --
+ * return the predecessor to a given node
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Prev --
+ * Return the predecessor to the given node on its list.
+ *
+ * Results:
+ * The predecessor of the node, if it exists (note that on a circular
+ * list, if the node is the only one in the list, it is its own
+ * predecessor).
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Prev(LstNode ln)
+{
+ if (ln == NULL) {
+ return NULL;
+ } else {
+ return (ln->prevPtr);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstRemove.c b/usr.bin/make/lst.lib/lstRemove.c
new file mode 100644
index 0000000..7480d30
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstRemove.c
@@ -0,0 +1,136 @@
+/* $NetBSD: lstRemove.c,v 1.16 2014/09/07 20:55:34 joerg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstRemove.c,v 1.16 2014/09/07 20:55:34 joerg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstRemove.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstRemove.c,v 1.16 2014/09/07 20:55:34 joerg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstRemove.c --
+ * Remove an element from a list
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Remove --
+ * Remove the given node from the given list.
+ *
+ * Results:
+ * SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ * The list's firstPtr will be set to NULL if ln is the last
+ * node on the list. firsPtr and lastPtr will be altered if ln is
+ * either the first or last node, respectively, on the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Remove(Lst l, LstNode ln)
+{
+ List list = l;
+ ListNode lNode = ln;
+
+ if (!LstValid (l) ||
+ !LstNodeValid (ln, l)) {
+ return (FAILURE);
+ }
+
+ /*
+ * unlink it from the list
+ */
+ if (lNode->nextPtr != NULL) {
+ lNode->nextPtr->prevPtr = lNode->prevPtr;
+ }
+ if (lNode->prevPtr != NULL) {
+ lNode->prevPtr->nextPtr = lNode->nextPtr;
+ }
+
+ /*
+ * if either the firstPtr or lastPtr of the list point to this node,
+ * adjust them accordingly
+ */
+ if (list->firstPtr == lNode) {
+ list->firstPtr = lNode->nextPtr;
+ }
+ if (list->lastPtr == lNode) {
+ list->lastPtr = lNode->prevPtr;
+ }
+
+ /*
+ * Sequential access stuff. If the node we're removing is the current
+ * node in the list, reset the current node to the previous one. If the
+ * previous one was non-existent (prevPtr == NULL), we set the
+ * end to be Unknown, since it is.
+ */
+ if (list->isOpen && (list->curPtr == lNode)) {
+ list->curPtr = list->prevPtr;
+ if (list->curPtr == NULL) {
+ list->atEnd = Unknown;
+ }
+ }
+
+ /*
+ * the only way firstPtr can still point to ln is if ln is the last
+ * node on the list (the list is circular, so lNode->nextptr == lNode in
+ * this case). The list is, therefore, empty and is marked as such
+ */
+ if (list->firstPtr == lNode) {
+ list->firstPtr = NULL;
+ }
+
+ /*
+ * note that the datum is unmolested. The caller must free it as
+ * necessary and as expected.
+ */
+ if (lNode->useCount == 0) {
+ free(ln);
+ } else {
+ lNode->flags |= LN_DELETED;
+ }
+
+ return (SUCCESS);
+}
+
diff --git a/usr.bin/make/lst.lib/lstReplace.c b/usr.bin/make/lst.lib/lstReplace.c
new file mode 100644
index 0000000..090e91a
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstReplace.c
@@ -0,0 +1,78 @@
+/* $NetBSD: lstReplace.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstReplace.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstReplace.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstReplace.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstReplace.c --
+ * Replace the datum in a node with a new datum
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Replace --
+ * Replace the datum in the given node with the new datum
+ *
+ * Results:
+ * SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ * The datum field fo the node is altered.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Replace(LstNode ln, void *d)
+{
+ if (ln == NULL) {
+ return (FAILURE);
+ } else {
+ (ln)->datum = d;
+ return (SUCCESS);
+ }
+}
+
diff --git a/usr.bin/make/lst.lib/lstSucc.c b/usr.bin/make/lst.lib/lstSucc.c
new file mode 100644
index 0000000..3f13aa5
--- /dev/null
+++ b/usr.bin/make/lst.lib/lstSucc.c
@@ -0,0 +1,79 @@
+/* $NetBSD: lstSucc.c,v 1.13 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstSucc.c,v 1.13 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstSucc.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstSucc.c,v 1.13 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstSucc.c --
+ * return the successor to a given node
+ */
+
+#include "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Succ --
+ * Return the successor to the given node on its list.
+ *
+ * Results:
+ * The successor of the node, if it exists (note that on a circular
+ * list, if the node is the only one in the list, it is its own
+ * successor).
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Succ(LstNode ln)
+{
+ if (ln == NULL) {
+ return NULL;
+ } else {
+ return (ln->nextPtr);
+ }
+}
+
diff --git a/usr.bin/make/main.c b/usr.bin/make/main.c
new file mode 100644
index 0000000..a9e756a
--- /dev/null
+++ b/usr.bin/make/main.c
@@ -0,0 +1,2189 @@
+/* $NetBSD: main.c,v 1.273 2017/10/28 21:54:54 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: main.c,v 1.273 2017/10/28 21:54:54 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)main.c 8.3 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: main.c,v 1.273 2017/10/28 21:54:54 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * main.c --
+ * The main file for this entire program. Exit routines etc
+ * reside here.
+ *
+ * Utility functions defined in this file:
+ * Main_ParseArgLine Takes a line of arguments, breaks them and
+ * treats them as if they were given when first
+ * invoked. Used by the parse module to implement
+ * the .MFLAGS target.
+ *
+ * Error Print a tagged error message. The global
+ * MAKE variable must have been defined. This
+ * takes a format string and optional arguments
+ * for it.
+ *
+ * Fatal Print an error message and exit. Also takes
+ * a format string and arguments for it.
+ *
+ * Punt Aborts all jobs and exits with a message. Also
+ * takes a format string and arguments for it.
+ *
+ * Finish Finish things up by printing the number of
+ * errors which occurred, as passed to it, and
+ * exiting.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#ifdef MAKE_NATIVE
+#include <sys/sysctl.h>
+#endif
+#include <sys/utsname.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "pathnames.h"
+#include "trace.h"
+
+#ifdef USE_IOVEC
+#include <sys/uio.h>
+#endif
+
+#ifndef DEFMAXLOCAL
+#define DEFMAXLOCAL DEFMAXJOBS
+#endif /* DEFMAXLOCAL */
+
+Lst create; /* Targets to be made */
+time_t now; /* Time at start of make */
+GNode *DEFAULT; /* .DEFAULT node */
+Boolean allPrecious; /* .PRECIOUS given on line by itself */
+Boolean deleteOnError; /* .DELETE_ON_ERROR: set */
+
+static Boolean noBuiltins; /* -r flag */
+static Lst makefiles; /* ordered list of makefiles to read */
+static int printVars; /* -[vV] argument */
+#define COMPAT_VARS 1
+#define EXPAND_VARS 2
+static Lst variables; /* list of variables to print */
+int maxJobs; /* -j argument */
+static int maxJobTokens; /* -j argument */
+Boolean compatMake; /* -B argument */
+int debug; /* -d argument */
+Boolean debugVflag; /* -dV */
+Boolean noExecute; /* -n flag */
+Boolean noRecursiveExecute; /* -N flag */
+Boolean keepgoing; /* -k flag */
+Boolean queryFlag; /* -q flag */
+Boolean touchFlag; /* -t flag */
+Boolean enterFlag; /* -w flag */
+Boolean enterFlagObj; /* -w and objdir != srcdir */
+Boolean ignoreErrors; /* -i flag */
+Boolean beSilent; /* -s flag */
+Boolean oldVars; /* variable substitution style */
+Boolean checkEnvFirst; /* -e flag */
+Boolean parseWarnFatal; /* -W flag */
+Boolean jobServer; /* -J flag */
+static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */
+Boolean varNoExportEnv; /* -X flag */
+Boolean doing_depend; /* Set while reading .depend */
+static Boolean jobsRunning; /* TRUE if the jobs might be running */
+static const char * tracefile;
+static void MainParseArgs(int, char **);
+static int ReadMakefile(const void *, const void *);
+static void usage(void) MAKE_ATTR_DEAD;
+static void purge_cached_realpaths(void);
+
+static Boolean ignorePWD; /* if we use -C, PWD is meaningless */
+static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */
+char curdir[MAXPATHLEN + 1]; /* Startup directory */
+char *progname; /* the program name */
+char *makeDependfile;
+pid_t myPid;
+int makelevel;
+
+Boolean forceJobs = FALSE;
+
+extern Lst parseIncPath;
+
+/*
+ * For compatibility with the POSIX version of MAKEFLAGS that includes
+ * all the options with out -, convert flags to -f -l -a -g -s.
+ */
+static char *
+explode(const char *flags)
+{
+ size_t len;
+ char *nf, *st;
+ const char *f;
+
+ if (flags == NULL)
+ return NULL;
+
+ for (f = flags; *f; f++)
+ if (!isalpha((unsigned char)*f))
+ break;
+
+ if (*f)
+ return bmake_strdup(flags);
+
+ len = strlen(flags);
+ st = nf = bmake_malloc(len * 3 + 1);
+ while (*flags) {
+ *nf++ = '-';
+ *nf++ = *flags++;
+ *nf++ = ' ';
+ }
+ *nf = '\0';
+ return st;
+}
+
+static void
+parse_debug_options(const char *argvalue)
+{
+ const char *modules;
+ const char *mode;
+ char *fname;
+ int len;
+
+ for (modules = argvalue; *modules; ++modules) {
+ switch (*modules) {
+ case 'A':
+ debug = ~0;
+ break;
+ case 'a':
+ debug |= DEBUG_ARCH;
+ break;
+ case 'C':
+ debug |= DEBUG_CWD;
+ break;
+ case 'c':
+ debug |= DEBUG_COND;
+ break;
+ case 'd':
+ debug |= DEBUG_DIR;
+ break;
+ case 'e':
+ debug |= DEBUG_ERROR;
+ break;
+ case 'f':
+ debug |= DEBUG_FOR;
+ break;
+ case 'g':
+ if (modules[1] == '1') {
+ debug |= DEBUG_GRAPH1;
+ ++modules;
+ }
+ else if (modules[1] == '2') {
+ debug |= DEBUG_GRAPH2;
+ ++modules;
+ }
+ else if (modules[1] == '3') {
+ debug |= DEBUG_GRAPH3;
+ ++modules;
+ }
+ break;
+ case 'j':
+ debug |= DEBUG_JOB;
+ break;
+ case 'l':
+ debug |= DEBUG_LOUD;
+ break;
+ case 'M':
+ debug |= DEBUG_META;
+ break;
+ case 'm':
+ debug |= DEBUG_MAKE;
+ break;
+ case 'n':
+ debug |= DEBUG_SCRIPT;
+ break;
+ case 'p':
+ debug |= DEBUG_PARSE;
+ break;
+ case 's':
+ debug |= DEBUG_SUFF;
+ break;
+ case 't':
+ debug |= DEBUG_TARG;
+ break;
+ case 'V':
+ debugVflag = TRUE;
+ break;
+ case 'v':
+ debug |= DEBUG_VAR;
+ break;
+ case 'x':
+ debug |= DEBUG_SHELL;
+ break;
+ case 'F':
+ if (debug_file != stdout && debug_file != stderr)
+ fclose(debug_file);
+ if (*++modules == '+') {
+ modules++;
+ mode = "a";
+ } else
+ mode = "w";
+ if (strcmp(modules, "stdout") == 0) {
+ debug_file = stdout;
+ goto debug_setbuf;
+ }
+ if (strcmp(modules, "stderr") == 0) {
+ debug_file = stderr;
+ goto debug_setbuf;
+ }
+ len = strlen(modules);
+ fname = bmake_malloc(len + 20);
+ memcpy(fname, modules, len + 1);
+ /* Let the filename be modified by the pid */
+ if (strcmp(fname + len - 3, ".%d") == 0)
+ snprintf(fname + len - 2, 20, "%d", getpid());
+ debug_file = fopen(fname, mode);
+ if (!debug_file) {
+ fprintf(stderr, "Cannot open debug file %s\n",
+ fname);
+ usage();
+ }
+ free(fname);
+ goto debug_setbuf;
+ default:
+ (void)fprintf(stderr,
+ "%s: illegal argument to d option -- %c\n",
+ progname, *modules);
+ usage();
+ }
+ }
+debug_setbuf:
+ /*
+ * Make the debug_file unbuffered, and make
+ * stdout line buffered (unless debugfile == stdout).
+ */
+ setvbuf(debug_file, NULL, _IONBF, 0);
+ if (debug_file != stdout) {
+ setvbuf(stdout, NULL, _IOLBF, 0);
+ }
+}
+
+/*
+ * does path contain any relative components
+ */
+static int
+is_relpath(const char *path)
+{
+ const char *cp;
+
+ if (path[0] != '/')
+ return TRUE;
+ cp = path;
+ do {
+ cp = strstr(cp, "/.");
+ if (!cp)
+ break;
+ cp += 2;
+ if (cp[0] == '/' || cp[0] == '\0')
+ return TRUE;
+ else if (cp[0] == '.') {
+ if (cp[1] == '/' || cp[1] == '\0')
+ return TRUE;
+ }
+ } while (cp);
+ return FALSE;
+}
+
+/*-
+ * MainParseArgs --
+ * Parse a given argument vector. Called from main() and from
+ * Main_ParseArgLine() when the .MAKEFLAGS target is used.
+ *
+ * XXX: Deal with command line overriding .MAKEFLAGS in makefile
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Various global and local flags will be set depending on the flags
+ * given
+ */
+static void
+MainParseArgs(int argc, char **argv)
+{
+ char *p;
+ int c = '?';
+ int arginc;
+ char *argvalue;
+ const char *getopt_def;
+ struct stat sa, sb;
+ char *optscan;
+ Boolean inOption, dashDash = FALSE;
+ char found_path[MAXPATHLEN + 1]; /* for searching for sys.mk */
+
+#define OPTFLAGS "BC:D:I:J:NST:V:WXd:ef:ij:km:nqrstv:w"
+/* Can't actually use getopt(3) because rescanning is not portable */
+
+ getopt_def = OPTFLAGS;
+rearg:
+ inOption = FALSE;
+ optscan = NULL;
+ while(argc > 1) {
+ char *getopt_spec;
+ if(!inOption)
+ optscan = argv[1];
+ c = *optscan++;
+ arginc = 0;
+ if(inOption) {
+ if(c == '\0') {
+ ++argv;
+ --argc;
+ inOption = FALSE;
+ continue;
+ }
+ } else {
+ if (c != '-' || dashDash)
+ break;
+ inOption = TRUE;
+ c = *optscan++;
+ }
+ /* '-' found at some earlier point */
+ getopt_spec = strchr(getopt_def, c);
+ if(c != '\0' && getopt_spec != NULL && getopt_spec[1] == ':') {
+ /* -<something> found, and <something> should have an arg */
+ inOption = FALSE;
+ arginc = 1;
+ argvalue = optscan;
+ if(*argvalue == '\0') {
+ if (argc < 3)
+ goto noarg;
+ argvalue = argv[2];
+ arginc = 2;
+ }
+ } else {
+ argvalue = NULL;
+ }
+ switch(c) {
+ case '\0':
+ arginc = 1;
+ inOption = FALSE;
+ break;
+ case 'B':
+ compatMake = TRUE;
+ Var_Append(MAKEFLAGS, "-B", VAR_GLOBAL);
+ Var_Set(MAKE_MODE, "compat", VAR_GLOBAL, 0);
+ break;
+ case 'C':
+ if (chdir(argvalue) == -1) {
+ (void)fprintf(stderr,
+ "%s: chdir %s: %s\n",
+ progname, argvalue,
+ strerror(errno));
+ exit(1);
+ }
+ if (getcwd(curdir, MAXPATHLEN) == NULL) {
+ (void)fprintf(stderr, "%s: %s.\n", progname, strerror(errno));
+ exit(2);
+ }
+ if (!is_relpath(argvalue) &&
+ stat(argvalue, &sa) != -1 &&
+ stat(curdir, &sb) != -1 &&
+ sa.st_ino == sb.st_ino &&
+ sa.st_dev == sb.st_dev)
+ strncpy(curdir, argvalue, MAXPATHLEN);
+ ignorePWD = TRUE;
+ break;
+ case 'D':
+ if (argvalue == NULL || argvalue[0] == 0) goto noarg;
+ Var_Set(argvalue, "1", VAR_GLOBAL, 0);
+ Var_Append(MAKEFLAGS, "-D", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ break;
+ case 'I':
+ if (argvalue == NULL) goto noarg;
+ Parse_AddIncludeDir(argvalue);
+ Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ break;
+ case 'J':
+ if (argvalue == NULL) goto noarg;
+ if (sscanf(argvalue, "%d,%d", &jp_0, &jp_1) != 2) {
+ (void)fprintf(stderr,
+ "%s: internal error -- J option malformed (%s)\n",
+ progname, argvalue);
+ usage();
+ }
+ if ((fcntl(jp_0, F_GETFD, 0) < 0) ||
+ (fcntl(jp_1, F_GETFD, 0) < 0)) {
+#if 0
+ (void)fprintf(stderr,
+ "%s: ###### warning -- J descriptors were closed!\n",
+ progname);
+ exit(2);
+#endif
+ jp_0 = -1;
+ jp_1 = -1;
+ compatMake = TRUE;
+ } else {
+ Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ jobServer = TRUE;
+ }
+ break;
+ case 'N':
+ noExecute = TRUE;
+ noRecursiveExecute = TRUE;
+ Var_Append(MAKEFLAGS, "-N", VAR_GLOBAL);
+ break;
+ case 'S':
+ keepgoing = FALSE;
+ Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL);
+ break;
+ case 'T':
+ if (argvalue == NULL) goto noarg;
+ tracefile = bmake_strdup(argvalue);
+ Var_Append(MAKEFLAGS, "-T", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ break;
+ case 'V':
+ case 'v':
+ if (argvalue == NULL) goto noarg;
+ printVars = c == 'v' ? EXPAND_VARS : COMPAT_VARS;
+ (void)Lst_AtEnd(variables, argvalue);
+ Var_Append(MAKEFLAGS, "-V", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ break;
+ case 'W':
+ parseWarnFatal = TRUE;
+ break;
+ case 'X':
+ varNoExportEnv = TRUE;
+ Var_Append(MAKEFLAGS, "-X", VAR_GLOBAL);
+ break;
+ case 'd':
+ if (argvalue == NULL) goto noarg;
+ /* If '-d-opts' don't pass to children */
+ if (argvalue[0] == '-')
+ argvalue++;
+ else {
+ Var_Append(MAKEFLAGS, "-d", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ }
+ parse_debug_options(argvalue);
+ break;
+ case 'e':
+ checkEnvFirst = TRUE;
+ Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL);
+ break;
+ case 'f':
+ if (argvalue == NULL) goto noarg;
+ (void)Lst_AtEnd(makefiles, argvalue);
+ break;
+ case 'i':
+ ignoreErrors = TRUE;
+ Var_Append(MAKEFLAGS, "-i", VAR_GLOBAL);
+ break;
+ case 'j':
+ if (argvalue == NULL) goto noarg;
+ forceJobs = TRUE;
+ maxJobs = strtol(argvalue, &p, 0);
+ if (*p != '\0' || maxJobs < 1) {
+ (void)fprintf(stderr, "%s: illegal argument to -j -- must be positive integer!\n",
+ progname);
+ exit(1);
+ }
+ Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ Var_Set(".MAKE.JOBS", argvalue, VAR_GLOBAL, 0);
+ maxJobTokens = maxJobs;
+ break;
+ case 'k':
+ keepgoing = TRUE;
+ Var_Append(MAKEFLAGS, "-k", VAR_GLOBAL);
+ break;
+ case 'm':
+ if (argvalue == NULL) goto noarg;
+ /* look for magic parent directory search string */
+ if (strncmp(".../", argvalue, 4) == 0) {
+ if (!Dir_FindHereOrAbove(curdir, argvalue+4,
+ found_path, sizeof(found_path)))
+ break; /* nothing doing */
+ (void)Dir_AddDir(sysIncPath, found_path);
+ } else {
+ (void)Dir_AddDir(sysIncPath, argvalue);
+ }
+ Var_Append(MAKEFLAGS, "-m", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+ break;
+ case 'n':
+ noExecute = TRUE;
+ Var_Append(MAKEFLAGS, "-n", VAR_GLOBAL);
+ break;
+ case 'q':
+ queryFlag = TRUE;
+ /* Kind of nonsensical, wot? */
+ Var_Append(MAKEFLAGS, "-q", VAR_GLOBAL);
+ break;
+ case 'r':
+ noBuiltins = TRUE;
+ Var_Append(MAKEFLAGS, "-r", VAR_GLOBAL);
+ break;
+ case 's':
+ beSilent = TRUE;
+ Var_Append(MAKEFLAGS, "-s", VAR_GLOBAL);
+ break;
+ case 't':
+ touchFlag = TRUE;
+ Var_Append(MAKEFLAGS, "-t", VAR_GLOBAL);
+ break;
+ case 'w':
+ enterFlag = TRUE;
+ Var_Append(MAKEFLAGS, "-w", VAR_GLOBAL);
+ break;
+ case '-':
+ dashDash = TRUE;
+ break;
+ default:
+ case '?':
+ usage();
+ }
+ argv += arginc;
+ argc -= arginc;
+ }
+
+ oldVars = TRUE;
+
+ /*
+ * See if the rest of the arguments are variable assignments and
+ * perform them if so. Else take them to be targets and stuff them
+ * on the end of the "create" list.
+ */
+ for (; argc > 1; ++argv, --argc)
+ if (Parse_IsVar(argv[1])) {
+ Parse_DoVar(argv[1], VAR_CMD);
+ } else {
+ if (!*argv[1])
+ Punt("illegal (null) argument.");
+ if (*argv[1] == '-' && !dashDash)
+ goto rearg;
+ (void)Lst_AtEnd(create, bmake_strdup(argv[1]));
+ }
+
+ return;
+noarg:
+ (void)fprintf(stderr, "%s: option requires an argument -- %c\n",
+ progname, c);
+ usage();
+}
+
+/*-
+ * Main_ParseArgLine --
+ * Used by the parse module when a .MFLAGS or .MAKEFLAGS target
+ * is encountered and by main() when reading the .MAKEFLAGS envariable.
+ * Takes a line of arguments and breaks it into its
+ * component words and passes those words and the number of them to the
+ * MainParseArgs function.
+ * The line should have all its leading whitespace removed.
+ *
+ * Input:
+ * line Line to fracture
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Only those that come from the various arguments.
+ */
+void
+Main_ParseArgLine(const char *line)
+{
+ char **argv; /* Manufactured argument vector */
+ int argc; /* Number of arguments in argv */
+ char *args; /* Space used by the args */
+ char *buf, *p1;
+ char *argv0 = Var_Value(".MAKE", VAR_GLOBAL, &p1);
+ size_t len;
+
+ if (line == NULL)
+ return;
+ for (; *line == ' '; ++line)
+ continue;
+ if (!*line)
+ return;
+
+ buf = bmake_malloc(len = strlen(line) + strlen(argv0) + 2);
+ (void)snprintf(buf, len, "%s %s", argv0, line);
+ free(p1);
+
+ argv = brk_string(buf, &argc, TRUE, &args);
+ if (argv == NULL) {
+ Error("Unterminated quoted string [%s]", buf);
+ free(buf);
+ return;
+ }
+ free(buf);
+ MainParseArgs(argc, argv);
+
+ free(args);
+ free(argv);
+}
+
+Boolean
+Main_SetObjdir(const char *fmt, ...)
+{
+ struct stat sb;
+ char *path;
+ char buf[MAXPATHLEN + 1];
+ char buf2[MAXPATHLEN + 1];
+ Boolean rc = FALSE;
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(path = buf, MAXPATHLEN, fmt, ap);
+ va_end(ap);
+
+ if (path[0] != '/') {
+ snprintf(buf2, MAXPATHLEN, "%s/%s", curdir, path);
+ path = buf2;
+ }
+
+ /* look for the directory and try to chdir there */
+ if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
+ if (chdir(path)) {
+ (void)fprintf(stderr, "make warning: %s: %s.\n",
+ path, strerror(errno));
+ } else {
+ strncpy(objdir, path, MAXPATHLEN);
+ Var_Set(".OBJDIR", objdir, VAR_GLOBAL, 0);
+ setenv("PWD", objdir, 1);
+ Dir_InitDot();
+ purge_cached_realpaths();
+ rc = TRUE;
+ if (enterFlag && strcmp(objdir, curdir) != 0)
+ enterFlagObj = TRUE;
+ }
+ }
+
+ return rc;
+}
+
+static Boolean
+Main_SetVarObjdir(const char *var, const char *suffix)
+{
+ char *p, *path, *xpath;
+
+ if ((path = Var_Value(var, VAR_CMD, &p)) == NULL ||
+ *path == '\0')
+ return FALSE;
+
+ /* expand variable substitutions */
+ if (strchr(path, '$') != 0)
+ xpath = Var_Subst(NULL, path, VAR_GLOBAL, VARF_WANTRES);
+ else
+ xpath = path;
+
+ (void)Main_SetObjdir("%s%s", xpath, suffix);
+
+ if (xpath != path)
+ free(xpath);
+ free(p);
+ return TRUE;
+}
+
+/*-
+ * ReadAllMakefiles --
+ * wrapper around ReadMakefile() to read all.
+ *
+ * Results:
+ * TRUE if ok, FALSE on error
+ */
+static int
+ReadAllMakefiles(const void *p, const void *q)
+{
+ return (ReadMakefile(p, q) == 0);
+}
+
+int
+str2Lst_Append(Lst lp, char *str, const char *sep)
+{
+ char *cp;
+ int n;
+
+ if (!sep)
+ sep = " \t";
+
+ for (n = 0, cp = strtok(str, sep); cp; cp = strtok(NULL, sep)) {
+ (void)Lst_AtEnd(lp, cp);
+ n++;
+ }
+ return (n);
+}
+
+#ifdef SIGINFO
+/*ARGSUSED*/
+static void
+siginfo(int signo MAKE_ATTR_UNUSED)
+{
+ char dir[MAXPATHLEN];
+ char str[2 * MAXPATHLEN];
+ int len;
+ if (getcwd(dir, sizeof(dir)) == NULL)
+ return;
+ len = snprintf(str, sizeof(str), "%s: Working in: %s\n", progname, dir);
+ if (len > 0)
+ (void)write(STDERR_FILENO, str, (size_t)len);
+}
+#endif
+
+/*
+ * Allow makefiles some control over the mode we run in.
+ */
+void
+MakeMode(const char *mode)
+{
+ char *mp = NULL;
+
+ if (!mode)
+ mode = mp = Var_Subst(NULL, "${" MAKE_MODE ":tl}",
+ VAR_GLOBAL, VARF_WANTRES);
+
+ if (mode && *mode) {
+ if (strstr(mode, "compat")) {
+ compatMake = TRUE;
+ forceJobs = FALSE;
+ }
+#if USE_META
+ if (strstr(mode, "meta"))
+ meta_mode_init(mode);
+#endif
+ }
+
+ free(mp);
+}
+
+static void
+doPrintVars(void)
+{
+ LstNode ln;
+ Boolean expandVars;
+
+ if (printVars == EXPAND_VARS)
+ expandVars = TRUE;
+ else if (debugVflag)
+ expandVars = FALSE;
+ else
+ expandVars = getBoolean(".MAKE.EXPAND_VARIABLES", FALSE);
+
+ for (ln = Lst_First(variables); ln != NULL;
+ ln = Lst_Succ(ln)) {
+ char *var = (char *)Lst_Datum(ln);
+ char *value;
+ char *p1;
+
+ if (strchr(var, '$')) {
+ value = p1 = Var_Subst(NULL, var, VAR_GLOBAL,
+ VARF_WANTRES);
+ } else if (expandVars) {
+ char tmp[128];
+ int len = snprintf(tmp, sizeof(tmp), "${%s}", var);
+
+ if (len >= (int)sizeof(tmp))
+ Fatal("%s: variable name too big: %s",
+ progname, var);
+ value = p1 = Var_Subst(NULL, tmp, VAR_GLOBAL,
+ VARF_WANTRES);
+ } else {
+ value = Var_Value(var, VAR_GLOBAL, &p1);
+ }
+ printf("%s\n", value ? value : "");
+ free(p1);
+ }
+}
+
+static Boolean
+runTargets(void)
+{
+ Lst targs; /* target nodes to create -- passed to Make_Init */
+ Boolean outOfDate; /* FALSE if all targets up to date */
+
+ /*
+ * Have now read the entire graph and need to make a list of
+ * targets to create. If none was given on the command line,
+ * we consult the parsing module to find the main target(s)
+ * to create.
+ */
+ if (Lst_IsEmpty(create))
+ targs = Parse_MainName();
+ else
+ targs = Targ_FindList(create, TARG_CREATE);
+
+ if (!compatMake) {
+ /*
+ * Initialize job module before traversing the graph
+ * now that any .BEGIN and .END targets have been read.
+ * This is done only if the -q flag wasn't given
+ * (to prevent the .BEGIN from being executed should
+ * it exist).
+ */
+ if (!queryFlag) {
+ Job_Init();
+ jobsRunning = TRUE;
+ }
+
+ /* Traverse the graph, checking on all the targets */
+ outOfDate = Make_Run(targs);
+ } else {
+ /*
+ * Compat_Init will take care of creating all the
+ * targets as well as initializing the module.
+ */
+ Compat_Run(targs);
+ outOfDate = FALSE;
+ }
+ Lst_Destroy(targs, NULL);
+ return outOfDate;
+}
+
+/*-
+ * main --
+ * The main function, for obvious reasons. Initializes variables
+ * and a few modules, then parses the arguments give it in the
+ * environment and on the command line. Reads the system makefile
+ * followed by either Makefile, makefile or the file given by the
+ * -f argument. Sets the .MAKEFLAGS PMake variable based on all the
+ * flags it has received by then uses either the Make or the Compat
+ * module to create the initial list of targets.
+ *
+ * Results:
+ * If -q was given, exits -1 if anything was out-of-date. Else it exits
+ * 0.
+ *
+ * Side Effects:
+ * The program exits when done. Targets are created. etc. etc. etc.
+ */
+int
+main(int argc, char **argv)
+{
+ Boolean outOfDate; /* FALSE if all targets up to date */
+ struct stat sb, sa;
+ char *p1, *path;
+ char mdpath[MAXPATHLEN];
+ const char *machine = getenv("MACHINE");
+ const char *machine_arch = getenv("MACHINE_ARCH");
+ char *syspath = getenv("MAKESYSPATH");
+ Lst sysMkPath; /* Path of sys.mk */
+ char *cp = NULL, *start;
+ /* avoid faults on read-only strings */
+ static char defsyspath[] = _PATH_DEFSYSPATH;
+ char found_path[MAXPATHLEN + 1]; /* for searching for sys.mk */
+ struct timeval rightnow; /* to initialize random seed */
+ struct utsname utsname;
+
+ /* default to writing debug to stderr */
+ debug_file = stderr;
+
+#ifdef SIGINFO
+ (void)bmake_signal(SIGINFO, siginfo);
+#endif
+ /*
+ * Set the seed to produce a different random sequence
+ * on each program execution.
+ */
+ gettimeofday(&rightnow, NULL);
+ srandom(rightnow.tv_sec + rightnow.tv_usec);
+
+ if ((progname = strrchr(argv[0], '/')) != NULL)
+ progname++;
+ else
+ progname = argv[0];
+#if defined(MAKE_NATIVE) || (defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE))
+ /*
+ * get rid of resource limit on file descriptors
+ */
+ {
+ struct rlimit rl;
+ if (getrlimit(RLIMIT_NOFILE, &rl) != -1 &&
+ rl.rlim_cur != rl.rlim_max) {
+ rl.rlim_cur = rl.rlim_max;
+ (void)setrlimit(RLIMIT_NOFILE, &rl);
+ }
+ }
+#endif
+
+ if (uname(&utsname) == -1) {
+ (void)fprintf(stderr, "%s: uname failed (%s).\n", progname,
+ strerror(errno));
+ exit(2);
+ }
+
+ /*
+ * Get the name of this type of MACHINE from utsname
+ * so we can share an executable for similar machines.
+ * (i.e. m68k: amiga hp300, mac68k, sun3, ...)
+ *
+ * Note that both MACHINE and MACHINE_ARCH are decided at
+ * run-time.
+ */
+ if (!machine) {
+#ifdef MAKE_NATIVE
+ machine = utsname.machine;
+#else
+#ifdef MAKE_MACHINE
+ machine = MAKE_MACHINE;
+#else
+ machine = "unknown";
+#endif
+#endif
+ }
+
+ if (!machine_arch) {
+#ifdef MAKE_NATIVE
+ static char machine_arch_buf[sizeof(utsname.machine)];
+ const int mib[2] = { CTL_HW, HW_MACHINE_ARCH };
+ size_t len = sizeof(machine_arch_buf);
+
+ if (sysctl(mib, __arraycount(mib), machine_arch_buf,
+ &len, NULL, 0) < 0) {
+ (void)fprintf(stderr, "%s: sysctl failed (%s).\n", progname,
+ strerror(errno));
+ exit(2);
+ }
+
+ machine_arch = machine_arch_buf;
+#else
+#ifndef MACHINE_ARCH
+#ifdef MAKE_MACHINE_ARCH
+ machine_arch = MAKE_MACHINE_ARCH;
+#else
+ machine_arch = "unknown";
+#endif
+#else
+ machine_arch = MACHINE_ARCH;
+#endif
+#endif
+ }
+
+ myPid = getpid(); /* remember this for vFork() */
+
+ /*
+ * Just in case MAKEOBJDIR wants us to do something tricky.
+ */
+ Var_Init(); /* Initialize the lists of variables for
+ * parsing arguments */
+ Var_Set(".MAKE.OS", utsname.sysname, VAR_GLOBAL, 0);
+ Var_Set("MACHINE", machine, VAR_GLOBAL, 0);
+ Var_Set("MACHINE_ARCH", machine_arch, VAR_GLOBAL, 0);
+#ifdef MAKE_VERSION
+ Var_Set("MAKE_VERSION", MAKE_VERSION, VAR_GLOBAL, 0);
+#endif
+ Var_Set(".newline", "\n", VAR_GLOBAL, 0); /* handy for :@ loops */
+ /*
+ * This is the traditional preference for makefiles.
+ */
+#ifndef MAKEFILE_PREFERENCE_LIST
+# define MAKEFILE_PREFERENCE_LIST "makefile Makefile"
+#endif
+ Var_Set(MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST,
+ VAR_GLOBAL, 0);
+ Var_Set(MAKE_DEPENDFILE, ".depend", VAR_GLOBAL, 0);
+
+ create = Lst_Init(FALSE);
+ makefiles = Lst_Init(FALSE);
+ printVars = 0;
+ debugVflag = FALSE;
+ variables = Lst_Init(FALSE);
+ beSilent = FALSE; /* Print commands as executed */
+ ignoreErrors = FALSE; /* Pay attention to non-zero returns */
+ noExecute = FALSE; /* Execute all commands */
+ noRecursiveExecute = FALSE; /* Execute all .MAKE targets */
+ keepgoing = FALSE; /* Stop on error */
+ allPrecious = FALSE; /* Remove targets when interrupted */
+ deleteOnError = FALSE; /* Historical default behavior */
+ queryFlag = FALSE; /* This is not just a check-run */
+ noBuiltins = FALSE; /* Read the built-in rules */
+ touchFlag = FALSE; /* Actually update targets */
+ debug = 0; /* No debug verbosity, please. */
+ jobsRunning = FALSE;
+
+ maxJobs = DEFMAXLOCAL; /* Set default local max concurrency */
+ maxJobTokens = maxJobs;
+ compatMake = FALSE; /* No compat mode */
+ ignorePWD = FALSE;
+
+ /*
+ * Initialize the parsing, directory and variable modules to prepare
+ * for the reading of inclusion paths and variable settings on the
+ * command line
+ */
+
+ /*
+ * Initialize various variables.
+ * MAKE also gets this name, for compatibility
+ * .MAKEFLAGS gets set to the empty string just in case.
+ * MFLAGS also gets initialized empty, for compatibility.
+ */
+ Parse_Init();
+ if (argv[0][0] == '/' || strchr(argv[0], '/') == NULL) {
+ /*
+ * Leave alone if it is an absolute path, or if it does
+ * not contain a '/' in which case we need to find it in
+ * the path, like execvp(3) and the shells do.
+ */
+ p1 = argv[0];
+ } else {
+ /*
+ * A relative path, canonicalize it.
+ */
+ p1 = cached_realpath(argv[0], mdpath);
+ if (!p1 || *p1 != '/' || stat(p1, &sb) < 0) {
+ p1 = argv[0]; /* realpath failed */
+ }
+ }
+ Var_Set("MAKE", p1, VAR_GLOBAL, 0);
+ Var_Set(".MAKE", p1, VAR_GLOBAL, 0);
+ Var_Set(MAKEFLAGS, "", VAR_GLOBAL, 0);
+ Var_Set(MAKEOVERRIDES, "", VAR_GLOBAL, 0);
+ Var_Set("MFLAGS", "", VAR_GLOBAL, 0);
+ Var_Set(".ALLTARGETS", "", VAR_GLOBAL, 0);
+ /* some makefiles need to know this */
+ Var_Set(MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV, VAR_CMD, 0);
+
+ /*
+ * Set some other useful macros
+ */
+ {
+ char tmp[64], *ep;
+
+ makelevel = ((ep = getenv(MAKE_LEVEL_ENV)) && *ep) ? atoi(ep) : 0;
+ if (makelevel < 0)
+ makelevel = 0;
+ snprintf(tmp, sizeof(tmp), "%d", makelevel);
+ Var_Set(MAKE_LEVEL, tmp, VAR_GLOBAL, 0);
+ snprintf(tmp, sizeof(tmp), "%u", myPid);
+ Var_Set(".MAKE.PID", tmp, VAR_GLOBAL, 0);
+ snprintf(tmp, sizeof(tmp), "%u", getppid());
+ Var_Set(".MAKE.PPID", tmp, VAR_GLOBAL, 0);
+ }
+ if (makelevel > 0) {
+ char pn[1024];
+ snprintf(pn, sizeof(pn), "%s[%d]", progname, makelevel);
+ progname = bmake_strdup(pn);
+ }
+
+#ifdef USE_META
+ meta_init();
+#endif
+ Dir_Init(NULL); /* Dir_* safe to call from MainParseArgs */
+
+ /*
+ * First snag any flags out of the MAKE environment variable.
+ * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's
+ * in a different format).
+ */
+#ifdef POSIX
+ p1 = explode(getenv("MAKEFLAGS"));
+ Main_ParseArgLine(p1);
+ free(p1);
+#else
+ Main_ParseArgLine(getenv("MAKE"));
+#endif
+
+ /*
+ * Find where we are (now).
+ * We take care of PWD for the automounter below...
+ */
+ if (getcwd(curdir, MAXPATHLEN) == NULL) {
+ (void)fprintf(stderr, "%s: getcwd: %s.\n",
+ progname, strerror(errno));
+ exit(2);
+ }
+
+ MainParseArgs(argc, argv);
+
+ if (enterFlag)
+ printf("%s: Entering directory `%s'\n", progname, curdir);
+
+ /*
+ * Verify that cwd is sane.
+ */
+ if (stat(curdir, &sa) == -1) {
+ (void)fprintf(stderr, "%s: %s: %s.\n",
+ progname, curdir, strerror(errno));
+ exit(2);
+ }
+
+ /*
+ * All this code is so that we know where we are when we start up
+ * on a different machine with pmake.
+ * Overriding getcwd() with $PWD totally breaks MAKEOBJDIRPREFIX
+ * since the value of curdir can vary depending on how we got
+ * here. Ie sitting at a shell prompt (shell that provides $PWD)
+ * or via subdir.mk in which case its likely a shell which does
+ * not provide it.
+ * So, to stop it breaking this case only, we ignore PWD if
+ * MAKEOBJDIRPREFIX is set or MAKEOBJDIR contains a transform.
+ */
+#ifndef NO_PWD_OVERRIDE
+ if (!ignorePWD) {
+ char *pwd, *ptmp1 = NULL, *ptmp2 = NULL;
+
+ if ((pwd = getenv("PWD")) != NULL &&
+ Var_Value("MAKEOBJDIRPREFIX", VAR_CMD, &ptmp1) == NULL) {
+ const char *makeobjdir = Var_Value("MAKEOBJDIR",
+ VAR_CMD, &ptmp2);
+
+ if (makeobjdir == NULL || !strchr(makeobjdir, '$')) {
+ if (stat(pwd, &sb) == 0 &&
+ sa.st_ino == sb.st_ino &&
+ sa.st_dev == sb.st_dev)
+ (void)strncpy(curdir, pwd, MAXPATHLEN);
+ }
+ }
+ free(ptmp1);
+ free(ptmp2);
+ }
+#endif
+ Var_Set(".CURDIR", curdir, VAR_GLOBAL, 0);
+
+ /*
+ * Find the .OBJDIR. If MAKEOBJDIRPREFIX, or failing that,
+ * MAKEOBJDIR is set in the environment, try only that value
+ * and fall back to .CURDIR if it does not exist.
+ *
+ * Otherwise, try _PATH_OBJDIR.MACHINE-MACHINE_ARCH, _PATH_OBJDIR.MACHINE,
+ * and * finally _PATH_OBJDIRPREFIX`pwd`, in that order. If none
+ * of these paths exist, just use .CURDIR.
+ */
+ Dir_Init(curdir);
+ (void)Main_SetObjdir("%s", curdir);
+
+ if (!Main_SetVarObjdir("MAKEOBJDIRPREFIX", curdir) &&
+ !Main_SetVarObjdir("MAKEOBJDIR", "") &&
+ !Main_SetObjdir("%s.%s-%s", _PATH_OBJDIR, machine, machine_arch) &&
+ !Main_SetObjdir("%s.%s", _PATH_OBJDIR, machine) &&
+ !Main_SetObjdir("%s", _PATH_OBJDIR))
+ (void)Main_SetObjdir("%s%s", _PATH_OBJDIRPREFIX, curdir);
+
+ /*
+ * Initialize archive, target and suffix modules in preparation for
+ * parsing the makefile(s)
+ */
+ Arch_Init();
+ Targ_Init();
+ Suff_Init();
+ Trace_Init(tracefile);
+
+ DEFAULT = NULL;
+ (void)time(&now);
+
+ Trace_Log(MAKESTART, NULL);
+
+ /*
+ * Set up the .TARGETS variable to contain the list of targets to be
+ * created. If none specified, make the variable empty -- the parser
+ * will fill the thing in with the default or .MAIN target.
+ */
+ if (!Lst_IsEmpty(create)) {
+ LstNode ln;
+
+ for (ln = Lst_First(create); ln != NULL;
+ ln = Lst_Succ(ln)) {
+ char *name = (char *)Lst_Datum(ln);
+
+ Var_Append(".TARGETS", name, VAR_GLOBAL);
+ }
+ } else
+ Var_Set(".TARGETS", "", VAR_GLOBAL, 0);
+
+
+ /*
+ * If no user-supplied system path was given (through the -m option)
+ * add the directories from the DEFSYSPATH (more than one may be given
+ * as dir1:...:dirn) to the system include path.
+ */
+ if (syspath == NULL || *syspath == '\0')
+ syspath = defsyspath;
+ else
+ syspath = bmake_strdup(syspath);
+
+ for (start = syspath; *start != '\0'; start = cp) {
+ for (cp = start; *cp != '\0' && *cp != ':'; cp++)
+ continue;
+ if (*cp == ':') {
+ *cp++ = '\0';
+ }
+ /* look for magic parent directory search string */
+ if (strncmp(".../", start, 4) != 0) {
+ (void)Dir_AddDir(defIncPath, start);
+ } else {
+ if (Dir_FindHereOrAbove(curdir, start+4,
+ found_path, sizeof(found_path))) {
+ (void)Dir_AddDir(defIncPath, found_path);
+ }
+ }
+ }
+ if (syspath != defsyspath)
+ free(syspath);
+
+ /*
+ * Read in the built-in rules first, followed by the specified
+ * makefile, if it was (makefile != NULL), or the default
+ * makefile and Makefile, in that order, if it wasn't.
+ */
+ if (!noBuiltins) {
+ LstNode ln;
+
+ sysMkPath = Lst_Init(FALSE);
+ Dir_Expand(_PATH_DEFSYSMK,
+ Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath,
+ sysMkPath);
+ if (Lst_IsEmpty(sysMkPath))
+ Fatal("%s: no system rules (%s).", progname,
+ _PATH_DEFSYSMK);
+ ln = Lst_Find(sysMkPath, NULL, ReadMakefile);
+ if (ln == NULL)
+ Fatal("%s: cannot open %s.", progname,
+ (char *)Lst_Datum(ln));
+ }
+
+ if (!Lst_IsEmpty(makefiles)) {
+ LstNode ln;
+
+ ln = Lst_Find(makefiles, NULL, ReadAllMakefiles);
+ if (ln != NULL)
+ Fatal("%s: cannot open %s.", progname,
+ (char *)Lst_Datum(ln));
+ } else {
+ p1 = Var_Subst(NULL, "${" MAKEFILE_PREFERENCE "}",
+ VAR_CMD, VARF_WANTRES);
+ if (p1) {
+ (void)str2Lst_Append(makefiles, p1, NULL);
+ (void)Lst_Find(makefiles, NULL, ReadMakefile);
+ free(p1);
+ }
+ }
+
+ /* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */
+ if (!noBuiltins || !printVars) {
+ makeDependfile = Var_Subst(NULL, "${.MAKE.DEPENDFILE:T}",
+ VAR_CMD, VARF_WANTRES);
+ doing_depend = TRUE;
+ (void)ReadMakefile(makeDependfile, NULL);
+ doing_depend = FALSE;
+ }
+
+ if (enterFlagObj)
+ printf("%s: Entering directory `%s'\n", progname, objdir);
+
+ MakeMode(NULL);
+
+ Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1), VAR_GLOBAL);
+ free(p1);
+
+ if (!forceJobs && !compatMake &&
+ Var_Exists(".MAKE.JOBS", VAR_GLOBAL)) {
+ char *value;
+ int n;
+
+ value = Var_Subst(NULL, "${.MAKE.JOBS}", VAR_GLOBAL, VARF_WANTRES);
+ n = strtol(value, NULL, 0);
+ if (n < 1) {
+ (void)fprintf(stderr, "%s: illegal value for .MAKE.JOBS -- must be positive integer!\n",
+ progname);
+ exit(1);
+ }
+ if (n != maxJobs) {
+ Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL);
+ Var_Append(MAKEFLAGS, value, VAR_GLOBAL);
+ }
+ maxJobs = n;
+ maxJobTokens = maxJobs;
+ forceJobs = TRUE;
+ free(value);
+ }
+
+ /*
+ * Be compatible if user did not specify -j and did not explicitly
+ * turned compatibility on
+ */
+ if (!compatMake && !forceJobs) {
+ compatMake = TRUE;
+ }
+
+ if (!compatMake)
+ Job_ServerStart(maxJobTokens, jp_0, jp_1);
+ if (DEBUG(JOB))
+ fprintf(debug_file, "job_pipe %d %d, maxjobs %d, tokens %d, compat %d\n",
+ jp_0, jp_1, maxJobs, maxJobTokens, compatMake);
+
+ if (!printVars)
+ Main_ExportMAKEFLAGS(TRUE); /* initial export */
+
+
+ /*
+ * For compatibility, look at the directories in the VPATH variable
+ * and add them to the search path, if the variable is defined. The
+ * variable's value is in the same format as the PATH envariable, i.e.
+ * <directory>:<directory>:<directory>...
+ */
+ if (Var_Exists("VPATH", VAR_CMD)) {
+ char *vpath, savec;
+ /*
+ * GCC stores string constants in read-only memory, but
+ * Var_Subst will want to write this thing, so store it
+ * in an array
+ */
+ static char VPATH[] = "${VPATH}";
+
+ vpath = Var_Subst(NULL, VPATH, VAR_CMD, VARF_WANTRES);
+ path = vpath;
+ do {
+ /* skip to end of directory */
+ for (cp = path; *cp != ':' && *cp != '\0'; cp++)
+ continue;
+ /* Save terminator character so know when to stop */
+ savec = *cp;
+ *cp = '\0';
+ /* Add directory to search path */
+ (void)Dir_AddDir(dirSearchPath, path);
+ *cp = savec;
+ path = cp + 1;
+ } while (savec == ':');
+ free(vpath);
+ }
+
+ /*
+ * Now that all search paths have been read for suffixes et al, it's
+ * time to add the default search path to their lists...
+ */
+ Suff_DoPaths();
+
+ /*
+ * Propagate attributes through :: dependency lists.
+ */
+ Targ_Propagate();
+
+ /* print the initial graph, if the user requested it */
+ if (DEBUG(GRAPH1))
+ Targ_PrintGraph(1);
+
+ /* print the values of any variables requested by the user */
+ if (printVars) {
+ doPrintVars();
+ outOfDate = FALSE;
+ } else {
+ outOfDate = runTargets();
+ }
+
+#ifdef CLEANUP
+ Lst_Destroy(variables, NULL);
+ Lst_Destroy(makefiles, NULL);
+ Lst_Destroy(create, (FreeProc *)free);
+#endif
+
+ /* print the graph now it's been processed if the user requested it */
+ if (DEBUG(GRAPH2))
+ Targ_PrintGraph(2);
+
+ Trace_Log(MAKEEND, 0);
+
+ if (enterFlagObj)
+ printf("%s: Leaving directory `%s'\n", progname, objdir);
+ if (enterFlag)
+ printf("%s: Leaving directory `%s'\n", progname, curdir);
+
+#ifdef USE_META
+ meta_finish();
+#endif
+ Suff_End();
+ Targ_End();
+ Arch_End();
+ Var_End();
+ Parse_End();
+ Dir_End();
+ Job_End();
+ Trace_End();
+
+ return outOfDate ? 1 : 0;
+}
+
+/*-
+ * ReadMakefile --
+ * Open and parse the given makefile.
+ *
+ * Results:
+ * 0 if ok. -1 if couldn't open file.
+ *
+ * Side Effects:
+ * lots
+ */
+static int
+ReadMakefile(const void *p, const void *q MAKE_ATTR_UNUSED)
+{
+ const char *fname = p; /* makefile to read */
+ int fd;
+ size_t len = MAXPATHLEN;
+ char *name, *path = bmake_malloc(len);
+
+ if (!strcmp(fname, "-")) {
+ Parse_File(NULL /*stdin*/, -1);
+ Var_Set("MAKEFILE", "", VAR_INTERNAL, 0);
+ } else {
+ /* if we've chdir'd, rebuild the path name */
+ if (strcmp(curdir, objdir) && *fname != '/') {
+ size_t plen = strlen(curdir) + strlen(fname) + 2;
+ if (len < plen)
+ path = bmake_realloc(path, len = 2 * plen);
+
+ (void)snprintf(path, len, "%s/%s", curdir, fname);
+ fd = open(path, O_RDONLY);
+ if (fd != -1) {
+ fname = path;
+ goto found;
+ }
+
+ /* If curdir failed, try objdir (ala .depend) */
+ plen = strlen(objdir) + strlen(fname) + 2;
+ if (len < plen)
+ path = bmake_realloc(path, len = 2 * plen);
+ (void)snprintf(path, len, "%s/%s", objdir, fname);
+ fd = open(path, O_RDONLY);
+ if (fd != -1) {
+ fname = path;
+ goto found;
+ }
+ } else {
+ fd = open(fname, O_RDONLY);
+ if (fd != -1)
+ goto found;
+ }
+ /* look in -I and system include directories. */
+ name = Dir_FindFile(fname, parseIncPath);
+ if (!name)
+ name = Dir_FindFile(fname,
+ Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath);
+ if (!name || (fd = open(name, O_RDONLY)) == -1) {
+ free(name);
+ free(path);
+ return(-1);
+ }
+ fname = name;
+ /*
+ * set the MAKEFILE variable desired by System V fans -- the
+ * placement of the setting here means it gets set to the last
+ * makefile specified, as it is set by SysV make.
+ */
+found:
+ if (!doing_depend)
+ Var_Set("MAKEFILE", fname, VAR_INTERNAL, 0);
+ Parse_File(fname, fd);
+ }
+ free(path);
+ return(0);
+}
+
+
+
+/*-
+ * Cmd_Exec --
+ * Execute the command in cmd, and return the output of that command
+ * in a string.
+ *
+ * Results:
+ * A string containing the output of the command, or the empty string
+ * If errnum is not NULL, it contains the reason for the command failure
+ *
+ * Side Effects:
+ * The string must be freed by the caller.
+ */
+char *
+Cmd_Exec(const char *cmd, const char **errnum)
+{
+ const char *args[4]; /* Args for invoking the shell */
+ int fds[2]; /* Pipe streams */
+ int cpid; /* Child PID */
+ int pid; /* PID from wait() */
+ char *res; /* result */
+ int status; /* command exit status */
+ Buffer buf; /* buffer to store the result */
+ char *cp;
+ int cc; /* bytes read, or -1 */
+ int savederr; /* saved errno */
+
+
+ *errnum = NULL;
+
+ if (!shellName)
+ Shell_Init();
+ /*
+ * Set up arguments for shell
+ */
+ args[0] = shellName;
+ args[1] = "-c";
+ args[2] = cmd;
+ args[3] = NULL;
+
+ /*
+ * Open a pipe for fetching its output
+ */
+ if (pipe(fds) == -1) {
+ *errnum = "Couldn't create pipe for \"%s\"";
+ goto bad;
+ }
+
+ /*
+ * Fork
+ */
+ switch (cpid = vFork()) {
+ case 0:
+ /*
+ * Close input side of pipe
+ */
+ (void)close(fds[0]);
+
+ /*
+ * Duplicate the output stream to the shell's output, then
+ * shut the extra thing down. Note we don't fetch the error
+ * stream...why not? Why?
+ */
+ (void)dup2(fds[1], 1);
+ (void)close(fds[1]);
+
+ Var_ExportVars();
+
+ (void)execv(shellPath, UNCONST(args));
+ _exit(1);
+ /*NOTREACHED*/
+
+ case -1:
+ *errnum = "Couldn't exec \"%s\"";
+ goto bad;
+
+ default:
+ /*
+ * No need for the writing half
+ */
+ (void)close(fds[1]);
+
+ savederr = 0;
+ Buf_Init(&buf, 0);
+
+ do {
+ char result[BUFSIZ];
+ cc = read(fds[0], result, sizeof(result));
+ if (cc > 0)
+ Buf_AddBytes(&buf, cc, result);
+ }
+ while (cc > 0 || (cc == -1 && errno == EINTR));
+ if (cc == -1)
+ savederr = errno;
+
+ /*
+ * Close the input side of the pipe.
+ */
+ (void)close(fds[0]);
+
+ /*
+ * Wait for the process to exit.
+ */
+ while(((pid = waitpid(cpid, &status, 0)) != cpid) && (pid >= 0)) {
+ JobReapChild(pid, status, FALSE);
+ continue;
+ }
+ cc = Buf_Size(&buf);
+ res = Buf_Destroy(&buf, FALSE);
+
+ if (savederr != 0)
+ *errnum = "Couldn't read shell's output for \"%s\"";
+
+ if (WIFSIGNALED(status))
+ *errnum = "\"%s\" exited on a signal";
+ else if (WEXITSTATUS(status) != 0)
+ *errnum = "\"%s\" returned non-zero status";
+
+ /*
+ * Null-terminate the result, convert newlines to spaces and
+ * install it in the variable.
+ */
+ res[cc] = '\0';
+ cp = &res[cc];
+
+ if (cc > 0 && *--cp == '\n') {
+ /*
+ * A final newline is just stripped
+ */
+ *cp-- = '\0';
+ }
+ while (cp >= res) {
+ if (*cp == '\n') {
+ *cp = ' ';
+ }
+ cp--;
+ }
+ break;
+ }
+ return res;
+bad:
+ res = bmake_malloc(1);
+ *res = '\0';
+ return res;
+}
+
+/*-
+ * Error --
+ * Print an error message given its format.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The message is printed.
+ */
+/* VARARGS */
+void
+Error(const char *fmt, ...)
+{
+ va_list ap;
+ FILE *err_file;
+
+ err_file = debug_file;
+ if (err_file == stdout)
+ err_file = stderr;
+ (void)fflush(stdout);
+ for (;;) {
+ va_start(ap, fmt);
+ fprintf(err_file, "%s: ", progname);
+ (void)vfprintf(err_file, fmt, ap);
+ va_end(ap);
+ (void)fprintf(err_file, "\n");
+ (void)fflush(err_file);
+ if (err_file == stderr)
+ break;
+ err_file = stderr;
+ }
+}
+
+/*-
+ * Fatal --
+ * Produce a Fatal error message. If jobs are running, waits for them
+ * to finish.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The program exits
+ */
+/* VARARGS */
+void
+Fatal(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (jobsRunning)
+ Job_Wait();
+
+ (void)fflush(stdout);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ (void)fprintf(stderr, "\n");
+ (void)fflush(stderr);
+
+ PrintOnError(NULL, NULL);
+
+ if (DEBUG(GRAPH2) || DEBUG(GRAPH3))
+ Targ_PrintGraph(2);
+ Trace_Log(MAKEERROR, 0);
+ exit(2); /* Not 1 so -q can distinguish error */
+}
+
+/*
+ * Punt --
+ * Major exception once jobs are being created. Kills all jobs, prints
+ * a message and exits.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * All children are killed indiscriminately and the program Lib_Exits
+ */
+/* VARARGS */
+void
+Punt(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)fflush(stdout);
+ (void)fprintf(stderr, "%s: ", progname);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ (void)fprintf(stderr, "\n");
+ (void)fflush(stderr);
+
+ PrintOnError(NULL, NULL);
+
+ DieHorribly();
+}
+
+/*-
+ * DieHorribly --
+ * Exit without giving a message.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * A big one...
+ */
+void
+DieHorribly(void)
+{
+ if (jobsRunning)
+ Job_AbortAll();
+ if (DEBUG(GRAPH2))
+ Targ_PrintGraph(2);
+ Trace_Log(MAKEERROR, 0);
+ exit(2); /* Not 1, so -q can distinguish error */
+}
+
+/*
+ * Finish --
+ * Called when aborting due to errors in child shell to signal
+ * abnormal exit.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The program exits
+ */
+void
+Finish(int errors)
+ /* number of errors encountered in Make_Make */
+{
+ Fatal("%d error%s", errors, errors == 1 ? "" : "s");
+}
+
+/*
+ * eunlink --
+ * Remove a file carefully, avoiding directories.
+ */
+int
+eunlink(const char *file)
+{
+ struct stat st;
+
+ if (lstat(file, &st) == -1)
+ return -1;
+
+ if (S_ISDIR(st.st_mode)) {
+ errno = EISDIR;
+ return -1;
+ }
+ return unlink(file);
+}
+
+/*
+ * execError --
+ * Print why exec failed, avoiding stdio.
+ */
+void
+execError(const char *af, const char *av)
+{
+#ifdef USE_IOVEC
+ int i = 0;
+ struct iovec iov[8];
+#define IOADD(s) \
+ (void)(iov[i].iov_base = UNCONST(s), \
+ iov[i].iov_len = strlen(iov[i].iov_base), \
+ i++)
+#else
+#define IOADD(void)write(2, s, strlen(s))
+#endif
+
+ IOADD(progname);
+ IOADD(": ");
+ IOADD(af);
+ IOADD("(");
+ IOADD(av);
+ IOADD(") failed (");
+ IOADD(strerror(errno));
+ IOADD(")\n");
+
+#ifdef USE_IOVEC
+ while (writev(2, iov, 8) == -1 && errno == EAGAIN)
+ continue;
+#endif
+}
+
+/*
+ * usage --
+ * exit with usage message
+ */
+static void
+usage(void)
+{
+ char *p;
+ if ((p = strchr(progname, '[')) != NULL)
+ *p = '\0';
+
+ (void)fprintf(stderr,
+"usage: %s [-BeikNnqrstWwX] \n\
+ [-C directory] [-D variable] [-d flags] [-f makefile]\n\
+ [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]\n\
+ [-V variable] [-v variable] [variable=value] [target ...]\n",
+ progname);
+ exit(2);
+}
+
+/*
+ * realpath(3) can get expensive, cache results...
+ */
+static GNode *cached_realpaths = NULL;
+
+static GNode *
+get_cached_realpaths(void)
+{
+
+ if (!cached_realpaths) {
+ cached_realpaths = Targ_NewGN("Realpath");
+#ifndef DEBUG_REALPATH_CACHE
+ cached_realpaths->flags = INTERNAL;
+#endif
+ }
+
+ return cached_realpaths;
+}
+
+/* purge any relative paths */
+static void
+purge_cached_realpaths(void)
+{
+ GNode *cache = get_cached_realpaths();
+ Hash_Entry *he, *nhe;
+ Hash_Search hs;
+
+ he = Hash_EnumFirst(&cache->context, &hs);
+ while (he) {
+ nhe = Hash_EnumNext(&hs);
+ if (he->name[0] != '/') {
+ if (DEBUG(DIR))
+ fprintf(stderr, "cached_realpath: purging %s\n", he->name);
+ Hash_DeleteEntry(&cache->context, he);
+ }
+ he = nhe;
+ }
+}
+
+char *
+cached_realpath(const char *pathname, char *resolved)
+{
+ GNode *cache;
+ char *rp, *cp;
+
+ if (!pathname || !pathname[0])
+ return NULL;
+
+ cache = get_cached_realpaths();
+
+ if ((rp = Var_Value(pathname, cache, &cp)) != NULL) {
+ /* a hit */
+ strncpy(resolved, rp, MAXPATHLEN);
+ resolved[MAXPATHLEN - 1] = '\0';
+ } else if ((rp = realpath(pathname, resolved)) != NULL) {
+ Var_Set(pathname, rp, cache, 0);
+ } /* else should we negative-cache? */
+
+ free(cp);
+ return rp ? resolved : NULL;
+}
+
+int
+PrintAddr(void *a, void *b)
+{
+ printf("%lx ", (unsigned long) a);
+ return b ? 0 : 0;
+}
+
+
+static int
+addErrorCMD(void *cmdp, void *gnp)
+{
+ if (cmdp == NULL)
+ return 1; /* stop */
+ Var_Append(".ERROR_CMD", cmdp, VAR_GLOBAL);
+ return 0;
+}
+
+void
+PrintOnError(GNode *gn, const char *s)
+{
+ static GNode *en = NULL;
+ char tmp[64];
+ char *cp;
+
+ if (s)
+ printf("%s", s);
+
+ printf("\n%s: stopped in %s\n", progname, curdir);
+
+ if (en)
+ return; /* we've been here! */
+ if (gn) {
+ /*
+ * We can print this even if there is no .ERROR target.
+ */
+ Var_Set(".ERROR_TARGET", gn->name, VAR_GLOBAL, 0);
+ Var_Delete(".ERROR_CMD", VAR_GLOBAL);
+ Lst_ForEach(gn->commands, addErrorCMD, gn);
+ }
+ strncpy(tmp, "${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}",
+ sizeof(tmp) - 1);
+ cp = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES);
+ if (cp) {
+ if (*cp)
+ printf("%s", cp);
+ free(cp);
+ }
+ fflush(stdout);
+
+ /*
+ * Finally, see if there is a .ERROR target, and run it if so.
+ */
+ en = Targ_FindNode(".ERROR", TARG_NOCREATE);
+ if (en) {
+ en->type |= OP_SPECIAL;
+ Compat_Make(en, en);
+ }
+}
+
+void
+Main_ExportMAKEFLAGS(Boolean first)
+{
+ static int once = 1;
+ char tmp[64];
+ char *s;
+
+ if (once != first)
+ return;
+ once = 0;
+
+ strncpy(tmp, "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}",
+ sizeof(tmp));
+ s = Var_Subst(NULL, tmp, VAR_CMD, VARF_WANTRES);
+ if (s && *s) {
+#ifdef POSIX
+ setenv("MAKEFLAGS", s, 1);
+#else
+ setenv("MAKE", s, 1);
+#endif
+ }
+}
+
+char *
+getTmpdir(void)
+{
+ static char *tmpdir = NULL;
+
+ if (!tmpdir) {
+ struct stat st;
+
+ /*
+ * Honor $TMPDIR but only if it is valid.
+ * Ensure it ends with /.
+ */
+ tmpdir = Var_Subst(NULL, "${TMPDIR:tA:U" _PATH_TMP "}/", VAR_GLOBAL,
+ VARF_WANTRES);
+ if (stat(tmpdir, &st) < 0 || !S_ISDIR(st.st_mode)) {
+ free(tmpdir);
+ tmpdir = bmake_strdup(_PATH_TMP);
+ }
+ }
+ return tmpdir;
+}
+
+/*
+ * Create and open a temp file using "pattern".
+ * If "fnamep" is provided set it to a copy of the filename created.
+ * Otherwise unlink the file once open.
+ */
+int
+mkTempFile(const char *pattern, char **fnamep)
+{
+ static char *tmpdir = NULL;
+ char tfile[MAXPATHLEN];
+ int fd;
+
+ if (!pattern)
+ pattern = TMPPAT;
+ if (!tmpdir)
+ tmpdir = getTmpdir();
+ if (pattern[0] == '/') {
+ snprintf(tfile, sizeof(tfile), "%s", pattern);
+ } else {
+ snprintf(tfile, sizeof(tfile), "%s%s", tmpdir, pattern);
+ }
+ if ((fd = mkstemp(tfile)) < 0)
+ Punt("Could not create temporary file %s: %s", tfile, strerror(errno));
+ if (fnamep) {
+ *fnamep = bmake_strdup(tfile);
+ } else {
+ unlink(tfile); /* we just want the descriptor */
+ }
+ return fd;
+}
+
+/*
+ * Convert a string representation of a boolean.
+ * Anything that looks like "No", "False", "Off", "0" etc,
+ * is FALSE, otherwise TRUE.
+ */
+Boolean
+s2Boolean(const char *s, Boolean bf)
+{
+ if (s) {
+ switch(*s) {
+ case '\0': /* not set - the default wins */
+ break;
+ case '0':
+ case 'F':
+ case 'f':
+ case 'N':
+ case 'n':
+ bf = FALSE;
+ break;
+ case 'O':
+ case 'o':
+ switch (s[1]) {
+ case 'F':
+ case 'f':
+ bf = FALSE;
+ break;
+ default:
+ bf = TRUE;
+ break;
+ }
+ break;
+ default:
+ bf = TRUE;
+ break;
+ }
+ }
+ return (bf);
+}
+
+/*
+ * Return a Boolean based on setting of a knob.
+ *
+ * If the knob is not set, the supplied default is the return value.
+ * If set, anything that looks or smells like "No", "False", "Off", "0" etc,
+ * is FALSE, otherwise TRUE.
+ */
+Boolean
+getBoolean(const char *name, Boolean bf)
+{
+ char tmp[64];
+ char *cp;
+
+ if (snprintf(tmp, sizeof(tmp), "${%s:U:tl}", name) < (int)(sizeof(tmp))) {
+ cp = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES);
+
+ if (cp) {
+ bf = s2Boolean(cp, bf);
+ free(cp);
+ }
+ }
+ return (bf);
+}
diff --git a/usr.bin/make/make.1 b/usr.bin/make/make.1
new file mode 100644
index 0000000..866631c
--- /dev/null
+++ b/usr.bin/make/make.1
@@ -0,0 +1,2413 @@
+.\" $NetBSD: make.1,v 1.273 2018/05/27 01:14:51 christos Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
+.\"
+.Dd May 26, 2018
+.Dt MAKE 1
+.Os
+.Sh NAME
+.Nm make
+.Nd maintain program dependencies
+.Sh SYNOPSIS
+.Nm
+.Op Fl BeikNnqrstWwX
+.Op Fl C Ar directory
+.Op Fl D Ar variable
+.Op Fl d Ar flags
+.Op Fl f Ar makefile
+.Op Fl I Ar directory
+.Op Fl J Ar private
+.Op Fl j Ar max_jobs
+.Op Fl m Ar directory
+.Op Fl T Ar file
+.Op Fl V Ar variable
+.Op Fl v Ar variable
+.Op Ar variable=value
+.Op Ar target ...
+.Sh DESCRIPTION
+.Nm
+is a program designed to simplify the maintenance of other programs.
+Its input is a list of specifications as to the files upon which programs
+and other files depend.
+If no
+.Fl f Ar makefile
+makefile option is given,
+.Nm
+will try to open
+.Ql Pa makefile
+then
+.Ql Pa Makefile
+in order to find the specifications.
+If the file
+.Ql Pa .depend
+exists, it is read (see
+.Xr mkdep 1 ) .
+.Pp
+This manual page is intended as a reference document only.
+For a more thorough description of
+.Nm
+and makefiles, please refer to
+.%T "PMake \- A Tutorial" .
+.Pp
+.Nm
+will prepend the contents of the
+.Va MAKEFLAGS
+environment variable to the command line arguments before parsing them.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl B
+Try to be backwards compatible by executing a single shell per command and
+by executing the commands to make the sources of a dependency line in sequence.
+.It Fl C Ar directory
+Change to
+.Ar directory
+before reading the makefiles or doing anything else.
+If multiple
+.Fl C
+options are specified, each is interpreted relative to the previous one:
+.Fl C Pa / Fl C Pa etc
+is equivalent to
+.Fl C Pa /etc .
+.It Fl D Ar variable
+Define
+.Ar variable
+to be 1, in the global context.
+.It Fl d Ar [-]flags
+Turn on debugging, and specify which portions of
+.Nm
+are to print debugging information.
+Unless the flags are preceded by
+.Ql \-
+they are added to the
+.Va MAKEFLAGS
+environment variable and will be processed by any child make processes.
+By default, debugging information is printed to standard error,
+but this can be changed using the
+.Ar F
+debugging flag.
+The debugging output is always unbuffered; in addition, if debugging
+is enabled but debugging output is not directed to standard output,
+then the standard output is line buffered.
+.Ar Flags
+is one or more of the following:
+.Bl -tag -width Ds
+.It Ar A
+Print all possible debugging information;
+equivalent to specifying all of the debugging flags.
+.It Ar a
+Print debugging information about archive searching and caching.
+.It Ar C
+Print debugging information about current working directory.
+.It Ar c
+Print debugging information about conditional evaluation.
+.It Ar d
+Print debugging information about directory searching and caching.
+.It Ar e
+Print debugging information about failed commands and targets.
+.It Ar F Ns Oo Sy \&+ Oc Ns Ar filename
+Specify where debugging output is written.
+This must be the last flag, because it consumes the remainder of
+the argument.
+If the character immediately after the
+.Ql F
+flag is
+.Ql \&+ ,
+then the file will be opened in append mode;
+otherwise the file will be overwritten.
+If the file name is
+.Ql stdout
+or
+.Ql stderr
+then debugging output will be written to the
+standard output or standard error output file descriptors respectively
+(and the
+.Ql \&+
+option has no effect).
+Otherwise, the output will be written to the named file.
+If the file name ends
+.Ql .%d
+then the
+.Ql %d
+is replaced by the pid.
+.It Ar f
+Print debugging information about loop evaluation.
+.It Ar "g1"
+Print the input graph before making anything.
+.It Ar "g2"
+Print the input graph after making everything, or before exiting
+on error.
+.It Ar "g3"
+Print the input graph before exiting on error.
+.It Ar j
+Print debugging information about running multiple shells.
+.It Ar l
+Print commands in Makefiles regardless of whether or not they are prefixed by
+.Ql @
+or other "quiet" flags.
+Also known as "loud" behavior.
+.It Ar M
+Print debugging information about "meta" mode decisions about targets.
+.It Ar m
+Print debugging information about making targets, including modification
+dates.
+.It Ar n
+Don't delete the temporary command scripts created when running commands.
+These temporary scripts are created in the directory
+referred to by the
+.Ev TMPDIR
+environment variable, or in
+.Pa /tmp
+if
+.Ev TMPDIR
+is unset or set to the empty string.
+The temporary scripts are created by
+.Xr mkstemp 3 ,
+and have names of the form
+.Pa makeXXXXXX .
+.Em NOTE :
+This can create many files in
+.Ev TMPDIR
+or
+.Pa /tmp ,
+so use with care.
+.It Ar p
+Print debugging information about makefile parsing.
+.It Ar s
+Print debugging information about suffix-transformation rules.
+.It Ar t
+Print debugging information about target list maintenance.
+.It Ar V
+Force the
+.Fl V
+option to print raw values of variables, overriding the default behavior
+set via
+.Va .MAKE.EXPAND_VARIABLES .
+.It Ar v
+Print debugging information about variable assignment.
+.It Ar x
+Run shell commands with
+.Fl x
+so the actual commands are printed as they are executed.
+.El
+.It Fl e
+Specify that environment variables override macro assignments within
+makefiles.
+.It Fl f Ar makefile
+Specify a makefile to read instead of the default
+.Ql Pa makefile .
+If
+.Ar makefile
+is
+.Ql Fl ,
+standard input is read.
+Multiple makefiles may be specified, and are read in the order specified.
+.It Fl I Ar directory
+Specify a directory in which to search for makefiles and included makefiles.
+The system makefile directory (or directories, see the
+.Fl m
+option) is automatically included as part of this list.
+.It Fl i
+Ignore non-zero exit of shell commands in the makefile.
+Equivalent to specifying
+.Ql Fl
+before each command line in the makefile.
+.It Fl J Ar private
+This option should
+.Em not
+be specified by the user.
+.Pp
+When the
+.Ar j
+option is in use in a recursive build, this option is passed by a make
+to child makes to allow all the make processes in the build to
+cooperate to avoid overloading the system.
+.It Fl j Ar max_jobs
+Specify the maximum number of jobs that
+.Nm
+may have running at any one time.
+The value is saved in
+.Va .MAKE.JOBS .
+Turns compatibility mode off, unless the
+.Ar B
+flag is also specified.
+When compatibility mode is off, all commands associated with a
+target are executed in a single shell invocation as opposed to the
+traditional one shell invocation per line.
+This can break traditional scripts which change directories on each
+command invocation and then expect to start with a fresh environment
+on the next line.
+It is more efficient to correct the scripts rather than turn backwards
+compatibility on.
+.It Fl k
+Continue processing after errors are encountered, but only on those targets
+that do not depend on the target whose creation caused the error.
+.It Fl m Ar directory
+Specify a directory in which to search for sys.mk and makefiles included
+via the
+.Ao Ar file Ac Ns -style
+include statement.
+The
+.Fl m
+option can be used multiple times to form a search path.
+This path will override the default system include path: /usr/share/mk.
+Furthermore the system include path will be appended to the search path used
+for
+.Qo Ar file Qc Ns -style
+include statements (see the
+.Fl I
+option).
+.Pp
+If a file or directory name in the
+.Fl m
+argument (or the
+.Ev MAKESYSPATH
+environment variable) starts with the string
+.Qq \&.../
+then
+.Nm
+will search for the specified file or directory named in the remaining part
+of the argument string.
+The search starts with the current directory of
+the Makefile and then works upward towards the root of the file system.
+If the search is successful, then the resulting directory replaces the
+.Qq \&.../
+specification in the
+.Fl m
+argument.
+If used, this feature allows
+.Nm
+to easily search in the current source tree for customized sys.mk files
+(e.g., by using
+.Qq \&.../mk/sys.mk
+as an argument).
+.It Fl n
+Display the commands that would have been executed, but do not
+actually execute them unless the target depends on the .MAKE special
+source (see below).
+.It Fl N
+Display the commands which would have been executed, but do not
+actually execute any of them; useful for debugging top-level makefiles
+without descending into subdirectories.
+.It Fl q
+Do not execute any commands, but exit 0 if the specified targets are
+up-to-date and 1, otherwise.
+.It Fl r
+Do not use the built-in rules specified in the system makefile.
+.It Fl s
+Do not echo any commands as they are executed.
+Equivalent to specifying
+.Ql Ic @
+before each command line in the makefile.
+.It Fl T Ar tracefile
+When used with the
+.Fl j
+flag,
+append a trace record to
+.Ar tracefile
+for each job started and completed.
+.It Fl t
+Rather than re-building a target as specified in the makefile, create it
+or update its modification time to make it appear up-to-date.
+.It Fl V Ar variable
+Print the value of
+.Ar variable .
+Do not build any targets.
+Multiple instances of this option may be specified;
+the variables will be printed one per line,
+with a blank line for each null or undefined variable.
+The value printed is extracted from the global context after all
+makefiles have been read.
+By default, the raw variable contents (which may
+include additional unexpanded variable references) are shown.
+If
+.Ar variable
+contains a
+.Ql \&$
+then the value will be recursively expanded to its complete resultant
+text before printing.
+The expanded value will also be printed if
+.Va .MAKE.EXPAND_VARIABLES
+is set to true and
+the
+.Fl dV
+option has not been used to override it.
+Note that loop-local and target-local variables, as well as values
+taken temporarily by global variables during makefile processing, are
+not accessible via this option.
+The
+.Fl dv
+debug mode can be used to see these at the cost of generating
+substantial extraneous output.
+.It Fl v Ar variable
+Like
+.Fl V
+but the variable is always expanded to its complete value.
+.It Fl W
+Treat any warnings during makefile parsing as errors.
+.It Fl w
+Print entering and leaving directory messages, pre and post processing.
+.It Fl X
+Don't export variables passed on the command line to the environment
+individually.
+Variables passed on the command line are still exported
+via the
+.Va MAKEFLAGS
+environment variable.
+This option may be useful on systems which have a small limit on the
+size of command arguments.
+.It Ar variable=value
+Set the value of the variable
+.Ar variable
+to
+.Ar value .
+Normally, all values passed on the command line are also exported to
+sub-makes in the environment.
+The
+.Fl X
+flag disables this behavior.
+Variable assignments should follow options for POSIX compatibility
+but no ordering is enforced.
+.El
+.Pp
+There are seven different types of lines in a makefile: file dependency
+specifications, shell commands, variable assignments, include statements,
+conditional directives, for loops, and comments.
+.Pp
+In general, lines may be continued from one line to the next by ending
+them with a backslash
+.Pq Ql \e .
+The trailing newline character and initial whitespace on the following
+line are compressed into a single space.
+.Sh FILE DEPENDENCY SPECIFICATIONS
+Dependency lines consist of one or more targets, an operator, and zero
+or more sources.
+This creates a relationship where the targets
+.Dq depend
+on the sources
+and are usually created from them.
+The exact relationship between the target and the source is determined
+by the operator that separates them.
+The three operators are as follows:
+.Bl -tag -width flag
+.It Ic \&:
+A target is considered out-of-date if its modification time is less than
+those of any of its sources.
+Sources for a target accumulate over dependency lines when this operator
+is used.
+The target is removed if
+.Nm
+is interrupted.
+.It Ic \&!
+Targets are always re-created, but not until all sources have been
+examined and re-created as necessary.
+Sources for a target accumulate over dependency lines when this operator
+is used.
+The target is removed if
+.Nm
+is interrupted.
+.It Ic \&::
+If no sources are specified, the target is always re-created.
+Otherwise, a target is considered out-of-date if any of its sources has
+been modified more recently than the target.
+Sources for a target do not accumulate over dependency lines when this
+operator is used.
+The target will not be removed if
+.Nm
+is interrupted.
+.El
+.Pp
+Targets and sources may contain the shell wildcard values
+.Ql \&? ,
+.Ql * ,
+.Ql [] ,
+and
+.Ql {} .
+The values
+.Ql \&? ,
+.Ql * ,
+and
+.Ql []
+may only be used as part of the final
+component of the target or source, and must be used to describe existing
+files.
+The value
+.Ql {}
+need not necessarily be used to describe existing files.
+Expansion is in directory order, not alphabetically as done in the shell.
+.Sh SHELL COMMANDS
+Each target may have associated with it one or more lines of shell
+commands, normally
+used to create the target.
+Each of the lines in this script
+.Em must
+be preceded by a tab.
+(For historical reasons, spaces are not accepted.)
+While targets can appear in many dependency lines if desired, by
+default only one of these rules may be followed by a creation
+script.
+If the
+.Ql Ic \&::
+operator is used, however, all rules may include scripts and the
+scripts are executed in the order found.
+.Pp
+Each line is treated as a separate shell command, unless the end of
+line is escaped with a backslash
+.Pq Ql \e
+in which case that line and the next are combined.
+.\" The escaped newline is retained and passed to the shell, which
+.\" normally ignores it.
+.\" However, the tab at the beginning of the following line is removed.
+If the first characters of the command are any combination of
+.Ql Ic @ ,
+.Ql Ic + ,
+or
+.Ql Ic \- ,
+the command is treated specially.
+A
+.Ql Ic @
+causes the command not to be echoed before it is executed.
+A
+.Ql Ic +
+causes the command to be executed even when
+.Fl n
+is given.
+This is similar to the effect of the .MAKE special source,
+except that the effect can be limited to a single line of a script.
+A
+.Ql Ic \-
+in compatibility mode
+causes any non-zero exit status of the command line to be ignored.
+.Pp
+When
+.Nm
+is run in jobs mode with
+.Fl j Ar max_jobs ,
+the entire script for the target is fed to a
+single instance of the shell.
+In compatibility (non-jobs) mode, each command is run in a separate process.
+If the command contains any shell meta characters
+.Pq Ql #=|^(){};&<>*?[]:$`\e\en
+it will be passed to the shell; otherwise
+.Nm
+will attempt direct execution.
+If a line starts with
+.Ql Ic \-
+and the shell has ErrCtl enabled then failure of the command line
+will be ignored as in compatibility mode.
+Otherwise
+.Ql Ic \-
+affects the entire job;
+the script will stop at the first command line that fails,
+but the target will not be deemed to have failed.
+.Pp
+Makefiles should be written so that the mode of
+.Nm
+operation does not change their behavior.
+For example, any command which needs to use
+.Dq cd
+or
+.Dq chdir
+without potentially changing the directory for subsequent commands
+should be put in parentheses so it executes in a subshell.
+To force the use of one shell, escape the line breaks so as to make
+the whole script one command.
+For example:
+.Bd -literal -offset indent
+avoid-chdir-side-effects:
+ @echo Building $@ in `pwd`
+ @(cd ${.CURDIR} && ${MAKE} $@)
+ @echo Back in `pwd`
+
+ensure-one-shell-regardless-of-mode:
+ @echo Building $@ in `pwd`; \e
+ (cd ${.CURDIR} && ${MAKE} $@); \e
+ echo Back in `pwd`
+.Ed
+.Pp
+Since
+.Nm
+will
+.Xr chdir 2
+to
+.Ql Va .OBJDIR
+before executing any targets, each child process
+starts with that as its current working directory.
+.Sh VARIABLE ASSIGNMENTS
+Variables in make are much like variables in the shell, and, by tradition,
+consist of all upper-case letters.
+.Ss Variable assignment modifiers
+The five operators that can be used to assign values to variables are as
+follows:
+.Bl -tag -width Ds
+.It Ic \&=
+Assign the value to the variable.
+Any previous value is overridden.
+.It Ic \&+=
+Append the value to the current value of the variable.
+.It Ic \&?=
+Assign the value to the variable if it is not already defined.
+.It Ic \&:=
+Assign with expansion, i.e. expand the value before assigning it
+to the variable.
+Normally, expansion is not done until the variable is referenced.
+.Em NOTE :
+References to undefined variables are
+.Em not
+expanded.
+This can cause problems when variable modifiers are used.
+.It Ic \&!=
+Expand the value and pass it to the shell for execution and assign
+the result to the variable.
+Any newlines in the result are replaced with spaces.
+.El
+.Pp
+Any white-space before the assigned
+.Ar value
+is removed; if the value is being appended, a single space is inserted
+between the previous contents of the variable and the appended value.
+.Pp
+Variables are expanded by surrounding the variable name with either
+curly braces
+.Pq Ql {}
+or parentheses
+.Pq Ql ()
+and preceding it with
+a dollar sign
+.Pq Ql \&$ .
+If the variable name contains only a single letter, the surrounding
+braces or parentheses are not required.
+This shorter form is not recommended.
+.Pp
+If the variable name contains a dollar, then the name itself is expanded first.
+This allows almost arbitrary variable names, however names containing dollar,
+braces, parenthesis, or whitespace are really best avoided!
+.Pp
+If the result of expanding a variable contains a dollar sign
+.Pq Ql \&$
+the string is expanded again.
+.Pp
+Variable substitution occurs at three distinct times, depending on where
+the variable is being used.
+.Bl -enum
+.It
+Variables in dependency lines are expanded as the line is read.
+.It
+Variables in shell commands are expanded when the shell command is
+executed.
+.It
+.Dq .for
+loop index variables are expanded on each loop iteration.
+Note that other variables are not expanded inside loops so
+the following example code:
+.Bd -literal -offset indent
+
+.Dv .for i in 1 2 3
+a+= ${i}
+j= ${i}
+b+= ${j}
+.Dv .endfor
+
+all:
+ @echo ${a}
+ @echo ${b}
+
+.Ed
+will print:
+.Bd -literal -offset indent
+1 2 3
+3 3 3
+
+.Ed
+Because while ${a} contains
+.Dq 1 2 3
+after the loop is executed, ${b}
+contains
+.Dq ${j} ${j} ${j}
+which expands to
+.Dq 3 3 3
+since after the loop completes ${j} contains
+.Dq 3 .
+.El
+.Ss Variable classes
+The four different classes of variables (in order of increasing precedence)
+are:
+.Bl -tag -width Ds
+.It Environment variables
+Variables defined as part of
+.Nm Ns 's
+environment.
+.It Global variables
+Variables defined in the makefile or in included makefiles.
+.It Command line variables
+Variables defined as part of the command line.
+.It Local variables
+Variables that are defined specific to a certain target.
+.El
+.Pp
+Local variables are all built in and their values vary magically from
+target to target.
+It is not currently possible to define new local variables.
+The seven local variables are as follows:
+.Bl -tag -width ".ARCHIVE" -offset indent
+.It Va .ALLSRC
+The list of all sources for this target; also known as
+.Ql Va \&> .
+.It Va .ARCHIVE
+The name of the archive file; also known as
+.Ql Va \&! .
+.It Va .IMPSRC
+In suffix-transformation rules, the name/path of the source from which the
+target is to be transformed (the
+.Dq implied
+source); also known as
+.Ql Va \&< .
+It is not defined in explicit rules.
+.It Va .MEMBER
+The name of the archive member; also known as
+.Ql Va % .
+.It Va .OODATE
+The list of sources for this target that were deemed out-of-date; also
+known as
+.Ql Va \&? .
+.It Va .PREFIX
+The file prefix of the target, containing only the file portion, no suffix
+or preceding directory components; also known as
+.Ql Va * .
+The suffix must be one of the known suffixes declared with
+.Ic .SUFFIXES
+or it will not be recognized.
+.It Va .TARGET
+The name of the target; also known as
+.Ql Va @ .
+For compatibility with other makes this is an alias for
+.Ic .ARCHIVE
+in archive member rules.
+.El
+.Pp
+The shorter forms
+.Ql ( Va > ,
+.Ql Va \&! ,
+.Ql Va < ,
+.Ql Va % ,
+.Ql Va \&? ,
+.Ql Va * ,
+and
+.Ql Va @ )
+are permitted for backward
+compatibility with historical makefiles and legacy POSIX make and are
+not recommended.
+.Pp
+Variants of these variables with the punctuation followed immediately by
+.Ql D
+or
+.Ql F ,
+e.g.
+.Ql Va $(@D) ,
+are legacy forms equivalent to using the
+.Ql :H
+and
+.Ql :T
+modifiers.
+These forms are accepted for compatibility with
+.At V
+makefiles and POSIX but are not recommended.
+.Pp
+Four of the local variables may be used in sources on dependency lines
+because they expand to the proper value for each target on the line.
+These variables are
+.Ql Va .TARGET ,
+.Ql Va .PREFIX ,
+.Ql Va .ARCHIVE ,
+and
+.Ql Va .MEMBER .
+.Ss Additional built-in variables
+In addition,
+.Nm
+sets or knows about the following variables:
+.Bl -tag -width .MAKEOVERRIDES
+.It Va \&$
+A single dollar sign
+.Ql \&$ ,
+i.e.
+.Ql \&$$
+expands to a single dollar
+sign.
+.It Va .ALLTARGETS
+The list of all targets encountered in the Makefile.
+If evaluated during
+Makefile parsing, lists only those targets encountered thus far.
+.It Va .CURDIR
+A path to the directory where
+.Nm
+was executed.
+Refer to the description of
+.Ql Ev PWD
+for more details.
+.It Va .INCLUDEDFROMDIR
+The directory of the file this Makefile was included from.
+.It Va .INCLUDEDFROMFILE
+The filename of the file this Makefile was included from.
+.It Ev MAKE
+The name that
+.Nm
+was executed with
+.Pq Va argv[0] .
+For compatibility
+.Nm
+also sets
+.Va .MAKE
+with the same value.
+The preferred variable to use is the environment variable
+.Ev MAKE
+because it is more compatible with other versions of
+.Nm
+and cannot be confused with the special target with the same name.
+.It Va .MAKE.DEPENDFILE
+Names the makefile (default
+.Ql Pa .depend )
+from which generated dependencies are read.
+.It Va .MAKE.EXPAND_VARIABLES
+A boolean that controls the default behavior of the
+.Fl V
+option.
+If true, variable values printed with
+.Fl V
+are fully expanded; if false, the raw variable contents (which may
+include additional unexpanded variable references) are shown.
+.It Va .MAKE.EXPORTED
+The list of variables exported by
+.Nm .
+.It Va .MAKE.JOBS
+The argument to the
+.Fl j
+option.
+.It Va .MAKE.JOB.PREFIX
+If
+.Nm
+is run with
+.Ar j
+then output for each target is prefixed with a token
+.Ql --- target ---
+the first part of which can be controlled via
+.Va .MAKE.JOB.PREFIX .
+If
+.Va .MAKE.JOB.PREFIX
+is empty, no token is printed.
+.br
+For example:
+.Li .MAKE.JOB.PREFIX=${.newline}---${.MAKE:T}[${.MAKE.PID}]
+would produce tokens like
+.Ql ---make[1234] target ---
+making it easier to track the degree of parallelism being achieved.
+.It Ev MAKEFLAGS
+The environment variable
+.Ql Ev MAKEFLAGS
+may contain anything that
+may be specified on
+.Nm Ns 's
+command line.
+Anything specified on
+.Nm Ns 's
+command line is appended to the
+.Ql Ev MAKEFLAGS
+variable which is then
+entered into the environment for all programs which
+.Nm
+executes.
+.It Va .MAKE.LEVEL
+The recursion depth of
+.Nm .
+The initial instance of
+.Nm
+will be 0, and an incremented value is put into the environment
+to be seen by the next generation.
+This allows tests like:
+.Li .if ${.MAKE.LEVEL} == 0
+to protect things which should only be evaluated in the initial instance of
+.Nm .
+.It Va .MAKE.MAKEFILE_PREFERENCE
+The ordered list of makefile names
+(default
+.Ql Pa makefile ,
+.Ql Pa Makefile )
+that
+.Nm
+will look for.
+.It Va .MAKE.MAKEFILES
+The list of makefiles read by
+.Nm ,
+which is useful for tracking dependencies.
+Each makefile is recorded only once, regardless of the number of times read.
+.It Va .MAKE.MODE
+Processed after reading all makefiles.
+Can affect the mode that
+.Nm
+runs in.
+It can contain a number of keywords:
+.Bl -hang -width missing-filemon=bf.
+.It Pa compat
+Like
+.Fl B ,
+puts
+.Nm
+into "compat" mode.
+.It Pa meta
+Puts
+.Nm
+into "meta" mode, where meta files are created for each target
+to capture the command run, the output generated and if
+.Xr filemon 4
+is available, the system calls which are of interest to
+.Nm .
+The captured output can be very useful when diagnosing errors.
+.It Pa curdirOk= Ar bf
+Normally
+.Nm
+will not create .meta files in
+.Ql Va .CURDIR .
+This can be overridden by setting
+.Va bf
+to a value which represents True.
+.It Pa missing-meta= Ar bf
+If
+.Va bf
+is True, then a missing .meta file makes the target out-of-date.
+.It Pa missing-filemon= Ar bf
+If
+.Va bf
+is True, then missing filemon data makes the target out-of-date.
+.It Pa nofilemon
+Do not use
+.Xr filemon 4 .
+.It Pa env
+For debugging, it can be useful to include the environment
+in the .meta file.
+.It Pa verbose
+If in "meta" mode, print a clue about the target being built.
+This is useful if the build is otherwise running silently.
+The message printed the value of:
+.Va .MAKE.META.PREFIX .
+.It Pa ignore-cmd
+Some makefiles have commands which are simply not stable.
+This keyword causes them to be ignored for
+determining whether a target is out of date in "meta" mode.
+See also
+.Ic .NOMETA_CMP .
+.It Pa silent= Ar bf
+If
+.Va bf
+is True, when a .meta file is created, mark the target
+.Ic .SILENT .
+.El
+.It Va .MAKE.META.BAILIWICK
+In "meta" mode, provides a list of prefixes which
+match the directories controlled by
+.Nm .
+If a file that was generated outside of
+.Va .OBJDIR
+but within said bailiwick is missing,
+the current target is considered out-of-date.
+.It Va .MAKE.META.CREATED
+In "meta" mode, this variable contains a list of all the meta files
+updated.
+If not empty, it can be used to trigger processing of
+.Va .MAKE.META.FILES .
+.It Va .MAKE.META.FILES
+In "meta" mode, this variable contains a list of all the meta files
+used (updated or not).
+This list can be used to process the meta files to extract dependency
+information.
+.It Va .MAKE.META.IGNORE_PATHS
+Provides a list of path prefixes that should be ignored;
+because the contents are expected to change over time.
+The default list includes:
+.Ql Pa /dev /etc /proc /tmp /var/run /var/tmp
+.It Va .MAKE.META.IGNORE_PATTERNS
+Provides a list of patterns to match against pathnames.
+Ignore any that match.
+.It Va .MAKE.META.IGNORE_FILTER
+Provides a list of variable modifiers to apply to each pathname.
+Ignore if the expansion is an empty string.
+.It Va .MAKE.META.PREFIX
+Defines the message printed for each meta file updated in "meta verbose" mode.
+The default value is:
+.Dl Building ${.TARGET:H:tA}/${.TARGET:T}
+.It Va .MAKEOVERRIDES
+This variable is used to record the names of variables assigned to
+on the command line, so that they may be exported as part of
+.Ql Ev MAKEFLAGS .
+This behavior can be disabled by assigning an empty value to
+.Ql Va .MAKEOVERRIDES
+within a makefile.
+Extra variables can be exported from a makefile
+by appending their names to
+.Ql Va .MAKEOVERRIDES .
+.Ql Ev MAKEFLAGS
+is re-exported whenever
+.Ql Va .MAKEOVERRIDES
+is modified.
+.It Va .MAKE.PATH_FILEMON
+If
+.Nm
+was built with
+.Xr filemon 4
+support, this is set to the path of the device node.
+This allows makefiles to test for this support.
+.It Va .MAKE.PID
+The process-id of
+.Nm .
+.It Va .MAKE.PPID
+The parent process-id of
+.Nm .
+.It Va .MAKE.SAVE_DOLLARS
+value should be a boolean that controls whether
+.Ql $$
+are preserved when doing
+.Ql :=
+assignments.
+The default is true, for compatibility with other makes.
+If set to false,
+.Ql $$
+becomes
+.Ql $
+per normal evaluation rules.
+.It Va MAKE_PRINT_VAR_ON_ERROR
+When
+.Nm
+stops due to an error, it sets
+.Ql Va .ERROR_TARGET
+to the name of the target that failed,
+.Ql Va .ERROR_CMD
+to the commands of the failed target,
+and in "meta" mode, it also sets
+.Ql Va .ERROR_CWD
+to the
+.Xr getcwd 3 ,
+and
+.Ql Va .ERROR_META_FILE
+to the path of the meta file (if any) describing the failed target.
+It then prints its name and the value of
+.Ql Va .CURDIR
+as well as the value of any variables named in
+.Ql Va MAKE_PRINT_VAR_ON_ERROR .
+.It Va .newline
+This variable is simply assigned a newline character as its value.
+This allows expansions using the
+.Cm \&:@
+modifier to put a newline between
+iterations of the loop rather than a space.
+For example, the printing of
+.Ql Va MAKE_PRINT_VAR_ON_ERROR
+could be done as ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@}.
+.It Va .OBJDIR
+A path to the directory where the targets are built.
+Its value is determined by trying to
+.Xr chdir 2
+to the following directories in order and using the first match:
+.Bl -enum
+.It
+.Ev ${MAKEOBJDIRPREFIX}${.CURDIR}
+.Pp
+(Only if
+.Ql Ev MAKEOBJDIRPREFIX
+is set in the environment or on the command line.)
+.It
+.Ev ${MAKEOBJDIR}
+.Pp
+(Only if
+.Ql Ev MAKEOBJDIR
+is set in the environment or on the command line.)
+.It
+.Ev ${.CURDIR} Ns Pa /obj. Ns Ev ${MACHINE}
+.It
+.Ev ${.CURDIR} Ns Pa /obj
+.It
+.Pa /usr/obj/ Ns Ev ${.CURDIR}
+.It
+.Ev ${.CURDIR}
+.El
+.Pp
+Variable expansion is performed on the value before it's used,
+so expressions such as
+.Dl ${.CURDIR:S,^/usr/src,/var/obj,}
+may be used.
+This is especially useful with
+.Ql Ev MAKEOBJDIR .
+.Pp
+.Ql Va .OBJDIR
+may be modified in the makefile via the special target
+.Ql Ic .OBJDIR .
+In all cases,
+.Nm
+will
+.Xr chdir 2
+to the specified directory if it exists, and set
+.Ql Va .OBJDIR
+and
+.Ql Ev PWD
+to that directory before executing any targets.
+.
+.It Va .PARSEDIR
+A path to the directory of the current
+.Ql Pa Makefile
+being parsed.
+.It Va .PARSEFILE
+The basename of the current
+.Ql Pa Makefile
+being parsed.
+This variable and
+.Ql Va .PARSEDIR
+are both set only while the
+.Ql Pa Makefiles
+are being parsed.
+If you want to retain their current values, assign them to a variable
+using assignment with expansion:
+.Pq Ql Cm \&:= .
+.It Va .PATH
+A variable that represents the list of directories that
+.Nm
+will search for files.
+The search list should be updated using the target
+.Ql Va .PATH
+rather than the variable.
+.It Ev PWD
+Alternate path to the current directory.
+.Nm
+normally sets
+.Ql Va .CURDIR
+to the canonical path given by
+.Xr getcwd 3 .
+However, if the environment variable
+.Ql Ev PWD
+is set and gives a path to the current directory, then
+.Nm
+sets
+.Ql Va .CURDIR
+to the value of
+.Ql Ev PWD
+instead.
+This behavior is disabled if
+.Ql Ev MAKEOBJDIRPREFIX
+is set or
+.Ql Ev MAKEOBJDIR
+contains a variable transform.
+.Ql Ev PWD
+is set to the value of
+.Ql Va .OBJDIR
+for all programs which
+.Nm
+executes.
+.It Ev .TARGETS
+The list of targets explicitly specified on the command line, if any.
+.It Ev VPATH
+Colon-separated
+.Pq Dq \&:
+lists of directories that
+.Nm
+will search for files.
+The variable is supported for compatibility with old make programs only,
+use
+.Ql Va .PATH
+instead.
+.El
+.Ss Variable modifiers
+Variable expansion may be modified to select or modify each word of the
+variable (where a
+.Dq word
+is white-space delimited sequence of characters).
+The general format of a variable expansion is as follows:
+.Pp
+.Dl ${variable[:modifier[:...]]}
+.Pp
+Each modifier begins with a colon,
+which may be escaped with a backslash
+.Pq Ql \e .
+.Pp
+A set of modifiers can be specified via a variable, as follows:
+.Pp
+.Dl modifier_variable=modifier[:...]
+.Dl ${variable:${modifier_variable}[:...]}
+.Pp
+In this case the first modifier in the modifier_variable does not
+start with a colon, since that must appear in the referencing
+variable.
+If any of the modifiers in the modifier_variable contain a dollar sign
+.Pq Ql $ ,
+these must be doubled to avoid early expansion.
+.Pp
+The supported modifiers are:
+.Bl -tag -width EEE
+.It Cm \&:E
+Replaces each word in the variable with its suffix.
+.It Cm \&:H
+Replaces each word in the variable with everything but the last component.
+.It Cm \&:M Ns Ar pattern
+Select only those words that match
+.Ar pattern .
+The standard shell wildcard characters
+.Pf ( Ql * ,
+.Ql \&? ,
+and
+.Ql Oo Oc )
+may
+be used.
+The wildcard characters may be escaped with a backslash
+.Pq Ql \e .
+As a consequence of the way values are split into words, matched,
+and then joined, a construct like
+.Dl ${VAR:M*}
+will normalize the inter-word spacing, removing all leading and
+trailing space, and converting multiple consecutive spaces
+to single spaces.
+.
+.It Cm \&:N Ns Ar pattern
+This is identical to
+.Ql Cm \&:M ,
+but selects all words which do not match
+.Ar pattern .
+.It Cm \&:O
+Order every word in variable alphabetically.
+To sort words in
+reverse order use the
+.Ql Cm \&:O:[-1..1]
+combination of modifiers.
+.It Cm \&:Ox
+Randomize words in variable.
+The results will be different each time you are referring to the
+modified variable; use the assignment with expansion
+.Pq Ql Cm \&:=
+to prevent such behavior.
+For example,
+.Bd -literal -offset indent
+LIST= uno due tre quattro
+RANDOM_LIST= ${LIST:Ox}
+STATIC_RANDOM_LIST:= ${LIST:Ox}
+
+all:
+ @echo "${RANDOM_LIST}"
+ @echo "${RANDOM_LIST}"
+ @echo "${STATIC_RANDOM_LIST}"
+ @echo "${STATIC_RANDOM_LIST}"
+.Ed
+may produce output similar to:
+.Bd -literal -offset indent
+quattro due tre uno
+tre due quattro uno
+due uno quattro tre
+due uno quattro tre
+.Ed
+.It Cm \&:Q
+Quotes every shell meta-character in the variable, so that it can be passed
+safely to the shell.
+.It Cm \&:q
+Quotes every shell meta-character in the variable, and also doubles
+.Sq $
+characters so that it can be passed
+safely through recursive invocations of
+.Nm .
+This is equivalent to:
+.Sq \&:S/\e\&$/&&/g:Q .
+.It Cm \&:R
+Replaces each word in the variable with everything but its suffix.
+.It Cm \&:range[=count]
+The value is an integer sequence representing the words of the original
+value, or the supplied
+.Va count .
+.It Cm \&:gmtime[=utc]
+The value is a format string for
+.Xr strftime 3 ,
+using
+.Xr gmtime 3 .
+If a
+.Va utc
+value is not provided or is 0, the current time is used.
+.It Cm \&:hash
+Compute a 32-bit hash of the value and encode it as hex digits.
+.It Cm \&:localtime[=utc]
+The value is a format string for
+.Xr strftime 3 ,
+using
+.Xr localtime 3 .
+If a
+.Va utc
+value is not provided or is 0, the current time is used.
+.It Cm \&:tA
+Attempt to convert variable to an absolute path using
+.Xr realpath 3 ,
+if that fails, the value is unchanged.
+.It Cm \&:tl
+Converts variable to lower-case letters.
+.It Cm \&:ts Ns Ar c
+Words in the variable are normally separated by a space on expansion.
+This modifier sets the separator to the character
+.Ar c .
+If
+.Ar c
+is omitted, then no separator is used.
+The common escapes (including octal numeric codes), work as expected.
+.It Cm \&:tu
+Converts variable to upper-case letters.
+.It Cm \&:tW
+Causes the value to be treated as a single word
+(possibly containing embedded white space).
+See also
+.Ql Cm \&:[*] .
+.It Cm \&:tw
+Causes the value to be treated as a sequence of
+words delimited by white space.
+See also
+.Ql Cm \&:[@] .
+.Sm off
+.It Cm \&:S No \&/ Ar old_string No \&/ Ar new_string No \&/ Op Cm 1gW
+.Sm on
+Modify the first occurrence of
+.Ar old_string
+in the variable's value, replacing it with
+.Ar new_string .
+If a
+.Ql g
+is appended to the last slash of the pattern, all occurrences
+in each word are replaced.
+If a
+.Ql 1
+is appended to the last slash of the pattern, only the first word
+is affected.
+If a
+.Ql W
+is appended to the last slash of the pattern,
+then the value is treated as a single word
+(possibly containing embedded white space).
+If
+.Ar old_string
+begins with a caret
+.Pq Ql ^ ,
+.Ar old_string
+is anchored at the beginning of each word.
+If
+.Ar old_string
+ends with a dollar sign
+.Pq Ql \&$ ,
+it is anchored at the end of each word.
+Inside
+.Ar new_string ,
+an ampersand
+.Pq Ql &
+is replaced by
+.Ar old_string
+(without any
+.Ql ^
+or
+.Ql \&$ ) .
+Any character may be used as a delimiter for the parts of the modifier
+string.
+The anchoring, ampersand and delimiter characters may be escaped with a
+backslash
+.Pq Ql \e .
+.Pp
+Variable expansion occurs in the normal fashion inside both
+.Ar old_string
+and
+.Ar new_string
+with the single exception that a backslash is used to prevent the expansion
+of a dollar sign
+.Pq Ql \&$ ,
+not a preceding dollar sign as is usual.
+.Sm off
+.It Cm \&:C No \&/ Ar pattern No \&/ Ar replacement No \&/ Op Cm 1gW
+.Sm on
+The
+.Cm \&:C
+modifier is just like the
+.Cm \&:S
+modifier except that the old and new strings, instead of being
+simple strings, are an extended regular expression (see
+.Xr regex 3 )
+string
+.Ar pattern
+and an
+.Xr ed 1 Ns \-style
+string
+.Ar replacement .
+Normally, the first occurrence of the pattern
+.Ar pattern
+in each word of the value is substituted with
+.Ar replacement .
+The
+.Ql 1
+modifier causes the substitution to apply to at most one word; the
+.Ql g
+modifier causes the substitution to apply to as many instances of the
+search pattern
+.Ar pattern
+as occur in the word or words it is found in; the
+.Ql W
+modifier causes the value to be treated as a single word
+(possibly containing embedded white space).
+Note that
+.Ql 1
+and
+.Ql g
+are orthogonal; the former specifies whether multiple words are
+potentially affected, the latter whether multiple substitutions can
+potentially occur within each affected word.
+.Pp
+As for the
+.Cm \&:S
+modifier, the
+.Ar pattern
+and
+.Ar replacement
+are subjected to variable expansion before being parsed as
+regular expressions.
+.It Cm \&:T
+Replaces each word in the variable with its last component.
+.It Cm \&:u
+Remove adjacent duplicate words (like
+.Xr uniq 1 ) .
+.Sm off
+.It Cm \&:\&? Ar true_string Cm \&: Ar false_string
+.Sm on
+If the variable name (not its value), when parsed as a .if conditional
+expression, evaluates to true, return as its value the
+.Ar true_string ,
+otherwise return the
+.Ar false_string .
+Since the variable name is used as the expression, \&:\&? must be the
+first modifier after the variable name itself - which will, of course,
+usually contain variable expansions.
+A common error is trying to use expressions like
+.Dl ${NUMBERS:M42:?match:no}
+which actually tests defined(NUMBERS),
+to determine is any words match "42" you need to use something like:
+.Dl ${"${NUMBERS:M42}" != \&"\&":?match:no} .
+.It Ar :old_string=new_string
+This is the
+.At V
+style variable substitution.
+It must be the last modifier specified.
+If
+.Ar old_string
+or
+.Ar new_string
+do not contain the pattern matching character
+.Ar %
+then it is assumed that they are
+anchored at the end of each word, so only suffixes or entire
+words may be replaced.
+Otherwise
+.Ar %
+is the substring of
+.Ar old_string
+to be replaced in
+.Ar new_string .
+.Pp
+Variable expansion occurs in the normal fashion inside both
+.Ar old_string
+and
+.Ar new_string
+with the single exception that a backslash is used to prevent the
+expansion of a dollar sign
+.Pq Ql \&$ ,
+not a preceding dollar sign as is usual.
+.Sm off
+.It Cm \&:@ Ar temp Cm @ Ar string Cm @
+.Sm on
+This is the loop expansion mechanism from the OSF Development
+Environment (ODE) make.
+Unlike
+.Cm \&.for
+loops expansion occurs at the time of
+reference.
+Assign
+.Ar temp
+to each word in the variable and evaluate
+.Ar string .
+The ODE convention is that
+.Ar temp
+should start and end with a period.
+For example.
+.Dl ${LINKS:@.LINK.@${LN} ${TARGET} ${.LINK.}@}
+.Pp
+However a single character variable is often more readable:
+.Dl ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@}
+.It Cm \&:_[=var]
+Save the current variable value in
+.Ql $_
+or the named
+.Va var
+for later reference.
+Example usage:
+.Bd -literal -offset indent
+M_cmpv.units = 1 1000 1000000
+M_cmpv = S,., ,g:_:range:@i@+ $${_:[-$$i]} \&\\
+\\* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh
+
+.Dv .if ${VERSION:${M_cmpv}} < ${3.1.12:L:${M_cmpv}}
+
+.Ed
+Here
+.Ql $_
+is used to save the result of the
+.Ql :S
+modifier which is later referenced using the index values from
+.Ql :range .
+.It Cm \&:U Ns Ar newval
+If the variable is undefined
+.Ar newval
+is the value.
+If the variable is defined, the existing value is returned.
+This is another ODE make feature.
+It is handy for setting per-target CFLAGS for instance:
+.Dl ${_${.TARGET:T}_CFLAGS:U${DEF_CFLAGS}}
+If a value is only required if the variable is undefined, use:
+.Dl ${VAR:D:Unewval}
+.It Cm \&:D Ns Ar newval
+If the variable is defined
+.Ar newval
+is the value.
+.It Cm \&:L
+The name of the variable is the value.
+.It Cm \&:P
+The path of the node which has the same name as the variable
+is the value.
+If no such node exists or its path is null, then the
+name of the variable is used.
+In order for this modifier to work, the name (node) must at least have
+appeared on the rhs of a dependency.
+.Sm off
+.It Cm \&:\&! Ar cmd Cm \&!
+.Sm on
+The output of running
+.Ar cmd
+is the value.
+.It Cm \&:sh
+If the variable is non-empty it is run as a command and the output
+becomes the new value.
+.It Cm \&::= Ns Ar str
+The variable is assigned the value
+.Ar str
+after substitution.
+This modifier and its variations are useful in
+obscure situations such as wanting to set a variable when shell commands
+are being parsed.
+These assignment modifiers always expand to
+nothing, so if appearing in a rule line by themselves should be
+preceded with something to keep
+.Nm
+happy.
+.Pp
+The
+.Ql Cm \&::
+helps avoid false matches with the
+.At V
+style
+.Cm \&:=
+modifier and since substitution always occurs the
+.Cm \&::=
+form is vaguely appropriate.
+.It Cm \&::?= Ns Ar str
+As for
+.Cm \&::=
+but only if the variable does not already have a value.
+.It Cm \&::+= Ns Ar str
+Append
+.Ar str
+to the variable.
+.It Cm \&::!= Ns Ar cmd
+Assign the output of
+.Ar cmd
+to the variable.
+.It Cm \&:\&[ Ns Ar range Ns Cm \&]
+Selects one or more words from the value,
+or performs other operations related to the way in which the
+value is divided into words.
+.Pp
+Ordinarily, a value is treated as a sequence of words
+delimited by white space.
+Some modifiers suppress this behavior,
+causing a value to be treated as a single word
+(possibly containing embedded white space).
+An empty value, or a value that consists entirely of white-space,
+is treated as a single word.
+For the purposes of the
+.Ql Cm \&:[]
+modifier, the words are indexed both forwards using positive integers
+(where index 1 represents the first word),
+and backwards using negative integers
+(where index \-1 represents the last word).
+.Pp
+The
+.Ar range
+is subjected to variable expansion, and the expanded result is
+then interpreted as follows:
+.Bl -tag -width index
+.\" :[n]
+.It Ar index
+Selects a single word from the value.
+.\" :[start..end]
+.It Ar start Ns Cm \&.. Ns Ar end
+Selects all words from
+.Ar start
+to
+.Ar end ,
+inclusive.
+For example,
+.Ql Cm \&:[2..-1]
+selects all words from the second word to the last word.
+If
+.Ar start
+is greater than
+.Ar end ,
+then the words are output in reverse order.
+For example,
+.Ql Cm \&:[-1..1]
+selects all the words from last to first.
+.\" :[*]
+.It Cm \&*
+Causes subsequent modifiers to treat the value as a single word
+(possibly containing embedded white space).
+Analogous to the effect of
+\&"$*\&"
+in Bourne shell.
+.\" :[0]
+.It 0
+Means the same as
+.Ql Cm \&:[*] .
+.\" :[*]
+.It Cm \&@
+Causes subsequent modifiers to treat the value as a sequence of words
+delimited by white space.
+Analogous to the effect of
+\&"$@\&"
+in Bourne shell.
+.\" :[#]
+.It Cm \&#
+Returns the number of words in the value.
+.El \" :[range]
+.El
+.Sh INCLUDE STATEMENTS, CONDITIONALS AND FOR LOOPS
+Makefile inclusion, conditional structures and for loops reminiscent
+of the C programming language are provided in
+.Nm .
+All such structures are identified by a line beginning with a single
+dot
+.Pq Ql \&.
+character.
+Files are included with either
+.Cm \&.include Aq Ar file
+or
+.Cm \&.include Pf \*q Ar file Ns \*q .
+Variables between the angle brackets or double quotes are expanded
+to form the file name.
+If angle brackets are used, the included makefile is expected to be in
+the system makefile directory.
+If double quotes are used, the including makefile's directory and any
+directories specified using the
+.Fl I
+option are searched before the system
+makefile directory.
+For compatibility with other versions of
+.Nm
+.Ql include file ...
+is also accepted.
+.Pp
+If the include statement is written as
+.Cm .-include
+or as
+.Cm .sinclude
+then errors locating and/or opening include files are ignored.
+.Pp
+If the include statement is written as
+.Cm .dinclude
+not only are errors locating and/or opening include files ignored,
+but stale dependencies within the included file will be ignored
+just like
+.Va .MAKE.DEPENDFILE .
+.Pp
+Conditional expressions are also preceded by a single dot as the first
+character of a line.
+The possible conditionals are as follows:
+.Bl -tag -width Ds
+.It Ic .error Ar message
+The message is printed along with the name of the makefile and line number,
+then
+.Nm
+will exit.
+.It Ic .export Ar variable ...
+Export the specified global variable.
+If no variable list is provided, all globals are exported
+except for internal variables (those that start with
+.Ql \&. ) .
+This is not affected by the
+.Fl X
+flag, so should be used with caution.
+For compatibility with other
+.Nm
+programs
+.Ql export variable=value
+is also accepted.
+.Pp
+Appending a variable name to
+.Va .MAKE.EXPORTED
+is equivalent to exporting a variable.
+.It Ic .export-env Ar variable ...
+The same as
+.Ql .export ,
+except that the variable is not appended to
+.Va .MAKE.EXPORTED .
+This allows exporting a value to the environment which is different from that
+used by
+.Nm
+internally.
+.It Ic .export-literal Ar variable ...
+The same as
+.Ql .export-env ,
+except that variables in the value are not expanded.
+.It Ic .info Ar message
+The message is printed along with the name of the makefile and line number.
+.It Ic .undef Ar variable
+Un-define the specified global variable.
+Only global variables may be un-defined.
+.It Ic .unexport Ar variable ...
+The opposite of
+.Ql .export .
+The specified global
+.Va variable
+will be removed from
+.Va .MAKE.EXPORTED .
+If no variable list is provided, all globals are unexported,
+and
+.Va .MAKE.EXPORTED
+deleted.
+.It Ic .unexport-env
+Unexport all globals previously exported and
+clear the environment inherited from the parent.
+This operation will cause a memory leak of the original environment,
+so should be used sparingly.
+Testing for
+.Va .MAKE.LEVEL
+being 0, would make sense.
+Also note that any variables which originated in the parent environment
+should be explicitly preserved if desired.
+For example:
+.Bd -literal -offset indent
+.Li .if ${.MAKE.LEVEL} == 0
+PATH := ${PATH}
+.Li .unexport-env
+.Li .export PATH
+.Li .endif
+.Pp
+.Ed
+Would result in an environment containing only
+.Ql Ev PATH ,
+which is the minimal useful environment.
+Actually
+.Ql Ev .MAKE.LEVEL
+will also be pushed into the new environment.
+.It Ic .warning Ar message
+The message prefixed by
+.Ql Pa warning:
+is printed along with the name of the makefile and line number.
+.It Ic \&.if Oo \&! Oc Ns Ar expression Op Ar operator expression ...
+Test the value of an expression.
+.It Ic .ifdef Oo \&! Oc Ns Ar variable Op Ar operator variable ...
+Test the value of a variable.
+.It Ic .ifndef Oo \&! Oc Ns Ar variable Op Ar operator variable ...
+Test the value of a variable.
+.It Ic .ifmake Oo \&! Oc Ns Ar target Op Ar operator target ...
+Test the target being built.
+.It Ic .ifnmake Oo \&! Ns Oc Ar target Op Ar operator target ...
+Test the target being built.
+.It Ic .else
+Reverse the sense of the last conditional.
+.It Ic .elif Oo \&! Ns Oc Ar expression Op Ar operator expression ...
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .if .
+.It Ic .elifdef Oo \&! Oc Ns Ar variable Op Ar operator variable ...
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifdef .
+.It Ic .elifndef Oo \&! Oc Ns Ar variable Op Ar operator variable ...
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifndef .
+.It Ic .elifmake Oo \&! Oc Ns Ar target Op Ar operator target ...
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifmake .
+.It Ic .elifnmake Oo \&! Oc Ns Ar target Op Ar operator target ...
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifnmake .
+.It Ic .endif
+End the body of the conditional.
+.El
+.Pp
+The
+.Ar operator
+may be any one of the following:
+.Bl -tag -width "Cm XX"
+.It Cm \&|\&|
+Logical OR.
+.It Cm \&&&
+Logical
+.Tn AND ;
+of higher precedence than
+.Dq \&|\&| .
+.El
+.Pp
+As in C,
+.Nm
+will only evaluate a conditional as far as is necessary to determine
+its value.
+Parentheses may be used to change the order of evaluation.
+The boolean operator
+.Ql Ic \&!
+may be used to logically negate an entire
+conditional.
+It is of higher precedence than
+.Ql Ic \&&& .
+.Pp
+The value of
+.Ar expression
+may be any of the following:
+.Bl -tag -width defined
+.It Ic defined
+Takes a variable name as an argument and evaluates to true if the variable
+has been defined.
+.It Ic make
+Takes a target name as an argument and evaluates to true if the target
+was specified as part of
+.Nm Ns 's
+command line or was declared the default target (either implicitly or
+explicitly, see
+.Va .MAIN )
+before the line containing the conditional.
+.It Ic empty
+Takes a variable, with possible modifiers, and evaluates to true if
+the expansion of the variable would result in an empty string.
+.It Ic exists
+Takes a file name as an argument and evaluates to true if the file exists.
+The file is searched for on the system search path (see
+.Va .PATH ) .
+.It Ic target
+Takes a target name as an argument and evaluates to true if the target
+has been defined.
+.It Ic commands
+Takes a target name as an argument and evaluates to true if the target
+has been defined and has commands associated with it.
+.El
+.Pp
+.Ar Expression
+may also be an arithmetic or string comparison.
+Variable expansion is
+performed on both sides of the comparison, after which the integral
+values are compared.
+A value is interpreted as hexadecimal if it is
+preceded by 0x, otherwise it is decimal; octal numbers are not supported.
+The standard C relational operators are all supported.
+If after
+variable expansion, either the left or right hand side of a
+.Ql Ic ==
+or
+.Ql Ic "!="
+operator is not an integral value, then
+string comparison is performed between the expanded
+variables.
+If no relational operator is given, it is assumed that the expanded
+variable is being compared against 0 or an empty string in the case
+of a string comparison.
+.Pp
+When
+.Nm
+is evaluating one of these conditional expressions, and it encounters
+a (white-space separated) word it doesn't recognize, either the
+.Dq make
+or
+.Dq defined
+expression is applied to it, depending on the form of the conditional.
+If the form is
+.Ql Ic .ifdef ,
+.Ql Ic .ifndef ,
+or
+.Ql Ic .if
+the
+.Dq defined
+expression is applied.
+Similarly, if the form is
+.Ql Ic .ifmake
+or
+.Ql Ic .ifnmake ,
+the
+.Dq make
+expression is applied.
+.Pp
+If the conditional evaluates to true the parsing of the makefile continues
+as before.
+If it evaluates to false, the following lines are skipped.
+In both cases this continues until a
+.Ql Ic .else
+or
+.Ql Ic .endif
+is found.
+.Pp
+For loops are typically used to apply a set of rules to a list of files.
+The syntax of a for loop is:
+.Pp
+.Bl -tag -compact -width Ds
+.It Ic \&.for Ar variable Oo Ar variable ... Oc Ic in Ar expression
+.It Aq make-rules
+.It Ic \&.endfor
+.El
+.Pp
+After the for
+.Ic expression
+is evaluated, it is split into words.
+On each iteration of the loop, one word is taken and assigned to each
+.Ic variable ,
+in order, and these
+.Ic variables
+are substituted into the
+.Ic make-rules
+inside the body of the for loop.
+The number of words must come out even; that is, if there are three
+iteration variables, the number of words provided must be a multiple
+of three.
+.Sh COMMENTS
+Comments begin with a hash
+.Pq Ql \&#
+character, anywhere but in a shell
+command line, and continue to the end of an unescaped new line.
+.Sh SPECIAL SOURCES (ATTRIBUTES)
+.Bl -tag -width .IGNOREx
+.It Ic .EXEC
+Target is never out of date, but always execute commands anyway.
+.It Ic .IGNORE
+Ignore any errors from the commands associated with this target, exactly
+as if they all were preceded by a dash
+.Pq Ql \- .
+.\" .It Ic .INVISIBLE
+.\" XXX
+.\" .It Ic .JOIN
+.\" XXX
+.It Ic .MADE
+Mark all sources of this target as being up-to-date.
+.It Ic .MAKE
+Execute the commands associated with this target even if the
+.Fl n
+or
+.Fl t
+options were specified.
+Normally used to mark recursive
+.Nm Ns s .
+.It Ic .META
+Create a meta file for the target, even if it is flagged as
+.Ic .PHONY ,
+.Ic .MAKE ,
+or
+.Ic .SPECIAL .
+Usage in conjunction with
+.Ic .MAKE
+is the most likely case.
+In "meta" mode, the target is out-of-date if the meta file is missing.
+.It Ic .NOMETA
+Do not create a meta file for the target.
+Meta files are also not created for
+.Ic .PHONY ,
+.Ic .MAKE ,
+or
+.Ic .SPECIAL
+targets.
+.It Ic .NOMETA_CMP
+Ignore differences in commands when deciding if target is out of date.
+This is useful if the command contains a value which always changes.
+If the number of commands change, though, the target will still be out of date.
+The same effect applies to any command line that uses the variable
+.Va .OODATE ,
+which can be used for that purpose even when not otherwise needed or desired:
+.Bd -literal -offset indent
+
+skip-compare-for-some:
+ @echo this will be compared
+ @echo this will not ${.OODATE:M.NOMETA_CMP}
+ @echo this will also be compared
+
+.Ed
+The
+.Cm \&:M
+pattern suppresses any expansion of the unwanted variable.
+.It Ic .NOPATH
+Do not search for the target in the directories specified by
+.Ic .PATH .
+.It Ic .NOTMAIN
+Normally
+.Nm
+selects the first target it encounters as the default target to be built
+if no target was specified.
+This source prevents this target from being selected.
+.It Ic .OPTIONAL
+If a target is marked with this attribute and
+.Nm
+can't figure out how to create it, it will ignore this fact and assume
+the file isn't needed or already exists.
+.It Ic .PHONY
+The target does not
+correspond to an actual file; it is always considered to be out of date,
+and will not be created with the
+.Fl t
+option.
+Suffix-transformation rules are not applied to
+.Ic .PHONY
+targets.
+.It Ic .PRECIOUS
+When
+.Nm
+is interrupted, it normally removes any partially made targets.
+This source prevents the target from being removed.
+.It Ic .RECURSIVE
+Synonym for
+.Ic .MAKE .
+.It Ic .SILENT
+Do not echo any of the commands associated with this target, exactly
+as if they all were preceded by an at sign
+.Pq Ql @ .
+.It Ic .USE
+Turn the target into
+.Nm Ns 's
+version of a macro.
+When the target is used as a source for another target, the other target
+acquires the commands, sources, and attributes (except for
+.Ic .USE )
+of the
+source.
+If the target already has commands, the
+.Ic .USE
+target's commands are appended
+to them.
+.It Ic .USEBEFORE
+Exactly like
+.Ic .USE ,
+but prepend the
+.Ic .USEBEFORE
+target commands to the target.
+.It Ic .WAIT
+If
+.Ic .WAIT
+appears in a dependency line, the sources that precede it are
+made before the sources that succeed it in the line.
+Since the dependents of files are not made until the file itself
+could be made, this also stops the dependents being built unless they
+are needed for another branch of the dependency tree.
+So given:
+.Bd -literal
+x: a .WAIT b
+ echo x
+a:
+ echo a
+b: b1
+ echo b
+b1:
+ echo b1
+
+.Ed
+the output is always
+.Ql a ,
+.Ql b1 ,
+.Ql b ,
+.Ql x .
+.br
+The ordering imposed by
+.Ic .WAIT
+is only relevant for parallel makes.
+.El
+.Sh SPECIAL TARGETS
+Special targets may not be included with other targets, i.e. they must be
+the only target specified.
+.Bl -tag -width .BEGINx
+.It Ic .BEGIN
+Any command lines attached to this target are executed before anything
+else is done.
+.It Ic .DEFAULT
+This is sort of a
+.Ic .USE
+rule for any target (that was used only as a
+source) that
+.Nm
+can't figure out any other way to create.
+Only the shell script is used.
+The
+.Ic .IMPSRC
+variable of a target that inherits
+.Ic .DEFAULT Ns 's
+commands is set
+to the target's own name.
+.It Ic .DELETE_ON_ERROR
+If this target is present in the makefile, it globally causes make to
+delete targets whose commands fail.
+(By default, only targets whose commands are interrupted during
+execution are deleted.
+This is the historical behavior.)
+This setting can be used to help prevent half-finished or malformed
+targets from being left around and corrupting future rebuilds.
+.It Ic .END
+Any command lines attached to this target are executed after everything
+else is done.
+.It Ic .ERROR
+Any command lines attached to this target are executed when another target fails.
+The
+.Ic .ERROR_TARGET
+variable is set to the target that failed.
+See also
+.Ic MAKE_PRINT_VAR_ON_ERROR .
+.It Ic .IGNORE
+Mark each of the sources with the
+.Ic .IGNORE
+attribute.
+If no sources are specified, this is the equivalent of specifying the
+.Fl i
+option.
+.It Ic .INTERRUPT
+If
+.Nm
+is interrupted, the commands for this target will be executed.
+.It Ic .MAIN
+If no target is specified when
+.Nm
+is invoked, this target will be built.
+.It Ic .MAKEFLAGS
+This target provides a way to specify flags for
+.Nm
+when the makefile is used.
+The flags are as if typed to the shell, though the
+.Fl f
+option will have
+no effect.
+.\" XXX: NOT YET!!!!
+.\" .It Ic .NOTPARALLEL
+.\" The named targets are executed in non parallel mode.
+.\" If no targets are
+.\" specified, then all targets are executed in non parallel mode.
+.It Ic .NOPATH
+Apply the
+.Ic .NOPATH
+attribute to any specified sources.
+.It Ic .NOTPARALLEL
+Disable parallel mode.
+.It Ic .NO_PARALLEL
+Synonym for
+.Ic .NOTPARALLEL ,
+for compatibility with other pmake variants.
+.It Ic .OBJDIR
+The source is a new value for
+.Ql Va .OBJDIR .
+If it exists,
+.Nm
+will
+.Xr chdir 2
+to it and update the value of
+.Ql Va .OBJDIR .
+.It Ic .ORDER
+The named targets are made in sequence.
+This ordering does not add targets to the list of targets to be made.
+Since the dependents of a target do not get built until the target itself
+could be built, unless
+.Ql a
+is built by another part of the dependency graph,
+the following is a dependency loop:
+.Bd -literal
+\&.ORDER: b a
+b: a
+.Ed
+.Pp
+The ordering imposed by
+.Ic .ORDER
+is only relevant for parallel makes.
+.\" XXX: NOT YET!!!!
+.\" .It Ic .PARALLEL
+.\" The named targets are executed in parallel mode.
+.\" If no targets are
+.\" specified, then all targets are executed in parallel mode.
+.It Ic .PATH
+The sources are directories which are to be searched for files not
+found in the current directory.
+If no sources are specified, any previously specified directories are
+deleted.
+If the source is the special
+.Ic .DOTLAST
+target, then the current working
+directory is searched last.
+.It Ic .PATH. Ns Va suffix
+Like
+.Ic .PATH
+but applies only to files with a particular suffix.
+The suffix must have been previously declared with
+.Ic .SUFFIXES .
+.It Ic .PHONY
+Apply the
+.Ic .PHONY
+attribute to any specified sources.
+.It Ic .PRECIOUS
+Apply the
+.Ic .PRECIOUS
+attribute to any specified sources.
+If no sources are specified, the
+.Ic .PRECIOUS
+attribute is applied to every
+target in the file.
+.It Ic .SHELL
+Sets the shell that
+.Nm
+will use to execute commands.
+The sources are a set of
+.Ar field=value
+pairs.
+.Bl -tag -width hasErrCtls
+.It Ar name
+This is the minimal specification, used to select one of the built-in
+shell specs;
+.Ar sh ,
+.Ar ksh ,
+and
+.Ar csh .
+.It Ar path
+Specifies the path to the shell.
+.It Ar hasErrCtl
+Indicates whether the shell supports exit on error.
+.It Ar check
+The command to turn on error checking.
+.It Ar ignore
+The command to disable error checking.
+.It Ar echo
+The command to turn on echoing of commands executed.
+.It Ar quiet
+The command to turn off echoing of commands executed.
+.It Ar filter
+The output to filter after issuing the
+.Ar quiet
+command.
+It is typically identical to
+.Ar quiet .
+.It Ar errFlag
+The flag to pass the shell to enable error checking.
+.It Ar echoFlag
+The flag to pass the shell to enable command echoing.
+.It Ar newline
+The string literal to pass the shell that results in a single newline
+character when used outside of any quoting characters.
+.El
+Example:
+.Bd -literal
+\&.SHELL: name=ksh path=/bin/ksh hasErrCtl=true \e
+ check="set \-e" ignore="set +e" \e
+ echo="set \-v" quiet="set +v" filter="set +v" \e
+ echoFlag=v errFlag=e newline="'\en'"
+.Ed
+.It Ic .SILENT
+Apply the
+.Ic .SILENT
+attribute to any specified sources.
+If no sources are specified, the
+.Ic .SILENT
+attribute is applied to every
+command in the file.
+.It Ic .STALE
+This target gets run when a dependency file contains stale entries, having
+.Va .ALLSRC
+set to the name of that dependency file.
+.It Ic .SUFFIXES
+Each source specifies a suffix to
+.Nm .
+If no sources are specified, any previously specified suffixes are deleted.
+It allows the creation of suffix-transformation rules.
+.Pp
+Example:
+.Bd -literal
+\&.SUFFIXES: .o
+\&.c.o:
+ cc \-o ${.TARGET} \-c ${.IMPSRC}
+.Ed
+.El
+.Sh ENVIRONMENT
+.Nm
+uses the following environment variables, if they exist:
+.Ev MACHINE ,
+.Ev MACHINE_ARCH ,
+.Ev MAKE ,
+.Ev MAKEFLAGS ,
+.Ev MAKEOBJDIR ,
+.Ev MAKEOBJDIRPREFIX ,
+.Ev MAKESYSPATH ,
+.Ev PWD ,
+and
+.Ev TMPDIR .
+.Pp
+.Ev MAKEOBJDIRPREFIX
+and
+.Ev MAKEOBJDIR
+may only be set in the environment or on the command line to
+.Nm
+and not as makefile variables;
+see the description of
+.Ql Va .OBJDIR
+for more details.
+.Sh FILES
+.Bl -tag -width /usr/share/mk -compact
+.It .depend
+list of dependencies
+.It Makefile
+list of dependencies
+.It makefile
+list of dependencies
+.It sys.mk
+system makefile
+.It /usr/share/mk
+system makefile directory
+.El
+.Sh COMPATIBILITY
+The basic make syntax is compatible between different versions of make;
+however the special variables, variable modifiers and conditionals are not.
+.Ss Older versions
+An incomplete list of changes in older versions of
+.Nm :
+.Pp
+The way that .for loop variables are substituted changed after
+.Nx 5.0
+so that they still appear to be variable expansions.
+In particular this stops them being treated as syntax, and removes some
+obscure problems using them in .if statements.
+.Pp
+The way that parallel makes are scheduled changed in
+.Nx 4.0
+so that .ORDER and .WAIT apply recursively to the dependent nodes.
+The algorithms used may change again in the future.
+.Ss Other make dialects
+Other make dialects (GNU make, SVR4 make, POSIX make, etc.) do not
+support most of the features of
+.Nm
+as described in this manual.
+Most notably:
+.Bl -bullet -offset indent
+.It
+The
+.Ic .WAIT
+and
+.Ic .ORDER
+declarations and most functionality pertaining to parallelization.
+(GNU make supports parallelization but lacks these features needed to
+control it effectively.)
+.It
+Directives, including for loops and conditionals and most of the
+forms of include files.
+(GNU make has its own incompatible and less powerful syntax for
+conditionals.)
+.It
+All built-in variables that begin with a dot.
+.It
+Most of the special sources and targets that begin with a dot,
+with the notable exception of
+.Ic .PHONY ,
+.Ic .PRECIOUS ,
+and
+.Ic .SUFFIXES .
+.It
+Variable modifiers, except for the
+.Dl :old=new
+string substitution, which does not portably support globbing with
+.Ql %
+and historically only works on declared suffixes.
+.It
+The
+.Ic $>
+variable even in its short form; most makes support this functionality
+but its name varies.
+.El
+.Pp
+Some features are somewhat more portable, such as assignment with
+.Ic += ,
+.Ic ?= ,
+and
+.Ic != .
+The
+.Ic .PATH
+functionality is based on an older feature
+.Ic VPATH
+found in GNU make and many versions of SVR4 make; however,
+historically its behavior is too ill-defined (and too buggy) to rely
+upon.
+.Pp
+The
+.Ic $@
+and
+.Ic $<
+variables are more or less universally portable, as is the
+.Ic $(MAKE)
+variable.
+Basic use of suffix rules (for files only in the current directory,
+not trying to chain transformations together, etc.) is also reasonably
+portable.
+.Sh SEE ALSO
+.Xr mkdep 1
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v7 .
+This
+.Nm
+implementation is based on Adam De Boor's pmake program which was written
+for Sprite at Berkeley.
+It was designed to be a parallel distributed make running jobs on different
+machines using a daemon called
+.Dq customs .
+.Pp
+Historically the target/dependency
+.Dq FRC
+has been used to FoRCe rebuilding (since the target/dependency
+does not exist... unless someone creates an
+.Dq FRC
+file).
+.Sh BUGS
+The
+.Nm
+syntax is difficult to parse without actually acting of the data.
+For instance finding the end of a variable use should involve scanning each
+the modifiers using the correct terminator for each field.
+In many places
+.Nm
+just counts {} and () in order to find the end of a variable expansion.
+.Pp
+There is no way of escaping a space character in a filename.
diff --git a/usr.bin/make/make.c b/usr.bin/make/make.c
new file mode 100644
index 0000000..8947582
--- /dev/null
+++ b/usr.bin/make/make.c
@@ -0,0 +1,1555 @@
+/* $NetBSD: make.c,v 1.96 2016/11/10 23:41:58 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: make.c,v 1.96 2016/11/10 23:41:58 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)make.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: make.c,v 1.96 2016/11/10 23:41:58 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * make.c --
+ * The functions which perform the examination of targets and
+ * their suitability for creation
+ *
+ * Interface:
+ * Make_Run Initialize things for the module and recreate
+ * whatever needs recreating. Returns TRUE if
+ * work was (or would have been) done and FALSE
+ * otherwise.
+ *
+ * Make_Update Update all parents of a given child. Performs
+ * various bookkeeping chores like the updating
+ * of the cmgn field of the parent, filling
+ * of the IMPSRC context variable, etc. It will
+ * place the parent on the toBeMade queue if it
+ * should be.
+ *
+ * Make_TimeStamp Function to set the parent's cmgn field
+ * based on a child's modification time.
+ *
+ * Make_DoAllVar Set up the various local variables for a
+ * target, including the .ALLSRC variable, making
+ * sure that any variable that needs to exist
+ * at the very least has the empty value.
+ *
+ * Make_OODate Determine if a target is out-of-date.
+ *
+ * Make_HandleUse See if a child is a .USE node for a parent
+ * and perform the .USE actions if so.
+ *
+ * Make_ExpandUse Expand .USE nodes
+ */
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+
+static unsigned int checked = 1;/* Sequence # to detect recursion */
+static Lst toBeMade; /* The current fringe of the graph. These
+ * are nodes which await examination by
+ * MakeOODate. It is added to by
+ * Make_Update and subtracted from by
+ * MakeStartJobs */
+
+static int MakeAddChild(void *, void *);
+static int MakeFindChild(void *, void *);
+static int MakeUnmark(void *, void *);
+static int MakeAddAllSrc(void *, void *);
+static int MakeTimeStamp(void *, void *);
+static int MakeHandleUse(void *, void *);
+static Boolean MakeStartJobs(void);
+static int MakePrintStatus(void *, void *);
+static int MakeCheckOrder(void *, void *);
+static int MakeBuildChild(void *, void *);
+static int MakeBuildParent(void *, void *);
+
+MAKE_ATTR_DEAD static void
+make_abort(GNode *gn, int line)
+{
+ static int two = 2;
+
+ fprintf(debug_file, "make_abort from line %d\n", line);
+ Targ_PrintNode(gn, &two);
+ Lst_ForEach(toBeMade, Targ_PrintNode, &two);
+ Targ_PrintGraph(3);
+ abort();
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_TimeStamp --
+ * Set the cmgn field of a parent node based on the mtime stamp in its
+ * child. Called from MakeOODate via Lst_ForEach.
+ *
+ * Input:
+ * pgn the current parent
+ * cgn the child we've just examined
+ *
+ * Results:
+ * Always returns 0.
+ *
+ * Side Effects:
+ * The cmgn of the parent node will be changed if the mtime
+ * field of the child is greater than it.
+ *-----------------------------------------------------------------------
+ */
+int
+Make_TimeStamp(GNode *pgn, GNode *cgn)
+{
+ if (pgn->cmgn == NULL || cgn->mtime > pgn->cmgn->mtime) {
+ pgn->cmgn = cgn;
+ }
+ return (0);
+}
+
+/*
+ * Input:
+ * pgn the current parent
+ * cgn the child we've just examined
+ *
+ */
+static int
+MakeTimeStamp(void *pgn, void *cgn)
+{
+ return Make_TimeStamp((GNode *)pgn, (GNode *)cgn);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_OODate --
+ * See if a given node is out of date with respect to its sources.
+ * Used by Make_Run when deciding which nodes to place on the
+ * toBeMade queue initially and by Make_Update to screen out USE and
+ * EXEC nodes. In the latter case, however, any other sort of node
+ * must be considered out-of-date since at least one of its children
+ * will have been recreated.
+ *
+ * Input:
+ * gn the node to check
+ *
+ * Results:
+ * TRUE if the node is out of date. FALSE otherwise.
+ *
+ * Side Effects:
+ * The mtime field of the node and the cmgn field of its parents
+ * will/may be changed.
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Make_OODate(GNode *gn)
+{
+ Boolean oodate;
+
+ /*
+ * Certain types of targets needn't even be sought as their datedness
+ * doesn't depend on their modification time...
+ */
+ if ((gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC)) == 0) {
+ (void)Dir_MTime(gn, 1);
+ if (DEBUG(MAKE)) {
+ if (gn->mtime != 0) {
+ fprintf(debug_file, "modified %s...", Targ_FmtTime(gn->mtime));
+ } else {
+ fprintf(debug_file, "non-existent...");
+ }
+ }
+ }
+
+ /*
+ * A target is remade in one of the following circumstances:
+ * its modification time is smaller than that of its youngest child
+ * and it would actually be run (has commands or type OP_NOP)
+ * it's the object of a force operator
+ * it has no children, was on the lhs of an operator and doesn't exist
+ * already.
+ *
+ * Libraries are only considered out-of-date if the archive module says
+ * they are.
+ *
+ * These weird rules are brought to you by Backward-Compatibility and
+ * the strange people who wrote 'Make'.
+ */
+ if (gn->type & (OP_USE|OP_USEBEFORE)) {
+ /*
+ * If the node is a USE node it is *never* out of date
+ * no matter *what*.
+ */
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, ".USE node...");
+ }
+ oodate = FALSE;
+ } else if ((gn->type & OP_LIB) &&
+ ((gn->mtime==0) || Arch_IsLib(gn))) {
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "library...");
+ }
+
+ /*
+ * always out of date if no children and :: target
+ * or non-existent.
+ */
+ oodate = (gn->mtime == 0 || Arch_LibOODate(gn) ||
+ (gn->cmgn == NULL && (gn->type & OP_DOUBLEDEP)));
+ } else if (gn->type & OP_JOIN) {
+ /*
+ * A target with the .JOIN attribute is only considered
+ * out-of-date if any of its children was out-of-date.
+ */
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, ".JOIN node...");
+ }
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "source %smade...", gn->flags & CHILDMADE ? "" : "not ");
+ }
+ oodate = (gn->flags & CHILDMADE) ? TRUE : FALSE;
+ } else if (gn->type & (OP_FORCE|OP_EXEC|OP_PHONY)) {
+ /*
+ * A node which is the object of the force (!) operator or which has
+ * the .EXEC attribute is always considered out-of-date.
+ */
+ if (DEBUG(MAKE)) {
+ if (gn->type & OP_FORCE) {
+ fprintf(debug_file, "! operator...");
+ } else if (gn->type & OP_PHONY) {
+ fprintf(debug_file, ".PHONY node...");
+ } else {
+ fprintf(debug_file, ".EXEC node...");
+ }
+ }
+ oodate = TRUE;
+ } else if ((gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime) ||
+ (gn->cmgn == NULL &&
+ ((gn->mtime == 0 && !(gn->type & OP_OPTIONAL))
+ || gn->type & OP_DOUBLEDEP)))
+ {
+ /*
+ * A node whose modification time is less than that of its
+ * youngest child or that has no children (cmgn == NULL) and
+ * either doesn't exist (mtime == 0) and it isn't optional
+ * or was the object of a * :: operator is out-of-date.
+ * Why? Because that's the way Make does it.
+ */
+ if (DEBUG(MAKE)) {
+ if (gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime) {
+ fprintf(debug_file, "modified before source %s...",
+ gn->cmgn->path ? gn->cmgn->path : gn->cmgn->name);
+ } else if (gn->mtime == 0) {
+ fprintf(debug_file, "non-existent and no sources...");
+ } else {
+ fprintf(debug_file, ":: operator and no sources...");
+ }
+ }
+ oodate = TRUE;
+ } else {
+ /*
+ * When a non-existing child with no sources
+ * (such as a typically used FORCE source) has been made and
+ * the target of the child (usually a directory) has the same
+ * timestamp as the timestamp just given to the non-existing child
+ * after it was considered made.
+ */
+ if (DEBUG(MAKE)) {
+ if (gn->flags & FORCE)
+ fprintf(debug_file, "non existing child...");
+ }
+ oodate = (gn->flags & FORCE) ? TRUE : FALSE;
+ }
+
+#ifdef USE_META
+ if (useMeta) {
+ oodate = meta_oodate(gn, oodate);
+ }
+#endif
+
+ /*
+ * If the target isn't out-of-date, the parents need to know its
+ * modification time. Note that targets that appear to be out-of-date
+ * but aren't, because they have no commands and aren't of type OP_NOP,
+ * have their mtime stay below their children's mtime to keep parents from
+ * thinking they're out-of-date.
+ */
+ if (!oodate) {
+ Lst_ForEach(gn->parents, MakeTimeStamp, gn);
+ }
+
+ return (oodate);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeAddChild --
+ * Function used by Make_Run to add a child to the list l.
+ * It will only add the child if its make field is FALSE.
+ *
+ * Input:
+ * gnp the node to add
+ * lp the list to which to add it
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * The given list is extended
+ *-----------------------------------------------------------------------
+ */
+static int
+MakeAddChild(void *gnp, void *lp)
+{
+ GNode *gn = (GNode *)gnp;
+ Lst l = (Lst) lp;
+
+ if ((gn->flags & REMAKE) == 0 && !(gn->type & (OP_USE|OP_USEBEFORE))) {
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "MakeAddChild: need to examine %s%s\n",
+ gn->name, gn->cohort_num);
+ (void)Lst_EnQueue(l, gn);
+ }
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeFindChild --
+ * Function used by Make_Run to find the pathname of a child
+ * that was already made.
+ *
+ * Input:
+ * gnp the node to find
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * The path and mtime of the node and the cmgn of the parent are
+ * updated; the unmade children count of the parent is decremented.
+ *-----------------------------------------------------------------------
+ */
+static int
+MakeFindChild(void *gnp, void *pgnp)
+{
+ GNode *gn = (GNode *)gnp;
+ GNode *pgn = (GNode *)pgnp;
+
+ (void)Dir_MTime(gn, 0);
+ Make_TimeStamp(pgn, gn);
+ pgn->unmade--;
+
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_HandleUse --
+ * Function called by Make_Run and SuffApplyTransform on the downward
+ * pass to handle .USE and transformation nodes. It implements the
+ * .USE and transformation functionality by copying the node's commands,
+ * type flags and children to the parent node.
+ *
+ * A .USE node is much like an explicit transformation rule, except
+ * its commands are always added to the target node, even if the
+ * target already has commands.
+ *
+ * Input:
+ * cgn The .USE node
+ * pgn The target of the .USE node
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * Children and commands may be added to the parent and the parent's
+ * type may be changed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Make_HandleUse(GNode *cgn, GNode *pgn)
+{
+ LstNode ln; /* An element in the children list */
+
+#ifdef DEBUG_SRC
+ if ((cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM)) == 0) {
+ fprintf(debug_file, "Make_HandleUse: called for plain node %s\n", cgn->name);
+ return;
+ }
+#endif
+
+ if ((cgn->type & (OP_USE|OP_USEBEFORE)) || Lst_IsEmpty(pgn->commands)) {
+ if (cgn->type & OP_USEBEFORE) {
+ /*
+ * .USEBEFORE --
+ * prepend the child's commands to the parent.
+ */
+ Lst cmds = pgn->commands;
+ pgn->commands = Lst_Duplicate(cgn->commands, NULL);
+ (void)Lst_Concat(pgn->commands, cmds, LST_CONCNEW);
+ Lst_Destroy(cmds, NULL);
+ } else {
+ /*
+ * .USE or target has no commands --
+ * append the child's commands to the parent.
+ */
+ (void)Lst_Concat(pgn->commands, cgn->commands, LST_CONCNEW);
+ }
+ }
+
+ if (Lst_Open(cgn->children) == SUCCESS) {
+ while ((ln = Lst_Next(cgn->children)) != NULL) {
+ GNode *tgn, *gn = (GNode *)Lst_Datum(ln);
+
+ /*
+ * Expand variables in the .USE node's name
+ * and save the unexpanded form.
+ * We don't need to do this for commands.
+ * They get expanded properly when we execute.
+ */
+ if (gn->uname == NULL) {
+ gn->uname = gn->name;
+ } else {
+ free(gn->name);
+ }
+ gn->name = Var_Subst(NULL, gn->uname, pgn, VARF_WANTRES);
+ if (gn->name && gn->uname && strcmp(gn->name, gn->uname) != 0) {
+ /* See if we have a target for this node. */
+ tgn = Targ_FindNode(gn->name, TARG_NOCREATE);
+ if (tgn != NULL)
+ gn = tgn;
+ }
+
+ (void)Lst_AtEnd(pgn->children, gn);
+ (void)Lst_AtEnd(gn->parents, pgn);
+ pgn->unmade += 1;
+ }
+ Lst_Close(cgn->children);
+ }
+
+ pgn->type |= cgn->type & ~(OP_OPMASK|OP_USE|OP_USEBEFORE|OP_TRANSFORM);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeHandleUse --
+ * Callback function for Lst_ForEach, used by Make_Run on the downward
+ * pass to handle .USE nodes. Should be called before the children
+ * are enqueued to be looked at by MakeAddChild.
+ * This function calls Make_HandleUse to copy the .USE node's commands,
+ * type flags and children to the parent node.
+ *
+ * Input:
+ * cgnp the child we've just examined
+ * pgnp the current parent
+ *
+ * Results:
+ * returns 0.
+ *
+ * Side Effects:
+ * After expansion, .USE child nodes are removed from the parent
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+MakeHandleUse(void *cgnp, void *pgnp)
+{
+ GNode *cgn = (GNode *)cgnp;
+ GNode *pgn = (GNode *)pgnp;
+ LstNode ln; /* An element in the children list */
+ int unmarked;
+
+ unmarked = ((cgn->type & OP_MARK) == 0);
+ cgn->type |= OP_MARK;
+
+ if ((cgn->type & (OP_USE|OP_USEBEFORE)) == 0)
+ return (0);
+
+ if (unmarked)
+ Make_HandleUse(cgn, pgn);
+
+ /*
+ * This child node is now "made", so we decrement the count of
+ * unmade children in the parent... We also remove the child
+ * from the parent's list to accurately reflect the number of decent
+ * children the parent has. This is used by Make_Run to decide
+ * whether to queue the parent or examine its children...
+ */
+ if ((ln = Lst_Member(pgn->children, cgn)) != NULL) {
+ Lst_Remove(pgn->children, ln);
+ pgn->unmade--;
+ }
+ return (0);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_Recheck --
+ * Check the modification time of a gnode, and update it as described
+ * in the comments below.
+ *
+ * Results:
+ * returns 0 if the gnode does not exist, or its filesystem
+ * time if it does.
+ *
+ * Side Effects:
+ * the gnode's modification time and path name are affected.
+ *
+ *-----------------------------------------------------------------------
+ */
+time_t
+Make_Recheck(GNode *gn)
+{
+ time_t mtime = Dir_MTime(gn, 1);
+
+#ifndef RECHECK
+ /*
+ * We can't re-stat the thing, but we can at least take care of rules
+ * where a target depends on a source that actually creates the
+ * target, but only if it has changed, e.g.
+ *
+ * parse.h : parse.o
+ *
+ * parse.o : parse.y
+ * yacc -d parse.y
+ * cc -c y.tab.c
+ * mv y.tab.o parse.o
+ * cmp -s y.tab.h parse.h || mv y.tab.h parse.h
+ *
+ * In this case, if the definitions produced by yacc haven't changed
+ * from before, parse.h won't have been updated and gn->mtime will
+ * reflect the current modification time for parse.h. This is
+ * something of a kludge, I admit, but it's a useful one..
+ * XXX: People like to use a rule like
+ *
+ * FRC:
+ *
+ * To force things that depend on FRC to be made, so we have to
+ * check for gn->children being empty as well...
+ */
+ if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) {
+ gn->mtime = now;
+ }
+#else
+ /*
+ * This is what Make does and it's actually a good thing, as it
+ * allows rules like
+ *
+ * cmp -s y.tab.h parse.h || cp y.tab.h parse.h
+ *
+ * to function as intended. Unfortunately, thanks to the stateless
+ * nature of NFS (by which I mean the loose coupling of two clients
+ * using the same file from a common server), there are times
+ * when the modification time of a file created on a remote
+ * machine will not be modified before the local stat() implied by
+ * the Dir_MTime occurs, thus leading us to believe that the file
+ * is unchanged, wreaking havoc with files that depend on this one.
+ *
+ * I have decided it is better to make too much than to make too
+ * little, so this stuff is commented out unless you're sure it's ok.
+ * -- ardeb 1/12/88
+ */
+ /*
+ * Christos, 4/9/92: If we are saving commands pretend that
+ * the target is made now. Otherwise archives with ... rules
+ * don't work!
+ */
+ if (NoExecute(gn) || (gn->type & OP_SAVE_CMDS) ||
+ (mtime == 0 && !(gn->type & OP_WAIT))) {
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, " recheck(%s): update time from %s to now\n",
+ gn->name, Targ_FmtTime(gn->mtime));
+ }
+ gn->mtime = now;
+ }
+ else {
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, " recheck(%s): current update time: %s\n",
+ gn->name, Targ_FmtTime(gn->mtime));
+ }
+ }
+#endif
+ return mtime;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_Update --
+ * Perform update on the parents of a node. Used by JobFinish once
+ * a node has been dealt with and by MakeStartJobs if it finds an
+ * up-to-date node.
+ *
+ * Input:
+ * cgn the child node
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * The unmade field of pgn is decremented and pgn may be placed on
+ * the toBeMade queue if this field becomes 0.
+ *
+ * If the child was made, the parent's flag CHILDMADE field will be
+ * set true.
+ *
+ * If the child is not up-to-date and still does not exist,
+ * set the FORCE flag on the parents.
+ *
+ * If the child wasn't made, the cmgn field of the parent will be
+ * altered if the child's mtime is big enough.
+ *
+ * Finally, if the child is the implied source for the parent, the
+ * parent's IMPSRC variable is set appropriately.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Make_Update(GNode *cgn)
+{
+ GNode *pgn; /* the parent node */
+ char *cname; /* the child's name */
+ LstNode ln; /* Element in parents and iParents lists */
+ time_t mtime = -1;
+ char *p1;
+ Lst parents;
+ GNode *centurion;
+
+ /* It is save to re-examine any nodes again */
+ checked++;
+
+ cname = Var_Value(TARGET, cgn, &p1);
+ free(p1);
+
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "Make_Update: %s%s\n", cgn->name, cgn->cohort_num);
+
+ /*
+ * If the child was actually made, see what its modification time is
+ * now -- some rules won't actually update the file. If the file still
+ * doesn't exist, make its mtime now.
+ */
+ if (cgn->made != UPTODATE) {
+ mtime = Make_Recheck(cgn);
+ }
+
+ /*
+ * If this is a `::' node, we must consult its first instance
+ * which is where all parents are linked.
+ */
+ if ((centurion = cgn->centurion) != NULL) {
+ if (!Lst_IsEmpty(cgn->parents))
+ Punt("%s%s: cohort has parents", cgn->name, cgn->cohort_num);
+ centurion->unmade_cohorts -= 1;
+ if (centurion->unmade_cohorts < 0)
+ Error("Graph cycles through centurion %s", centurion->name);
+ } else {
+ centurion = cgn;
+ }
+ parents = centurion->parents;
+
+ /* If this was a .ORDER node, schedule the RHS */
+ Lst_ForEach(centurion->order_succ, MakeBuildParent, Lst_First(toBeMade));
+
+ /* Now mark all the parents as having one less unmade child */
+ if (Lst_Open(parents) == SUCCESS) {
+ while ((ln = Lst_Next(parents)) != NULL) {
+ pgn = (GNode *)Lst_Datum(ln);
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "inspect parent %s%s: flags %x, "
+ "type %x, made %d, unmade %d ",
+ pgn->name, pgn->cohort_num, pgn->flags,
+ pgn->type, pgn->made, pgn->unmade-1);
+
+ if (!(pgn->flags & REMAKE)) {
+ /* This parent isn't needed */
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "- not needed\n");
+ continue;
+ }
+ if (mtime == 0 && !(cgn->type & OP_WAIT))
+ pgn->flags |= FORCE;
+
+ /*
+ * If the parent has the .MADE attribute, its timestamp got
+ * updated to that of its newest child, and its unmake
+ * child count got set to zero in Make_ExpandUse().
+ * However other things might cause us to build one of its
+ * children - and so we mustn't do any processing here when
+ * the child build finishes.
+ */
+ if (pgn->type & OP_MADE) {
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "- .MADE\n");
+ continue;
+ }
+
+ if ( ! (cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE))) {
+ if (cgn->made == MADE)
+ pgn->flags |= CHILDMADE;
+ (void)Make_TimeStamp(pgn, cgn);
+ }
+
+ /*
+ * A parent must wait for the completion of all instances
+ * of a `::' dependency.
+ */
+ if (centurion->unmade_cohorts != 0 || centurion->made < MADE) {
+ if (DEBUG(MAKE))
+ fprintf(debug_file,
+ "- centurion made %d, %d unmade cohorts\n",
+ centurion->made, centurion->unmade_cohorts);
+ continue;
+ }
+
+ /* One more child of this parent is now made */
+ pgn->unmade -= 1;
+ if (pgn->unmade < 0) {
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "Graph cycles through %s%s\n",
+ pgn->name, pgn->cohort_num);
+ Targ_PrintGraph(2);
+ }
+ Error("Graph cycles through %s%s", pgn->name, pgn->cohort_num);
+ }
+
+ /* We must always rescan the parents of .WAIT and .ORDER nodes. */
+ if (pgn->unmade != 0 && !(centurion->type & OP_WAIT)
+ && !(centurion->flags & DONE_ORDER)) {
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "- unmade children\n");
+ continue;
+ }
+ if (pgn->made != DEFERRED) {
+ /*
+ * Either this parent is on a different branch of the tree,
+ * or it on the RHS of a .WAIT directive
+ * or it is already on the toBeMade list.
+ */
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "- not deferred\n");
+ continue;
+ }
+ if (pgn->order_pred
+ && Lst_ForEach(pgn->order_pred, MakeCheckOrder, 0)) {
+ /* A .ORDER rule stops us building this */
+ continue;
+ }
+ if (DEBUG(MAKE)) {
+ static int two = 2;
+ fprintf(debug_file, "- %s%s made, schedule %s%s (made %d)\n",
+ cgn->name, cgn->cohort_num,
+ pgn->name, pgn->cohort_num, pgn->made);
+ Targ_PrintNode(pgn, &two);
+ }
+ /* Ok, we can schedule the parent again */
+ pgn->made = REQUESTED;
+ (void)Lst_EnQueue(toBeMade, pgn);
+ }
+ Lst_Close(parents);
+ }
+
+ /*
+ * Set the .PREFIX and .IMPSRC variables for all the implied parents
+ * of this node.
+ */
+ if (Lst_Open(cgn->iParents) == SUCCESS) {
+ char *cpref = Var_Value(PREFIX, cgn, &p1);
+
+ while ((ln = Lst_Next(cgn->iParents)) != NULL) {
+ pgn = (GNode *)Lst_Datum(ln);
+ if (pgn->flags & REMAKE) {
+ Var_Set(IMPSRC, cname, pgn, 0);
+ if (cpref != NULL)
+ Var_Set(PREFIX, cpref, pgn, 0);
+ }
+ }
+ free(p1);
+ Lst_Close(cgn->iParents);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeAddAllSrc --
+ * Add a child's name to the ALLSRC and OODATE variables of the given
+ * node. Called from Make_DoAllVar via Lst_ForEach. A child is added only
+ * if it has not been given the .EXEC, .USE or .INVISIBLE attributes.
+ * .EXEC and .USE children are very rarely going to be files, so...
+ * If the child is a .JOIN node, its ALLSRC is propagated to the parent.
+ *
+ * A child is added to the OODATE variable if its modification time is
+ * later than that of its parent, as defined by Make, except if the
+ * parent is a .JOIN node. In that case, it is only added to the OODATE
+ * variable if it was actually made (since .JOIN nodes don't have
+ * modification times, the comparison is rather unfair...)..
+ *
+ * Results:
+ * Always returns 0
+ *
+ * Side Effects:
+ * The ALLSRC variable for the given node is extended.
+ *-----------------------------------------------------------------------
+ */
+static int
+MakeUnmark(void *cgnp, void *pgnp MAKE_ATTR_UNUSED)
+{
+ GNode *cgn = (GNode *)cgnp;
+
+ cgn->type &= ~OP_MARK;
+ return (0);
+}
+
+/*
+ * Input:
+ * cgnp The child to add
+ * pgnp The parent to whose ALLSRC variable it should
+ * be added
+ *
+ */
+static int
+MakeAddAllSrc(void *cgnp, void *pgnp)
+{
+ GNode *cgn = (GNode *)cgnp;
+ GNode *pgn = (GNode *)pgnp;
+
+ if (cgn->type & OP_MARK)
+ return (0);
+ cgn->type |= OP_MARK;
+
+ if ((cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE)) == 0) {
+ char *child, *allsrc;
+ char *p1 = NULL, *p2 = NULL;
+
+ if (cgn->type & OP_ARCHV)
+ child = Var_Value(MEMBER, cgn, &p1);
+ else
+ child = cgn->path ? cgn->path : cgn->name;
+ if (cgn->type & OP_JOIN) {
+ allsrc = Var_Value(ALLSRC, cgn, &p2);
+ } else {
+ allsrc = child;
+ }
+ if (allsrc != NULL)
+ Var_Append(ALLSRC, allsrc, pgn);
+ free(p2);
+ if (pgn->type & OP_JOIN) {
+ if (cgn->made == MADE) {
+ Var_Append(OODATE, child, pgn);
+ }
+ } else if ((pgn->mtime < cgn->mtime) ||
+ (cgn->mtime >= now && cgn->made == MADE))
+ {
+ /*
+ * It goes in the OODATE variable if the parent is younger than the
+ * child or if the child has been modified more recently than
+ * the start of the make. This is to keep pmake from getting
+ * confused if something else updates the parent after the
+ * make starts (shouldn't happen, I know, but sometimes it
+ * does). In such a case, if we've updated the kid, the parent
+ * is likely to have a modification time later than that of
+ * the kid and anything that relies on the OODATE variable will
+ * be hosed.
+ *
+ * XXX: This will cause all made children to go in the OODATE
+ * variable, even if they're not touched, if RECHECK isn't defined,
+ * since cgn->mtime is set to now in Make_Update. According to
+ * some people, this is good...
+ */
+ Var_Append(OODATE, child, pgn);
+ }
+ free(p1);
+ }
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_DoAllVar --
+ * Set up the ALLSRC and OODATE variables. Sad to say, it must be
+ * done separately, rather than while traversing the graph. This is
+ * because Make defined OODATE to contain all sources whose modification
+ * times were later than that of the target, *not* those sources that
+ * were out-of-date. Since in both compatibility and native modes,
+ * the modification time of the parent isn't found until the child
+ * has been dealt with, we have to wait until now to fill in the
+ * variable. As for ALLSRC, the ordering is important and not
+ * guaranteed when in native mode, so it must be set here, too.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The ALLSRC and OODATE variables of the given node is filled in.
+ * If the node is a .JOIN node, its TARGET variable will be set to
+ * match its ALLSRC variable.
+ *-----------------------------------------------------------------------
+ */
+void
+Make_DoAllVar(GNode *gn)
+{
+ if (gn->flags & DONE_ALLSRC)
+ return;
+
+ Lst_ForEach(gn->children, MakeUnmark, gn);
+ Lst_ForEach(gn->children, MakeAddAllSrc, gn);
+
+ if (!Var_Exists (OODATE, gn)) {
+ Var_Set(OODATE, "", gn, 0);
+ }
+ if (!Var_Exists (ALLSRC, gn)) {
+ Var_Set(ALLSRC, "", gn, 0);
+ }
+
+ if (gn->type & OP_JOIN) {
+ char *p1;
+ Var_Set(TARGET, Var_Value(ALLSRC, gn, &p1), gn, 0);
+ free(p1);
+ }
+ gn->flags |= DONE_ALLSRC;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeStartJobs --
+ * Start as many jobs as possible.
+ *
+ * Results:
+ * If the query flag was given to pmake, no job will be started,
+ * but as soon as an out-of-date target is found, this function
+ * returns TRUE. At all other times, this function returns FALSE.
+ *
+ * Side Effects:
+ * Nodes are removed from the toBeMade queue and job table slots
+ * are filled.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+static int
+MakeCheckOrder(void *v_bn, void *ignore MAKE_ATTR_UNUSED)
+{
+ GNode *bn = v_bn;
+
+ if (bn->made >= MADE || !(bn->flags & REMAKE))
+ return 0;
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "MakeCheckOrder: Waiting for .ORDER node %s%s\n",
+ bn->name, bn->cohort_num);
+ return 1;
+}
+
+static int
+MakeBuildChild(void *v_cn, void *toBeMade_next)
+{
+ GNode *cn = v_cn;
+
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "MakeBuildChild: inspect %s%s, made %d, type %x\n",
+ cn->name, cn->cohort_num, cn->made, cn->type);
+ if (cn->made > DEFERRED)
+ return 0;
+
+ /* If this node is on the RHS of a .ORDER, check LHSs. */
+ if (cn->order_pred && Lst_ForEach(cn->order_pred, MakeCheckOrder, 0)) {
+ /* Can't build this (or anything else in this child list) yet */
+ cn->made = DEFERRED;
+ return 0; /* but keep looking */
+ }
+
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "MakeBuildChild: schedule %s%s\n",
+ cn->name, cn->cohort_num);
+
+ cn->made = REQUESTED;
+ if (toBeMade_next == NULL)
+ Lst_AtEnd(toBeMade, cn);
+ else
+ Lst_InsertBefore(toBeMade, toBeMade_next, cn);
+
+ if (cn->unmade_cohorts != 0)
+ Lst_ForEach(cn->cohorts, MakeBuildChild, toBeMade_next);
+
+ /*
+ * If this node is a .WAIT node with unmade chlidren
+ * then don't add the next sibling.
+ */
+ return cn->type & OP_WAIT && cn->unmade > 0;
+}
+
+/* When a .ORDER LHS node completes we do this on each RHS */
+static int
+MakeBuildParent(void *v_pn, void *toBeMade_next)
+{
+ GNode *pn = v_pn;
+
+ if (pn->made != DEFERRED)
+ return 0;
+
+ if (MakeBuildChild(pn, toBeMade_next) == 0) {
+ /* Mark so that when this node is built we reschedule its parents */
+ pn->flags |= DONE_ORDER;
+ }
+
+ return 0;
+}
+
+static Boolean
+MakeStartJobs(void)
+{
+ GNode *gn;
+ int have_token = 0;
+
+ while (!Lst_IsEmpty (toBeMade)) {
+ /* Get token now to avoid cycling job-list when we only have 1 token */
+ if (!have_token && !Job_TokenWithdraw())
+ break;
+ have_token = 1;
+
+ gn = (GNode *)Lst_DeQueue(toBeMade);
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "Examining %s%s...\n",
+ gn->name, gn->cohort_num);
+
+ if (gn->made != REQUESTED) {
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "state %d\n", gn->made);
+
+ make_abort(gn, __LINE__);
+ }
+
+ if (gn->checked == checked) {
+ /* We've already looked at this node since a job finished... */
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "already checked %s%s\n",
+ gn->name, gn->cohort_num);
+ gn->made = DEFERRED;
+ continue;
+ }
+ gn->checked = checked;
+
+ if (gn->unmade != 0) {
+ /*
+ * We can't build this yet, add all unmade children to toBeMade,
+ * just before the current first element.
+ */
+ gn->made = DEFERRED;
+ Lst_ForEach(gn->children, MakeBuildChild, Lst_First(toBeMade));
+ /* and drop this node on the floor */
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "dropped %s%s\n", gn->name, gn->cohort_num);
+ continue;
+ }
+
+ gn->made = BEINGMADE;
+ if (Make_OODate(gn)) {
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "out-of-date\n");
+ }
+ if (queryFlag) {
+ return (TRUE);
+ }
+ Make_DoAllVar(gn);
+ Job_Make(gn);
+ have_token = 0;
+ } else {
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "up-to-date\n");
+ }
+ gn->made = UPTODATE;
+ if (gn->type & OP_JOIN) {
+ /*
+ * Even for an up-to-date .JOIN node, we need it to have its
+ * context variables so references to it get the correct
+ * value for .TARGET when building up the context variables
+ * of its parent(s)...
+ */
+ Make_DoAllVar(gn);
+ }
+ Make_Update(gn);
+ }
+ }
+
+ if (have_token)
+ Job_TokenReturn();
+
+ return (FALSE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakePrintStatus --
+ * Print the status of a top-level node, viz. it being up-to-date
+ * already or not created due to an error in a lower level.
+ * Callback function for Make_Run via Lst_ForEach.
+ *
+ * Input:
+ * gnp Node to examine
+ * cyclep True if gn->unmade being non-zero implies a
+ * cycle in the graph, not an error in an
+ * inferior.
+ *
+ * Results:
+ * Always returns 0.
+ *
+ * Side Effects:
+ * A message may be printed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+MakePrintStatusOrder(void *ognp, void *gnp)
+{
+ GNode *ogn = ognp;
+ GNode *gn = gnp;
+
+ if (!(ogn->flags & REMAKE) || ogn->made > REQUESTED)
+ /* not waiting for this one */
+ return 0;
+
+ printf(" `%s%s' has .ORDER dependency against %s%s "
+ "(made %d, flags %x, type %x)\n",
+ gn->name, gn->cohort_num,
+ ogn->name, ogn->cohort_num, ogn->made, ogn->flags, ogn->type);
+ if (DEBUG(MAKE) && debug_file != stdout)
+ fprintf(debug_file, " `%s%s' has .ORDER dependency against %s%s "
+ "(made %d, flags %x, type %x)\n",
+ gn->name, gn->cohort_num,
+ ogn->name, ogn->cohort_num, ogn->made, ogn->flags, ogn->type);
+ return 0;
+}
+
+static int
+MakePrintStatus(void *gnp, void *v_errors)
+{
+ GNode *gn = (GNode *)gnp;
+ int *errors = v_errors;
+
+ if (gn->flags & DONECYCLE)
+ /* We've completely processed this node before, don't do it again. */
+ return 0;
+
+ if (gn->unmade == 0) {
+ gn->flags |= DONECYCLE;
+ switch (gn->made) {
+ case UPTODATE:
+ printf("`%s%s' is up to date.\n", gn->name, gn->cohort_num);
+ break;
+ case MADE:
+ break;
+ case UNMADE:
+ case DEFERRED:
+ case REQUESTED:
+ case BEINGMADE:
+ (*errors)++;
+ printf("`%s%s' was not built (made %d, flags %x, type %x)!\n",
+ gn->name, gn->cohort_num, gn->made, gn->flags, gn->type);
+ if (DEBUG(MAKE) && debug_file != stdout)
+ fprintf(debug_file,
+ "`%s%s' was not built (made %d, flags %x, type %x)!\n",
+ gn->name, gn->cohort_num, gn->made, gn->flags, gn->type);
+ /* Most likely problem is actually caused by .ORDER */
+ Lst_ForEach(gn->order_pred, MakePrintStatusOrder, gn);
+ break;
+ default:
+ /* Errors - already counted */
+ printf("`%s%s' not remade because of errors.\n",
+ gn->name, gn->cohort_num);
+ if (DEBUG(MAKE) && debug_file != stdout)
+ fprintf(debug_file, "`%s%s' not remade because of errors.\n",
+ gn->name, gn->cohort_num);
+ break;
+ }
+ return 0;
+ }
+
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "MakePrintStatus: %s%s has %d unmade children\n",
+ gn->name, gn->cohort_num, gn->unmade);
+ /*
+ * If printing cycles and came to one that has unmade children,
+ * print out the cycle by recursing on its children.
+ */
+ if (!(gn->flags & CYCLE)) {
+ /* Fist time we've seen this node, check all children */
+ gn->flags |= CYCLE;
+ Lst_ForEach(gn->children, MakePrintStatus, errors);
+ /* Mark that this node needn't be processed again */
+ gn->flags |= DONECYCLE;
+ return 0;
+ }
+
+ /* Only output the error once per node */
+ gn->flags |= DONECYCLE;
+ Error("Graph cycles through `%s%s'", gn->name, gn->cohort_num);
+ if ((*errors)++ > 100)
+ /* Abandon the whole error report */
+ return 1;
+
+ /* Reporting for our children will give the rest of the loop */
+ Lst_ForEach(gn->children, MakePrintStatus, errors);
+ return 0;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_ExpandUse --
+ * Expand .USE nodes and create a new targets list
+ *
+ * Input:
+ * targs the initial list of targets
+ *
+ * Side Effects:
+ *-----------------------------------------------------------------------
+ */
+void
+Make_ExpandUse(Lst targs)
+{
+ GNode *gn; /* a temporary pointer */
+ Lst examine; /* List of targets to examine */
+
+ examine = Lst_Duplicate(targs, NULL);
+
+ /*
+ * Make an initial downward pass over the graph, marking nodes to be made
+ * as we go down. We call Suff_FindDeps to find where a node is and
+ * to get some children for it if it has none and also has no commands.
+ * If the node is a leaf, we stick it on the toBeMade queue to
+ * be looked at in a minute, otherwise we add its children to our queue
+ * and go on about our business.
+ */
+ while (!Lst_IsEmpty (examine)) {
+ gn = (GNode *)Lst_DeQueue(examine);
+
+ if (gn->flags & REMAKE)
+ /* We've looked at this one already */
+ continue;
+ gn->flags |= REMAKE;
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "Make_ExpandUse: examine %s%s\n",
+ gn->name, gn->cohort_num);
+
+ if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts)) {
+ /* Append all the 'cohorts' to the list of things to examine */
+ Lst new;
+ new = Lst_Duplicate(gn->cohorts, NULL);
+ Lst_Concat(new, examine, LST_CONCLINK);
+ examine = new;
+ }
+
+ /*
+ * Apply any .USE rules before looking for implicit dependencies
+ * to make sure everything has commands that should...
+ * Make sure that the TARGET is set, so that we can make
+ * expansions.
+ */
+ if (gn->type & OP_ARCHV) {
+ char *eoa, *eon;
+ eoa = strchr(gn->name, '(');
+ eon = strchr(gn->name, ')');
+ if (eoa == NULL || eon == NULL)
+ continue;
+ *eoa = '\0';
+ *eon = '\0';
+ Var_Set(MEMBER, eoa + 1, gn, 0);
+ Var_Set(ARCHIVE, gn->name, gn, 0);
+ *eoa = '(';
+ *eon = ')';
+ }
+
+ (void)Dir_MTime(gn, 0);
+ Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0);
+ Lst_ForEach(gn->children, MakeUnmark, gn);
+ Lst_ForEach(gn->children, MakeHandleUse, gn);
+
+ if ((gn->type & OP_MADE) == 0)
+ Suff_FindDeps(gn);
+ else {
+ /* Pretend we made all this node's children */
+ Lst_ForEach(gn->children, MakeFindChild, gn);
+ if (gn->unmade != 0)
+ printf("Warning: %s%s still has %d unmade children\n",
+ gn->name, gn->cohort_num, gn->unmade);
+ }
+
+ if (gn->unmade != 0)
+ Lst_ForEach(gn->children, MakeAddChild, examine);
+ }
+
+ Lst_Destroy(examine, NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_ProcessWait --
+ * Convert .WAIT nodes into dependencies
+ *
+ * Input:
+ * targs the initial list of targets
+ *
+ *-----------------------------------------------------------------------
+ */
+
+static int
+link_parent(void *cnp, void *pnp)
+{
+ GNode *cn = cnp;
+ GNode *pn = pnp;
+
+ Lst_AtEnd(pn->children, cn);
+ Lst_AtEnd(cn->parents, pn);
+ pn->unmade++;
+ return 0;
+}
+
+static int
+add_wait_dep(void *v_cn, void *v_wn)
+{
+ GNode *cn = v_cn;
+ GNode *wn = v_wn;
+
+ if (cn == wn)
+ return 1;
+
+ if (cn == NULL || wn == NULL) {
+ printf("bad wait dep %p %p\n", cn, wn);
+ exit(4);
+ }
+ if (DEBUG(MAKE))
+ fprintf(debug_file, ".WAIT: add dependency %s%s -> %s\n",
+ cn->name, cn->cohort_num, wn->name);
+
+ Lst_AtEnd(wn->children, cn);
+ wn->unmade++;
+ Lst_AtEnd(cn->parents, wn);
+ return 0;
+}
+
+static void
+Make_ProcessWait(Lst targs)
+{
+ GNode *pgn; /* 'parent' node we are examining */
+ GNode *cgn; /* Each child in turn */
+ LstNode owln; /* Previous .WAIT node */
+ Lst examine; /* List of targets to examine */
+ LstNode ln;
+
+ /*
+ * We need all the nodes to have a common parent in order for the
+ * .WAIT and .ORDER scheduling to work.
+ * Perhaps this should be done earlier...
+ */
+
+ pgn = Targ_NewGN(".MAIN");
+ pgn->flags = REMAKE;
+ pgn->type = OP_PHONY | OP_DEPENDS;
+ /* Get it displayed in the diag dumps */
+ Lst_AtFront(Targ_List(), pgn);
+
+ Lst_ForEach(targs, link_parent, pgn);
+
+ /* Start building with the 'dummy' .MAIN' node */
+ MakeBuildChild(pgn, NULL);
+
+ examine = Lst_Init(FALSE);
+ Lst_AtEnd(examine, pgn);
+
+ while (!Lst_IsEmpty (examine)) {
+ pgn = Lst_DeQueue(examine);
+
+ /* We only want to process each child-list once */
+ if (pgn->flags & DONE_WAIT)
+ continue;
+ pgn->flags |= DONE_WAIT;
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "Make_ProcessWait: examine %s\n", pgn->name);
+
+ if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (pgn->cohorts)) {
+ /* Append all the 'cohorts' to the list of things to examine */
+ Lst new;
+ new = Lst_Duplicate(pgn->cohorts, NULL);
+ Lst_Concat(new, examine, LST_CONCLINK);
+ examine = new;
+ }
+
+ owln = Lst_First(pgn->children);
+ Lst_Open(pgn->children);
+ for (; (ln = Lst_Next(pgn->children)) != NULL; ) {
+ cgn = Lst_Datum(ln);
+ if (cgn->type & OP_WAIT) {
+ /* Make the .WAIT node depend on the previous children */
+ Lst_ForEachFrom(pgn->children, owln, add_wait_dep, cgn);
+ owln = ln;
+ } else {
+ Lst_AtEnd(examine, cgn);
+ }
+ }
+ Lst_Close(pgn->children);
+ }
+
+ Lst_Destroy(examine, NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_Run --
+ * Initialize the nodes to remake and the list of nodes which are
+ * ready to be made by doing a breadth-first traversal of the graph
+ * starting from the nodes in the given list. Once this traversal
+ * is finished, all the 'leaves' of the graph are in the toBeMade
+ * queue.
+ * Using this queue and the Job module, work back up the graph,
+ * calling on MakeStartJobs to keep the job table as full as
+ * possible.
+ *
+ * Input:
+ * targs the initial list of targets
+ *
+ * Results:
+ * TRUE if work was done. FALSE otherwise.
+ *
+ * Side Effects:
+ * The make field of all nodes involved in the creation of the given
+ * targets is set to 1. The toBeMade list is set to contain all the
+ * 'leaves' of these subgraphs.
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Make_Run(Lst targs)
+{
+ int errors; /* Number of errors the Job module reports */
+
+ /* Start trying to make the current targets... */
+ toBeMade = Lst_Init(FALSE);
+
+ Make_ExpandUse(targs);
+ Make_ProcessWait(targs);
+
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "#***# full graph\n");
+ Targ_PrintGraph(1);
+ }
+
+ if (queryFlag) {
+ /*
+ * We wouldn't do any work unless we could start some jobs in the
+ * next loop... (we won't actually start any, of course, this is just
+ * to see if any of the targets was out of date)
+ */
+ return (MakeStartJobs());
+ }
+ /*
+ * Initialization. At the moment, no jobs are running and until some
+ * get started, nothing will happen since the remaining upward
+ * traversal of the graph is performed by the routines in job.c upon
+ * the finishing of a job. So we fill the Job table as much as we can
+ * before going into our loop.
+ */
+ (void)MakeStartJobs();
+
+ /*
+ * Main Loop: The idea here is that the ending of jobs will take
+ * care of the maintenance of data structures and the waiting for output
+ * will cause us to be idle most of the time while our children run as
+ * much as possible. Because the job table is kept as full as possible,
+ * the only time when it will be empty is when all the jobs which need
+ * running have been run, so that is the end condition of this loop.
+ * Note that the Job module will exit if there were any errors unless the
+ * keepgoing flag was given.
+ */
+ while (!Lst_IsEmpty(toBeMade) || jobTokensRunning > 0) {
+ Job_CatchOutput();
+ (void)MakeStartJobs();
+ }
+
+ errors = Job_Finish();
+
+ /*
+ * Print the final status of each target. E.g. if it wasn't made
+ * because some inferior reported an error.
+ */
+ if (DEBUG(MAKE))
+ fprintf(debug_file, "done: errors %d\n", errors);
+ if (errors == 0) {
+ Lst_ForEach(targs, MakePrintStatus, &errors);
+ if (DEBUG(MAKE)) {
+ fprintf(debug_file, "done: errors %d\n", errors);
+ if (errors)
+ Targ_PrintGraph(4);
+ }
+ }
+ return errors != 0;
+}
diff --git a/usr.bin/make/make.h b/usr.bin/make/make.h
new file mode 100644
index 0000000..53e1895
--- /dev/null
+++ b/usr.bin/make/make.h
@@ -0,0 +1,535 @@
+/* $NetBSD: make.h,v 1.104 2018/02/12 21:38:09 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)make.h 8.3 (Berkeley) 6/13/95
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)make.h 8.3 (Berkeley) 6/13/95
+ */
+
+/*-
+ * make.h --
+ * The global definitions for pmake
+ */
+
+#ifndef _MAKE_H_
+#define _MAKE_H_
+
+#include <sys/types.h>
+#include <sys/param.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef BSD4_4
+# include <sys/cdefs.h>
+#endif
+
+#ifndef FD_CLOEXEC
+#define FD_CLOEXEC 1
+#endif
+
+#if defined(__GNUC__)
+#define MAKE_GNUC_PREREQ(x, y) \
+ ((__GNUC__ == (x) && __GNUC_MINOR__ >= (y)) || \
+ (__GNUC__ > (x)))
+#else /* defined(__GNUC__) */
+#define MAKE_GNUC_PREREQ(x, y) 0
+#endif /* defined(__GNUC__) */
+
+#if MAKE_GNUC_PREREQ(2, 7)
+#define MAKE_ATTR_UNUSED __attribute__((__unused__))
+#else
+#define MAKE_ATTR_UNUSED /* delete */
+#endif
+
+#if MAKE_GNUC_PREREQ(2, 5)
+#define MAKE_ATTR_DEAD __attribute__((__noreturn__))
+#elif defined(__GNUC__)
+#define MAKE_ATTR_DEAD __volatile
+#else
+#define MAKE_ATTR_DEAD /* delete */
+#endif
+
+#if MAKE_GNUC_PREREQ(2, 7)
+#define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) \
+ __attribute__((__format__ (__printf__, fmtarg, firstvararg)))
+#else
+#define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) /* delete */
+#endif
+
+#include "sprite.h"
+#include "lst.h"
+#include "hash.h"
+#include "config.h"
+#include "buf.h"
+#include "make_malloc.h"
+
+/*-
+ * The structure for an individual graph node. Each node has several
+ * pieces of data associated with it.
+ * 1) the name of the target it describes
+ * 2) the location of the target file in the file system.
+ * 3) the type of operator used to define its sources (qv. parse.c)
+ * 4) whether it is involved in this invocation of make
+ * 5) whether the target has been remade
+ * 6) whether any of its children has been remade
+ * 7) the number of its children that are, as yet, unmade
+ * 8) its modification time
+ * 9) the modification time of its youngest child (qv. make.c)
+ * 10) a list of nodes for which this is a source (parents)
+ * 11) a list of nodes on which this depends (children)
+ * 12) a list of nodes that depend on this, as gleaned from the
+ * transformation rules (iParents)
+ * 13) a list of ancestor nodes, which includes parents, iParents,
+ * and recursive parents of parents
+ * 14) a list of nodes of the same name created by the :: operator
+ * 15) a list of nodes that must be made (if they're made) before
+ * this node can be, but that do not enter into the datedness of
+ * this node.
+ * 16) a list of nodes that must be made (if they're made) before
+ * this node or any child of this node can be, but that do not
+ * enter into the datedness of this node.
+ * 17) a list of nodes that must be made (if they're made) after
+ * this node is, but that do not depend on this node, in the
+ * normal sense.
+ * 18) a Lst of ``local'' variables that are specific to this target
+ * and this target only (qv. var.c [$@ $< $?, etc.])
+ * 19) a Lst of strings that are commands to be given to a shell
+ * to create this target.
+ */
+typedef struct GNode {
+ char *name; /* The target's name */
+ char *uname; /* The unexpanded name of a .USE node */
+ char *path; /* The full pathname of the file */
+ int type; /* Its type (see the OP flags, below) */
+
+ int flags;
+#define REMAKE 0x1 /* this target needs to be (re)made */
+#define CHILDMADE 0x2 /* children of this target were made */
+#define FORCE 0x4 /* children don't exist, and we pretend made */
+#define DONE_WAIT 0x8 /* Set by Make_ProcessWait() */
+#define DONE_ORDER 0x10 /* Build requested by .ORDER processing */
+#define FROM_DEPEND 0x20 /* Node created from .depend */
+#define DONE_ALLSRC 0x40 /* We do it once only */
+#define CYCLE 0x1000 /* Used by MakePrintStatus */
+#define DONECYCLE 0x2000 /* Used by MakePrintStatus */
+#define INTERNAL 0x4000 /* Internal use only */
+ enum enum_made {
+ UNMADE, DEFERRED, REQUESTED, BEINGMADE,
+ MADE, UPTODATE, ERROR, ABORTED
+ } made; /* Set to reflect the state of processing
+ * on this node:
+ * UNMADE - Not examined yet
+ * DEFERRED - Examined once (building child)
+ * REQUESTED - on toBeMade list
+ * BEINGMADE - Target is already being made.
+ * Indicates a cycle in the graph.
+ * MADE - Was out-of-date and has been made
+ * UPTODATE - Was already up-to-date
+ * ERROR - An error occurred while it was being
+ * made (used only in compat mode)
+ * ABORTED - The target was aborted due to
+ * an error making an inferior (compat).
+ */
+ int unmade; /* The number of unmade children */
+
+ time_t mtime; /* Its modification time */
+ struct GNode *cmgn; /* The youngest child */
+
+ Lst iParents; /* Links to parents for which this is an
+ * implied source, if any */
+ Lst cohorts; /* Other nodes for the :: operator */
+ Lst parents; /* Nodes that depend on this one */
+ Lst children; /* Nodes on which this one depends */
+ Lst order_pred; /* .ORDER nodes we need made */
+ Lst order_succ; /* .ORDER nodes who need us */
+
+ char cohort_num[8]; /* #n for this cohort */
+ int unmade_cohorts;/* # of unmade instances on the
+ cohorts list */
+ struct GNode *centurion; /* Pointer to the first instance of a ::
+ node; only set when on a cohorts list */
+ unsigned int checked; /* Last time we tried to makle this node */
+
+ Hash_Table context; /* The local variables */
+ Lst commands; /* Creation commands */
+
+ struct _Suff *suffix; /* Suffix for the node (determined by
+ * Suff_FindDeps and opaque to everyone
+ * but the Suff module) */
+ const char *fname; /* filename where the GNode got defined */
+ int lineno; /* line number where the GNode got defined */
+} GNode;
+
+/*
+ * The OP_ constants are used when parsing a dependency line as a way of
+ * communicating to other parts of the program the way in which a target
+ * should be made. These constants are bitwise-OR'ed together and
+ * placed in the 'type' field of each node. Any node that has
+ * a 'type' field which satisfies the OP_NOP function was never never on
+ * the lefthand side of an operator, though it may have been on the
+ * righthand side...
+ */
+#define OP_DEPENDS 0x00000001 /* Execution of commands depends on
+ * kids (:) */
+#define OP_FORCE 0x00000002 /* Always execute commands (!) */
+#define OP_DOUBLEDEP 0x00000004 /* Execution of commands depends on kids
+ * per line (::) */
+#define OP_OPMASK (OP_DEPENDS|OP_FORCE|OP_DOUBLEDEP)
+
+#define OP_OPTIONAL 0x00000008 /* Don't care if the target doesn't
+ * exist and can't be created */
+#define OP_USE 0x00000010 /* Use associated commands for parents */
+#define OP_EXEC 0x00000020 /* Target is never out of date, but always
+ * execute commands anyway. Its time
+ * doesn't matter, so it has none...sort
+ * of */
+#define OP_IGNORE 0x00000040 /* Ignore errors when creating the node */
+#define OP_PRECIOUS 0x00000080 /* Don't remove the target when
+ * interrupted */
+#define OP_SILENT 0x00000100 /* Don't echo commands when executed */
+#define OP_MAKE 0x00000200 /* Target is a recursive make so its
+ * commands should always be executed when
+ * it is out of date, regardless of the
+ * state of the -n or -t flags */
+#define OP_JOIN 0x00000400 /* Target is out-of-date only if any of its
+ * children was out-of-date */
+#define OP_MADE 0x00000800 /* Assume the children of the node have
+ * been already made */
+#define OP_SPECIAL 0x00001000 /* Special .BEGIN, .END, .INTERRUPT */
+#define OP_USEBEFORE 0x00002000 /* Like .USE, only prepend commands */
+#define OP_INVISIBLE 0x00004000 /* The node is invisible to its parents.
+ * I.e. it doesn't show up in the parents's
+ * local variables. */
+#define OP_NOTMAIN 0x00008000 /* The node is exempt from normal 'main
+ * target' processing in parse.c */
+#define OP_PHONY 0x00010000 /* Not a file target; run always */
+#define OP_NOPATH 0x00020000 /* Don't search for file in the path */
+#define OP_WAIT 0x00040000 /* .WAIT phony node */
+#define OP_NOMETA 0x00080000 /* .NOMETA do not create a .meta file */
+#define OP_META 0x00100000 /* .META we _do_ want a .meta file */
+#define OP_NOMETA_CMP 0x00200000 /* Do not compare commands in .meta file */
+#define OP_SUBMAKE 0x00400000 /* Possibly a submake node */
+/* Attributes applied by PMake */
+#define OP_TRANSFORM 0x80000000 /* The node is a transformation rule */
+#define OP_MEMBER 0x40000000 /* Target is a member of an archive */
+#define OP_LIB 0x20000000 /* Target is a library */
+#define OP_ARCHV 0x10000000 /* Target is an archive construct */
+#define OP_HAS_COMMANDS 0x08000000 /* Target has all the commands it should.
+ * Used when parsing to catch multiple
+ * commands for a target */
+#define OP_SAVE_CMDS 0x04000000 /* Saving commands on .END (Compat) */
+#define OP_DEPS_FOUND 0x02000000 /* Already processed by Suff_FindDeps */
+#define OP_MARK 0x01000000 /* Node found while expanding .ALLSRC */
+
+#define NoExecute(gn) ((gn->type & OP_MAKE) ? noRecursiveExecute : noExecute)
+/*
+ * OP_NOP will return TRUE if the node with the given type was not the
+ * object of a dependency operator
+ */
+#define OP_NOP(t) (((t) & OP_OPMASK) == 0x00000000)
+
+#define OP_NOTARGET (OP_NOTMAIN|OP_USE|OP_EXEC|OP_TRANSFORM)
+
+/*
+ * The TARG_ constants are used when calling the Targ_FindNode and
+ * Targ_FindList functions in targ.c. They simply tell the functions what to
+ * do if the desired node(s) is (are) not found. If the TARG_CREATE constant
+ * is given, a new, empty node will be created for the target, placed in the
+ * table of all targets and its address returned. If TARG_NOCREATE is given,
+ * a NULL pointer will be returned.
+ */
+#define TARG_NOCREATE 0x00 /* don't create it */
+#define TARG_CREATE 0x01 /* create node if not found */
+#define TARG_NOHASH 0x02 /* don't look in/add to hash table */
+
+/*
+ * These constants are all used by the Str_Concat function to decide how the
+ * final string should look. If STR_ADDSPACE is given, a space will be
+ * placed between the two strings. If STR_ADDSLASH is given, a '/' will
+ * be used instead of a space. If neither is given, no intervening characters
+ * will be placed between the two strings in the final output. If the
+ * STR_DOFREE bit is set, the two input strings will be freed before
+ * Str_Concat returns.
+ */
+#define STR_ADDSPACE 0x01 /* add a space when Str_Concat'ing */
+#define STR_ADDSLASH 0x02 /* add a slash when Str_Concat'ing */
+
+/*
+ * Error levels for parsing. PARSE_FATAL means the process cannot continue
+ * once the makefile has been parsed. PARSE_WARNING means it can. Passed
+ * as the first argument to Parse_Error.
+ */
+#define PARSE_INFO 3
+#define PARSE_WARNING 2
+#define PARSE_FATAL 1
+
+/*
+ * Values returned by Cond_Eval.
+ */
+#define COND_PARSE 0 /* Parse the next lines */
+#define COND_SKIP 1 /* Skip the next lines */
+#define COND_INVALID 2 /* Not a conditional statement */
+
+/*
+ * Definitions for the "local" variables. Used only for clarity.
+ */
+#define TARGET "@" /* Target of dependency */
+#define OODATE "?" /* All out-of-date sources */
+#define ALLSRC ">" /* All sources */
+#define IMPSRC "<" /* Source implied by transformation */
+#define PREFIX "*" /* Common prefix */
+#define ARCHIVE "!" /* Archive in "archive(member)" syntax */
+#define MEMBER "%" /* Member in "archive(member)" syntax */
+
+#define FTARGET "@F" /* file part of TARGET */
+#define DTARGET "@D" /* directory part of TARGET */
+#define FIMPSRC "<F" /* file part of IMPSRC */
+#define DIMPSRC "<D" /* directory part of IMPSRC */
+#define FPREFIX "*F" /* file part of PREFIX */
+#define DPREFIX "*D" /* directory part of PREFIX */
+
+/*
+ * Global Variables
+ */
+extern Lst create; /* The list of target names specified on the
+ * command line. used to resolve #if
+ * make(...) statements */
+extern Lst dirSearchPath; /* The list of directories to search when
+ * looking for targets */
+
+extern Boolean compatMake; /* True if we are make compatible */
+extern Boolean ignoreErrors; /* True if should ignore all errors */
+extern Boolean beSilent; /* True if should print no commands */
+extern Boolean noExecute; /* True if should execute nothing */
+extern Boolean noRecursiveExecute; /* True if should execute nothing */
+extern Boolean allPrecious; /* True if every target is precious */
+extern Boolean deleteOnError; /* True if failed targets should be deleted */
+extern Boolean keepgoing; /* True if should continue on unaffected
+ * portions of the graph when have an error
+ * in one portion */
+extern Boolean touchFlag; /* TRUE if targets should just be 'touched'
+ * if out of date. Set by the -t flag */
+extern Boolean queryFlag; /* TRUE if we aren't supposed to really make
+ * anything, just see if the targets are out-
+ * of-date */
+extern Boolean doing_depend; /* TRUE if processing .depend */
+
+extern Boolean checkEnvFirst; /* TRUE if environment should be searched for
+ * variables before the global context */
+extern Boolean jobServer; /* a jobServer already exists */
+
+extern Boolean parseWarnFatal; /* TRUE if makefile parsing warnings are
+ * treated as errors */
+
+extern Boolean varNoExportEnv; /* TRUE if we should not export variables
+ * set on the command line to the env. */
+
+extern GNode *DEFAULT; /* .DEFAULT rule */
+
+extern GNode *VAR_INTERNAL; /* Variables defined internally by make
+ * which should not override those set by
+ * makefiles.
+ */
+extern GNode *VAR_GLOBAL; /* Variables defined in a global context, e.g
+ * in the Makefile itself */
+extern GNode *VAR_CMD; /* Variables defined on the command line */
+extern GNode *VAR_FOR; /* Iteration variables */
+extern char var_Error[]; /* Value returned by Var_Parse when an error
+ * is encountered. It actually points to
+ * an empty string, so naive callers needn't
+ * worry about it. */
+
+extern time_t now; /* The time at the start of this whole
+ * process */
+
+extern Boolean oldVars; /* Do old-style variable substitution */
+
+extern Lst sysIncPath; /* The system include path. */
+extern Lst defIncPath; /* The default include path. */
+
+extern char curdir[]; /* Startup directory */
+extern char *progname; /* The program name */
+extern char *makeDependfile; /* .depend */
+extern char **savedEnv; /* if we replaced environ this will be non-NULL */
+
+/*
+ * We cannot vfork() in a child of vfork().
+ * Most systems do not enforce this but some do.
+ */
+#define vFork() ((getpid() == myPid) ? vfork() : fork())
+extern pid_t myPid;
+
+#define MAKEFLAGS ".MAKEFLAGS"
+#define MAKEOVERRIDES ".MAKEOVERRIDES"
+#define MAKE_JOB_PREFIX ".MAKE.JOB.PREFIX" /* prefix for job target output */
+#define MAKE_EXPORTED ".MAKE.EXPORTED" /* variables we export */
+#define MAKE_MAKEFILES ".MAKE.MAKEFILES" /* all the makefiles we read */
+#define MAKE_LEVEL ".MAKE.LEVEL" /* recursion level */
+#define MAKEFILE_PREFERENCE ".MAKE.MAKEFILE_PREFERENCE"
+#define MAKE_DEPENDFILE ".MAKE.DEPENDFILE" /* .depend */
+#define MAKE_MODE ".MAKE.MODE"
+#ifndef MAKE_LEVEL_ENV
+# define MAKE_LEVEL_ENV "MAKELEVEL"
+#endif
+
+/*
+ * debug control:
+ * There is one bit per module. It is up to the module what debug
+ * information to print.
+ */
+FILE *debug_file; /* Output written here - default stdout */
+extern int debug;
+#define DEBUG_ARCH 0x00001
+#define DEBUG_COND 0x00002
+#define DEBUG_DIR 0x00004
+#define DEBUG_GRAPH1 0x00008
+#define DEBUG_GRAPH2 0x00010
+#define DEBUG_JOB 0x00020
+#define DEBUG_MAKE 0x00040
+#define DEBUG_SUFF 0x00080
+#define DEBUG_TARG 0x00100
+#define DEBUG_VAR 0x00200
+#define DEBUG_FOR 0x00400
+#define DEBUG_SHELL 0x00800
+#define DEBUG_ERROR 0x01000
+#define DEBUG_LOUD 0x02000
+#define DEBUG_META 0x04000
+
+#define DEBUG_GRAPH3 0x10000
+#define DEBUG_SCRIPT 0x20000
+#define DEBUG_PARSE 0x40000
+#define DEBUG_CWD 0x80000
+
+#define CONCAT(a,b) a##b
+
+#define DEBUG(module) (debug & CONCAT(DEBUG_,module))
+
+#include "nonints.h"
+
+int Make_TimeStamp(GNode *, GNode *);
+Boolean Make_OODate(GNode *);
+void Make_ExpandUse(Lst);
+time_t Make_Recheck(GNode *);
+void Make_HandleUse(GNode *, GNode *);
+void Make_Update(GNode *);
+void Make_DoAllVar(GNode *);
+Boolean Make_Run(Lst);
+char * Check_Cwd_Cmd(const char *);
+void Check_Cwd(const char **);
+void PrintOnError(GNode *, const char *);
+void Main_ExportMAKEFLAGS(Boolean);
+Boolean Main_SetObjdir(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
+int mkTempFile(const char *, char **);
+int str2Lst_Append(Lst, char *, const char *);
+int cached_lstat(const char *, void *);
+int cached_stat(const char *, void *);
+
+#define VARF_UNDEFERR 1
+#define VARF_WANTRES 2
+#define VARF_ASSIGN 4
+
+#ifdef __GNUC__
+#define UNCONST(ptr) ({ \
+ union __unconst { \
+ const void *__cp; \
+ void *__p; \
+ } __d; \
+ __d.__cp = ptr, __d.__p; })
+#else
+#define UNCONST(ptr) (void *)(ptr)
+#endif
+
+#ifndef MIN
+#define MIN(a, b) ((a < b) ? a : b)
+#endif
+#ifndef MAX
+#define MAX(a, b) ((a > b) ? a : b)
+#endif
+
+/* At least GNU/Hurd systems lack hardcoded MAXPATHLEN/PATH_MAX */
+#include <limits.h>
+#ifndef MAXPATHLEN
+#define MAXPATHLEN 4096
+#endif
+#ifndef PATH_MAX
+#define PATH_MAX MAXPATHLEN
+#endif
+
+#if defined(SYSV)
+#define KILLPG(pid, sig) kill(-(pid), (sig))
+#else
+#define KILLPG(pid, sig) killpg((pid), (sig))
+#endif
+
+#endif /* _MAKE_H_ */
diff --git a/usr.bin/make/make_malloc.c b/usr.bin/make/make_malloc.c
new file mode 100644
index 0000000..035d519
--- /dev/null
+++ b/usr.bin/make/make_malloc.c
@@ -0,0 +1,119 @@
+/* $NetBSD: make_malloc.c,v 1.11 2017/04/16 20:20:24 dholland Exp $ */
+
+/*-
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef MAKE_NATIVE
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: make_malloc.c,v 1.11 2017/04/16 20:20:24 dholland Exp $");
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "make.h"
+
+#ifndef USE_EMALLOC
+static MAKE_ATTR_DEAD void enomem(void);
+
+/*
+ * enomem --
+ * die when out of memory.
+ */
+static MAKE_ATTR_DEAD void
+enomem(void)
+{
+ (void)fprintf(stderr, "%s: %s.\n", progname, strerror(ENOMEM));
+ exit(2);
+}
+
+/*
+ * bmake_malloc --
+ * malloc, but die on error.
+ */
+void *
+bmake_malloc(size_t len)
+{
+ void *p;
+
+ if ((p = malloc(len)) == NULL)
+ enomem();
+ return(p);
+}
+
+/*
+ * bmake_strdup --
+ * strdup, but die on error.
+ */
+char *
+bmake_strdup(const char *str)
+{
+ size_t len;
+ char *p;
+
+ len = strlen(str) + 1;
+ if ((p = malloc(len)) == NULL)
+ enomem();
+ return memcpy(p, str, len);
+}
+
+/*
+ * bmake_strndup --
+ * strndup, but die on error.
+ */
+char *
+bmake_strndup(const char *str, size_t max_len)
+{
+ size_t len;
+ char *p;
+
+ if (str == NULL)
+ return NULL;
+
+ len = strlen(str);
+ if (len > max_len)
+ len = max_len;
+ p = bmake_malloc(len + 1);
+ memcpy(p, str, len);
+ p[len] = '\0';
+
+ return(p);
+}
+
+/*
+ * bmake_realloc --
+ * realloc, but die on error.
+ */
+void *
+bmake_realloc(void *ptr, size_t size)
+{
+ if ((ptr = realloc(ptr, size)) == NULL)
+ enomem();
+ return(ptr);
+}
+#endif
diff --git a/usr.bin/make/make_malloc.h b/usr.bin/make/make_malloc.h
new file mode 100644
index 0000000..36d3eff
--- /dev/null
+++ b/usr.bin/make/make_malloc.h
@@ -0,0 +1,41 @@
+/* $NetBSD: make_malloc.h,v 1.4 2009/01/24 14:43:29 dsl Exp $ */
+
+/*-
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef USE_EMALLOC
+void *bmake_malloc(size_t);
+void *bmake_realloc(void *, size_t);
+char *bmake_strdup(const char *);
+char *bmake_strndup(const char *, size_t);
+#else
+#include <util.h>
+#define bmake_malloc(x) emalloc(x)
+#define bmake_realloc(x,y) erealloc(x,y)
+#define bmake_strdup(x) estrdup(x)
+#define bmake_strndup(x,y) estrndup(x,y)
+#endif
+
diff --git a/usr.bin/make/meta.c b/usr.bin/make/meta.c
new file mode 100644
index 0000000..4a3f41d
--- /dev/null
+++ b/usr.bin/make/meta.c
@@ -0,0 +1,1641 @@
+/* $NetBSD: meta.c,v 1.70 2018/02/13 19:37:30 sjg Exp $ */
+
+/*
+ * Implement 'meta' mode.
+ * Adapted from John Birrell's patches to FreeBSD make.
+ * --sjg
+ */
+/*
+ * Copyright (c) 2009-2016, Juniper Networks, Inc.
+ * Portions Copyright (c) 2009, John Birrell.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if defined(USE_META)
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <libgen.h>
+#include <errno.h>
+#if !defined(HAVE_CONFIG_H) || defined(HAVE_ERR_H)
+#include <err.h>
+#endif
+
+#include "make.h"
+#include "job.h"
+
+#ifdef HAVE_FILEMON_H
+# include <filemon.h>
+#endif
+#if !defined(USE_FILEMON) && defined(FILEMON_SET_FD)
+# define USE_FILEMON
+#endif
+
+static BuildMon Mybm; /* for compat */
+static Lst metaBailiwick; /* our scope of control */
+static char *metaBailiwickStr; /* string storage for the list */
+static Lst metaIgnorePaths; /* paths we deliberately ignore */
+static char *metaIgnorePathsStr; /* string storage for the list */
+
+#ifndef MAKE_META_IGNORE_PATHS
+#define MAKE_META_IGNORE_PATHS ".MAKE.META.IGNORE_PATHS"
+#endif
+#ifndef MAKE_META_IGNORE_PATTERNS
+#define MAKE_META_IGNORE_PATTERNS ".MAKE.META.IGNORE_PATTERNS"
+#endif
+#ifndef MAKE_META_IGNORE_FILTER
+#define MAKE_META_IGNORE_FILTER ".MAKE.META.IGNORE_FILTER"
+#endif
+
+Boolean useMeta = FALSE;
+static Boolean useFilemon = FALSE;
+static Boolean writeMeta = FALSE;
+static Boolean metaMissing = FALSE; /* oodate if missing */
+static Boolean filemonMissing = FALSE; /* oodate if missing */
+static Boolean metaEnv = FALSE; /* don't save env unless asked */
+static Boolean metaVerbose = FALSE;
+static Boolean metaIgnoreCMDs = FALSE; /* ignore CMDs in .meta files */
+static Boolean metaIgnorePatterns = FALSE; /* do we need to do pattern matches */
+static Boolean metaIgnoreFilter = FALSE; /* do we have more complex filtering? */
+static Boolean metaCurdirOk = FALSE; /* write .meta in .CURDIR Ok? */
+static Boolean metaSilent = FALSE; /* if we have a .meta be SILENT */
+
+extern Boolean forceJobs;
+extern Boolean comatMake;
+extern char **environ;
+
+#define MAKE_META_PREFIX ".MAKE.META.PREFIX"
+
+#ifndef N2U
+# define N2U(n, u) (((n) + ((u) - 1)) / (u))
+#endif
+#ifndef ROUNDUP
+# define ROUNDUP(n, u) (N2U((n), (u)) * (u))
+#endif
+
+#if !defined(HAVE_STRSEP)
+# define strsep(s, d) stresep((s), (d), 0)
+#endif
+
+/*
+ * Filemon is a kernel module which snoops certain syscalls.
+ *
+ * C chdir
+ * E exec
+ * F [v]fork
+ * L [sym]link
+ * M rename
+ * R read
+ * W write
+ * S stat
+ *
+ * See meta_oodate below - we mainly care about 'E' and 'R'.
+ *
+ * We can still use meta mode without filemon, but
+ * the benefits are more limited.
+ */
+#ifdef USE_FILEMON
+# ifndef _PATH_FILEMON
+# define _PATH_FILEMON "/dev/filemon"
+# endif
+
+/*
+ * Open the filemon device.
+ */
+static void
+filemon_open(BuildMon *pbm)
+{
+ int retry;
+
+ pbm->mon_fd = pbm->filemon_fd = -1;
+ if (!useFilemon)
+ return;
+
+ for (retry = 5; retry >= 0; retry--) {
+ if ((pbm->filemon_fd = open(_PATH_FILEMON, O_RDWR)) >= 0)
+ break;
+ }
+
+ if (pbm->filemon_fd < 0) {
+ useFilemon = FALSE;
+ warn("Could not open %s", _PATH_FILEMON);
+ return;
+ }
+
+ /*
+ * We use a file outside of '.'
+ * to avoid a FreeBSD kernel bug where unlink invalidates
+ * cwd causing getcwd to do a lot more work.
+ * We only care about the descriptor.
+ */
+ pbm->mon_fd = mkTempFile("filemon.XXXXXX", NULL);
+ if (ioctl(pbm->filemon_fd, FILEMON_SET_FD, &pbm->mon_fd) < 0) {
+ err(1, "Could not set filemon file descriptor!");
+ }
+ /* we don't need these once we exec */
+ (void)fcntl(pbm->mon_fd, F_SETFD, FD_CLOEXEC);
+ (void)fcntl(pbm->filemon_fd, F_SETFD, FD_CLOEXEC);
+}
+
+/*
+ * Read the build monitor output file and write records to the target's
+ * metadata file.
+ */
+static int
+filemon_read(FILE *mfp, int fd)
+{
+ char buf[BUFSIZ];
+ int n;
+ int error;
+
+ /* Check if we're not writing to a meta data file.*/
+ if (mfp == NULL) {
+ if (fd >= 0)
+ close(fd); /* not interested */
+ return 0;
+ }
+ /* rewind */
+ (void)lseek(fd, (off_t)0, SEEK_SET);
+
+ error = 0;
+ fprintf(mfp, "\n-- filemon acquired metadata --\n");
+
+ while ((n = read(fd, buf, sizeof(buf))) > 0) {
+ if ((int)fwrite(buf, 1, n, mfp) < n)
+ error = EIO;
+ }
+ fflush(mfp);
+ if (close(fd) < 0)
+ error = errno;
+ return error;
+}
+#endif
+
+/*
+ * when realpath() fails,
+ * we use this, to clean up ./ and ../
+ */
+static void
+eat_dots(char *buf, size_t bufsz, int dots)
+{
+ char *cp;
+ char *cp2;
+ const char *eat;
+ size_t eatlen;
+
+ switch (dots) {
+ case 1:
+ eat = "/./";
+ eatlen = 2;
+ break;
+ case 2:
+ eat = "/../";
+ eatlen = 3;
+ break;
+ default:
+ return;
+ }
+
+ do {
+ cp = strstr(buf, eat);
+ if (cp) {
+ cp2 = cp + eatlen;
+ if (dots == 2 && cp > buf) {
+ do {
+ cp--;
+ } while (cp > buf && *cp != '/');
+ }
+ if (*cp == '/') {
+ strlcpy(cp, cp2, bufsz - (cp - buf));
+ } else {
+ return; /* can't happen? */
+ }
+ }
+ } while (cp);
+}
+
+static char *
+meta_name(struct GNode *gn, char *mname, size_t mnamelen,
+ const char *dname,
+ const char *tname,
+ const char *cwd)
+{
+ char buf[MAXPATHLEN];
+ char *rp;
+ char *cp;
+ char *tp;
+ char *dtp;
+ size_t ldname;
+
+ /*
+ * Weed out relative paths from the target file name.
+ * We have to be careful though since if target is a
+ * symlink, the result will be unstable.
+ * So we use realpath() just to get the dirname, and leave the
+ * basename as given to us.
+ */
+ if ((cp = strrchr(tname, '/'))) {
+ if (cached_realpath(tname, buf)) {
+ if ((rp = strrchr(buf, '/'))) {
+ rp++;
+ cp++;
+ if (strcmp(cp, rp) != 0)
+ strlcpy(rp, cp, sizeof(buf) - (rp - buf));
+ }
+ tname = buf;
+ } else {
+ /*
+ * We likely have a directory which is about to be made.
+ * We pretend realpath() succeeded, to have a chance
+ * of generating the same meta file name that we will
+ * next time through.
+ */
+ if (tname[0] == '/') {
+ strlcpy(buf, tname, sizeof(buf));
+ } else {
+ snprintf(buf, sizeof(buf), "%s/%s", cwd, tname);
+ }
+ eat_dots(buf, sizeof(buf), 1); /* ./ */
+ eat_dots(buf, sizeof(buf), 2); /* ../ */
+ tname = buf;
+ }
+ }
+ /* on some systems dirname may modify its arg */
+ tp = bmake_strdup(tname);
+ dtp = dirname(tp);
+ if (strcmp(dname, dtp) == 0)
+ snprintf(mname, mnamelen, "%s.meta", tname);
+ else {
+ ldname = strlen(dname);
+ if (strncmp(dname, dtp, ldname) == 0 && dtp[ldname] == '/')
+ snprintf(mname, mnamelen, "%s/%s.meta", dname, &tname[ldname+1]);
+ else
+ snprintf(mname, mnamelen, "%s/%s.meta", dname, tname);
+
+ /*
+ * Replace path separators in the file name after the
+ * current object directory path.
+ */
+ cp = mname + strlen(dname) + 1;
+
+ while (*cp != '\0') {
+ if (*cp == '/')
+ *cp = '_';
+ cp++;
+ }
+ }
+ free(tp);
+ return (mname);
+}
+
+/*
+ * Return true if running ${.MAKE}
+ * Bypassed if target is flagged .MAKE
+ */
+static int
+is_submake(void *cmdp, void *gnp)
+{
+ static char *p_make = NULL;
+ static int p_len;
+ char *cmd = cmdp;
+ GNode *gn = gnp;
+ char *mp = NULL;
+ char *cp;
+ char *cp2;
+ int rc = 0; /* keep looking */
+
+ if (!p_make) {
+ p_make = Var_Value(".MAKE", gn, &cp);
+ p_len = strlen(p_make);
+ }
+ cp = strchr(cmd, '$');
+ if ((cp)) {
+ mp = Var_Subst(NULL, cmd, gn, VARF_WANTRES);
+ cmd = mp;
+ }
+ cp2 = strstr(cmd, p_make);
+ if ((cp2)) {
+ switch (cp2[p_len]) {
+ case '\0':
+ case ' ':
+ case '\t':
+ case '\n':
+ rc = 1;
+ break;
+ }
+ if (cp2 > cmd && rc > 0) {
+ switch (cp2[-1]) {
+ case ' ':
+ case '\t':
+ case '\n':
+ break;
+ default:
+ rc = 0; /* no match */
+ break;
+ }
+ }
+ }
+ free(mp);
+ return (rc);
+}
+
+typedef struct meta_file_s {
+ FILE *fp;
+ GNode *gn;
+} meta_file_t;
+
+static int
+printCMD(void *cmdp, void *mfpp)
+{
+ meta_file_t *mfp = mfpp;
+ char *cmd = cmdp;
+ char *cp = NULL;
+
+ if (strchr(cmd, '$')) {
+ cmd = cp = Var_Subst(NULL, cmd, mfp->gn, VARF_WANTRES);
+ }
+ fprintf(mfp->fp, "CMD %s\n", cmd);
+ free(cp);
+ return 0;
+}
+
+/*
+ * Certain node types never get a .meta file
+ */
+#define SKIP_META_TYPE(_type) do { \
+ if ((gn->type & __CONCAT(OP_, _type))) { \
+ if (verbose) { \
+ fprintf(debug_file, "Skipping meta for %s: .%s\n", \
+ gn->name, __STRING(_type)); \
+ } \
+ return FALSE; \
+ } \
+} while (0)
+
+
+/*
+ * Do we need/want a .meta file ?
+ */
+static Boolean
+meta_needed(GNode *gn, const char *dname, const char *tname,
+ char *objdir, int verbose)
+{
+ struct stat fs;
+
+ if (verbose)
+ verbose = DEBUG(META);
+
+ /* This may be a phony node which we don't want meta data for... */
+ /* Skip .meta for .BEGIN, .END, .ERROR etc as well. */
+ /* Or it may be explicitly flagged as .NOMETA */
+ SKIP_META_TYPE(NOMETA);
+ /* Unless it is explicitly flagged as .META */
+ if (!(gn->type & OP_META)) {
+ SKIP_META_TYPE(PHONY);
+ SKIP_META_TYPE(SPECIAL);
+ SKIP_META_TYPE(MAKE);
+ }
+
+ /* Check if there are no commands to execute. */
+ if (Lst_IsEmpty(gn->commands)) {
+ if (verbose)
+ fprintf(debug_file, "Skipping meta for %s: no commands\n",
+ gn->name);
+ return FALSE;
+ }
+ if ((gn->type & (OP_META|OP_SUBMAKE)) == OP_SUBMAKE) {
+ /* OP_SUBMAKE is a bit too aggressive */
+ if (Lst_ForEach(gn->commands, is_submake, gn)) {
+ if (DEBUG(META))
+ fprintf(debug_file, "Skipping meta for %s: .SUBMAKE\n",
+ gn->name);
+ return FALSE;
+ }
+ }
+
+ /* The object directory may not exist. Check it.. */
+ if (cached_stat(dname, &fs) != 0) {
+ if (verbose)
+ fprintf(debug_file, "Skipping meta for %s: no .OBJDIR\n",
+ gn->name);
+ return FALSE;
+ }
+
+ /* make sure these are canonical */
+ if (cached_realpath(dname, objdir))
+ dname = objdir;
+
+ /* If we aren't in the object directory, don't create a meta file. */
+ if (!metaCurdirOk && strcmp(curdir, dname) == 0) {
+ if (verbose)
+ fprintf(debug_file, "Skipping meta for %s: .OBJDIR == .CURDIR\n",
+ gn->name);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+static FILE *
+meta_create(BuildMon *pbm, GNode *gn)
+{
+ meta_file_t mf;
+ char buf[MAXPATHLEN];
+ char objdir[MAXPATHLEN];
+ char **ptr;
+ const char *dname;
+ const char *tname;
+ char *fname;
+ const char *cp;
+ char *p[4]; /* >= possible uses */
+ int i;
+
+ mf.fp = NULL;
+ i = 0;
+
+ dname = Var_Value(".OBJDIR", gn, &p[i++]);
+ tname = Var_Value(TARGET, gn, &p[i++]);
+
+ /* if this succeeds objdir is realpath of dname */
+ if (!meta_needed(gn, dname, tname, objdir, TRUE))
+ goto out;
+ dname = objdir;
+
+ if (metaVerbose) {
+ char *mp;
+
+ /* Describe the target we are building */
+ mp = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", gn, VARF_WANTRES);
+ if (*mp)
+ fprintf(stdout, "%s\n", mp);
+ free(mp);
+ }
+ /* Get the basename of the target */
+ if ((cp = strrchr(tname, '/')) == NULL) {
+ cp = tname;
+ } else {
+ cp++;
+ }
+
+ fflush(stdout);
+
+ if (!writeMeta)
+ /* Don't create meta data. */
+ goto out;
+
+ fname = meta_name(gn, pbm->meta_fname, sizeof(pbm->meta_fname),
+ dname, tname, objdir);
+
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_create: %s\n", fname);
+#endif
+
+ if ((mf.fp = fopen(fname, "w")) == NULL)
+ err(1, "Could not open meta file '%s'", fname);
+
+ fprintf(mf.fp, "# Meta data file %s\n", fname);
+
+ mf.gn = gn;
+
+ Lst_ForEach(gn->commands, printCMD, &mf);
+
+ fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf)));
+ fprintf(mf.fp, "TARGET %s\n", tname);
+
+ if (metaEnv) {
+ for (ptr = environ; *ptr != NULL; ptr++)
+ fprintf(mf.fp, "ENV %s\n", *ptr);
+ }
+
+ fprintf(mf.fp, "-- command output --\n");
+ fflush(mf.fp);
+
+ Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL);
+ Var_Append(".MAKE.META.CREATED", fname, VAR_GLOBAL);
+
+ gn->type |= OP_META; /* in case anyone wants to know */
+ if (metaSilent) {
+ gn->type |= OP_SILENT;
+ }
+ out:
+ for (i--; i >= 0; i--) {
+ free(p[i]);
+ }
+
+ return (mf.fp);
+}
+
+static Boolean
+boolValue(char *s)
+{
+ switch(*s) {
+ case '0':
+ case 'N':
+ case 'n':
+ case 'F':
+ case 'f':
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * Initialization we need before reading makefiles.
+ */
+void
+meta_init(void)
+{
+#ifdef USE_FILEMON
+ /* this allows makefiles to test if we have filemon support */
+ Var_Set(".MAKE.PATH_FILEMON", _PATH_FILEMON, VAR_GLOBAL, 0);
+#endif
+}
+
+
+#define get_mode_bf(bf, token) \
+ if ((cp = strstr(make_mode, token))) \
+ bf = boolValue(&cp[sizeof(token) - 1])
+
+/*
+ * Initialization we need after reading makefiles.
+ */
+void
+meta_mode_init(const char *make_mode)
+{
+ static int once = 0;
+ char *cp;
+
+ useMeta = TRUE;
+ useFilemon = TRUE;
+ writeMeta = TRUE;
+
+ if (make_mode) {
+ if (strstr(make_mode, "env"))
+ metaEnv = TRUE;
+ if (strstr(make_mode, "verb"))
+ metaVerbose = TRUE;
+ if (strstr(make_mode, "read"))
+ writeMeta = FALSE;
+ if (strstr(make_mode, "nofilemon"))
+ useFilemon = FALSE;
+ if (strstr(make_mode, "ignore-cmd"))
+ metaIgnoreCMDs = TRUE;
+ if (useFilemon)
+ get_mode_bf(filemonMissing, "missing-filemon=");
+ get_mode_bf(metaCurdirOk, "curdirok=");
+ get_mode_bf(metaMissing, "missing-meta=");
+ get_mode_bf(metaSilent, "silent=");
+ }
+ if (metaVerbose && !Var_Exists(MAKE_META_PREFIX, VAR_GLOBAL)) {
+ /*
+ * The default value for MAKE_META_PREFIX
+ * prints the absolute path of the target.
+ * This works be cause :H will generate '.' if there is no /
+ * and :tA will resolve that to cwd.
+ */
+ Var_Set(MAKE_META_PREFIX, "Building ${.TARGET:H:tA}/${.TARGET:T}", VAR_GLOBAL, 0);
+ }
+ if (once)
+ return;
+ once = 1;
+ memset(&Mybm, 0, sizeof(Mybm));
+ /*
+ * We consider ourselves master of all within ${.MAKE.META.BAILIWICK}
+ */
+ metaBailiwick = Lst_Init(FALSE);
+ metaBailiwickStr = Var_Subst(NULL, "${.MAKE.META.BAILIWICK:O:u:tA}",
+ VAR_GLOBAL, VARF_WANTRES);
+ if (metaBailiwickStr) {
+ str2Lst_Append(metaBailiwick, metaBailiwickStr, NULL);
+ }
+ /*
+ * We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS}
+ */
+ metaIgnorePaths = Lst_Init(FALSE);
+ Var_Append(MAKE_META_IGNORE_PATHS,
+ "/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}", VAR_GLOBAL);
+ metaIgnorePathsStr = Var_Subst(NULL,
+ "${" MAKE_META_IGNORE_PATHS ":O:u:tA}", VAR_GLOBAL,
+ VARF_WANTRES);
+ if (metaIgnorePathsStr) {
+ str2Lst_Append(metaIgnorePaths, metaIgnorePathsStr, NULL);
+ }
+
+ /*
+ * We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS}
+ */
+ cp = NULL;
+ if (Var_Value(MAKE_META_IGNORE_PATTERNS, VAR_GLOBAL, &cp)) {
+ metaIgnorePatterns = TRUE;
+ free(cp);
+ }
+ cp = NULL;
+ if (Var_Value(MAKE_META_IGNORE_FILTER, VAR_GLOBAL, &cp)) {
+ metaIgnoreFilter = TRUE;
+ free(cp);
+ }
+}
+
+/*
+ * In each case below we allow for job==NULL
+ */
+void
+meta_job_start(Job *job, GNode *gn)
+{
+ BuildMon *pbm;
+
+ if (job != NULL) {
+ pbm = &job->bm;
+ } else {
+ pbm = &Mybm;
+ }
+ pbm->mfp = meta_create(pbm, gn);
+#ifdef USE_FILEMON_ONCE
+ /* compat mode we open the filemon dev once per command */
+ if (job == NULL)
+ return;
+#endif
+#ifdef USE_FILEMON
+ if (pbm->mfp != NULL && useFilemon) {
+ filemon_open(pbm);
+ } else {
+ pbm->mon_fd = pbm->filemon_fd = -1;
+ }
+#endif
+}
+
+/*
+ * The child calls this before doing anything.
+ * It does not disturb our state.
+ */
+void
+meta_job_child(Job *job)
+{
+#ifdef USE_FILEMON
+ BuildMon *pbm;
+
+ if (job != NULL) {
+ pbm = &job->bm;
+ } else {
+ pbm = &Mybm;
+ }
+ if (pbm->mfp != NULL) {
+ close(fileno(pbm->mfp));
+ if (useFilemon) {
+ pid_t pid;
+
+ pid = getpid();
+ if (ioctl(pbm->filemon_fd, FILEMON_SET_PID, &pid) < 0) {
+ err(1, "Could not set filemon pid!");
+ }
+ }
+ }
+#endif
+}
+
+void
+meta_job_error(Job *job, GNode *gn, int flags, int status)
+{
+ char cwd[MAXPATHLEN];
+ BuildMon *pbm;
+
+ if (job != NULL) {
+ pbm = &job->bm;
+ if (!gn)
+ gn = job->node;
+ } else {
+ pbm = &Mybm;
+ }
+ if (pbm->mfp != NULL) {
+ fprintf(pbm->mfp, "\n*** Error code %d%s\n",
+ status,
+ (flags & JOB_IGNERR) ?
+ "(ignored)" : "");
+ }
+ if (gn) {
+ Var_Set(".ERROR_TARGET", gn->path ? gn->path : gn->name, VAR_GLOBAL, 0);
+ }
+ getcwd(cwd, sizeof(cwd));
+ Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL, 0);
+ if (pbm->meta_fname[0]) {
+ Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL, 0);
+ }
+ meta_job_finish(job);
+}
+
+void
+meta_job_output(Job *job, char *cp, const char *nl)
+{
+ BuildMon *pbm;
+
+ if (job != NULL) {
+ pbm = &job->bm;
+ } else {
+ pbm = &Mybm;
+ }
+ if (pbm->mfp != NULL) {
+ if (metaVerbose) {
+ static char *meta_prefix = NULL;
+ static int meta_prefix_len;
+
+ if (!meta_prefix) {
+ char *cp2;
+
+ meta_prefix = Var_Subst(NULL, "${" MAKE_META_PREFIX "}",
+ VAR_GLOBAL, VARF_WANTRES);
+ if ((cp2 = strchr(meta_prefix, '$')))
+ meta_prefix_len = cp2 - meta_prefix;
+ else
+ meta_prefix_len = strlen(meta_prefix);
+ }
+ if (strncmp(cp, meta_prefix, meta_prefix_len) == 0) {
+ cp = strchr(cp+1, '\n');
+ if (!cp++)
+ return;
+ }
+ }
+ fprintf(pbm->mfp, "%s%s", cp, nl);
+ }
+}
+
+int
+meta_cmd_finish(void *pbmp)
+{
+ int error = 0;
+ BuildMon *pbm = pbmp;
+#ifdef USE_FILEMON
+ int x;
+#endif
+
+ if (!pbm)
+ pbm = &Mybm;
+
+#ifdef USE_FILEMON
+ if (pbm->filemon_fd >= 0) {
+ if (close(pbm->filemon_fd) < 0)
+ error = errno;
+ x = filemon_read(pbm->mfp, pbm->mon_fd);
+ if (error == 0 && x != 0)
+ error = x;
+ pbm->filemon_fd = pbm->mon_fd = -1;
+ } else
+#endif
+ fprintf(pbm->mfp, "\n"); /* ensure end with newline */
+ return error;
+}
+
+int
+meta_job_finish(Job *job)
+{
+ BuildMon *pbm;
+ int error = 0;
+ int x;
+
+ if (job != NULL) {
+ pbm = &job->bm;
+ } else {
+ pbm = &Mybm;
+ }
+ if (pbm->mfp != NULL) {
+ error = meta_cmd_finish(pbm);
+ x = fclose(pbm->mfp);
+ if (error == 0 && x != 0)
+ error = errno;
+ pbm->mfp = NULL;
+ pbm->meta_fname[0] = '\0';
+ }
+ return error;
+}
+
+void
+meta_finish(void)
+{
+ Lst_Destroy(metaBailiwick, NULL);
+ free(metaBailiwickStr);
+ Lst_Destroy(metaIgnorePaths, NULL);
+ free(metaIgnorePathsStr);
+}
+
+/*
+ * Fetch a full line from fp - growing bufp if needed
+ * Return length in bufp.
+ */
+static int
+fgetLine(char **bufp, size_t *szp, int o, FILE *fp)
+{
+ char *buf = *bufp;
+ size_t bufsz = *szp;
+ struct stat fs;
+ int x;
+
+ if (fgets(&buf[o], bufsz - o, fp) != NULL) {
+ check_newline:
+ x = o + strlen(&buf[o]);
+ if (buf[x - 1] == '\n')
+ return x;
+ /*
+ * We need to grow the buffer.
+ * The meta file can give us a clue.
+ */
+ if (fstat(fileno(fp), &fs) == 0) {
+ size_t newsz;
+ char *p;
+
+ newsz = ROUNDUP((fs.st_size / 2), BUFSIZ);
+ if (newsz <= bufsz)
+ newsz = ROUNDUP(fs.st_size, BUFSIZ);
+ if (newsz <= bufsz)
+ return x; /* truncated */
+ if (DEBUG(META))
+ fprintf(debug_file, "growing buffer %zu -> %zu\n",
+ bufsz, newsz);
+ p = bmake_realloc(buf, newsz);
+ if (p) {
+ *bufp = buf = p;
+ *szp = bufsz = newsz;
+ /* fetch the rest */
+ if (!fgets(&buf[x], bufsz - x, fp))
+ return x; /* truncated! */
+ goto check_newline;
+ }
+ }
+ }
+ return 0;
+}
+
+/* Lst_ForEach wants 1 to stop search */
+static int
+prefix_match(void *p, void *q)
+{
+ const char *prefix = p;
+ const char *path = q;
+ size_t n = strlen(prefix);
+
+ return (0 == strncmp(path, prefix, n));
+}
+
+/*
+ * looking for exact or prefix/ match to
+ * Lst_Find wants 0 to stop search
+ */
+static int
+path_match(const void *p, const void *q)
+{
+ const char *prefix = q;
+ const char *path = p;
+ size_t n = strlen(prefix);
+ int rc;
+
+ if ((rc = strncmp(path, prefix, n)) == 0) {
+ switch (path[n]) {
+ case '\0':
+ case '/':
+ break;
+ default:
+ rc = 1;
+ break;
+ }
+ }
+ return rc;
+}
+
+/* Lst_Find wants 0 to stop search */
+static int
+string_match(const void *p, const void *q)
+{
+ const char *p1 = p;
+ const char *p2 = q;
+
+ return strcmp(p1, p2);
+}
+
+
+static int
+meta_ignore(GNode *gn, const char *p)
+{
+ char fname[MAXPATHLEN];
+
+ if (p == NULL)
+ return TRUE;
+
+ if (*p == '/') {
+ cached_realpath(p, fname); /* clean it up */
+ if (Lst_ForEach(metaIgnorePaths, prefix_match, fname)) {
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_oodate: ignoring path: %s\n",
+ p);
+#endif
+ return TRUE;
+ }
+ }
+
+ if (metaIgnorePatterns) {
+ char *pm;
+
+ Var_Set(".p.", p, gn, 0);
+ pm = Var_Subst(NULL,
+ "${" MAKE_META_IGNORE_PATTERNS ":@m@${.p.:M$m}@}",
+ gn, VARF_WANTRES);
+ if (*pm) {
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_oodate: ignoring pattern: %s\n",
+ p);
+#endif
+ free(pm);
+ return TRUE;
+ }
+ free(pm);
+ }
+
+ if (metaIgnoreFilter) {
+ char *fm;
+
+ /* skip if filter result is empty */
+ snprintf(fname, sizeof(fname),
+ "${%s:L:${%s:ts:}}",
+ p, MAKE_META_IGNORE_FILTER);
+ fm = Var_Subst(NULL, fname, gn, VARF_WANTRES);
+ if (*fm == '\0') {
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_oodate: ignoring filtered: %s\n",
+ p);
+#endif
+ free(fm);
+ return TRUE;
+ }
+ free(fm);
+ }
+ return FALSE;
+}
+
+/*
+ * When running with 'meta' functionality, a target can be out-of-date
+ * if any of the references in its meta data file is more recent.
+ * We have to track the latestdir on a per-process basis.
+ */
+#define LCWD_VNAME_FMT ".meta.%d.lcwd"
+#define LDIR_VNAME_FMT ".meta.%d.ldir"
+
+/*
+ * It is possible that a .meta file is corrupted,
+ * if we detect this we want to reproduce it.
+ * Setting oodate TRUE will have that effect.
+ */
+#define CHECK_VALID_META(p) if (!(p && *p)) { \
+ warnx("%s: %d: malformed", fname, lineno); \
+ oodate = TRUE; \
+ continue; \
+ }
+
+#define DEQUOTE(p) if (*p == '\'') { \
+ char *ep; \
+ p++; \
+ if ((ep = strchr(p, '\''))) \
+ *ep = '\0'; \
+ }
+
+Boolean
+meta_oodate(GNode *gn, Boolean oodate)
+{
+ static char *tmpdir = NULL;
+ static char cwd[MAXPATHLEN];
+ char lcwd_vname[64];
+ char ldir_vname[64];
+ char lcwd[MAXPATHLEN];
+ char latestdir[MAXPATHLEN];
+ char fname[MAXPATHLEN];
+ char fname1[MAXPATHLEN];
+ char fname2[MAXPATHLEN];
+ char fname3[MAXPATHLEN];
+ const char *dname;
+ const char *tname;
+ char *p;
+ char *cp;
+ char *link_src;
+ char *move_target;
+ static size_t cwdlen = 0;
+ static size_t tmplen = 0;
+ FILE *fp;
+ Boolean needOODATE = FALSE;
+ Lst missingFiles;
+ char *pa[4]; /* >= possible uses */
+ int i;
+ int have_filemon = FALSE;
+
+ if (oodate)
+ return oodate; /* we're done */
+
+ i = 0;
+
+ dname = Var_Value(".OBJDIR", gn, &pa[i++]);
+ tname = Var_Value(TARGET, gn, &pa[i++]);
+
+ /* if this succeeds fname3 is realpath of dname */
+ if (!meta_needed(gn, dname, tname, fname3, FALSE))
+ goto oodate_out;
+ dname = fname3;
+
+ missingFiles = Lst_Init(FALSE);
+
+ /*
+ * We need to check if the target is out-of-date. This includes
+ * checking if the expanded command has changed. This in turn
+ * requires that all variables are set in the same way that they
+ * would be if the target needs to be re-built.
+ */
+ Make_DoAllVar(gn);
+
+ meta_name(gn, fname, sizeof(fname), dname, tname, dname);
+
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_oodate: %s\n", fname);
+#endif
+
+ if ((fp = fopen(fname, "r")) != NULL) {
+ static char *buf = NULL;
+ static size_t bufsz;
+ int lineno = 0;
+ int lastpid = 0;
+ int pid;
+ int x;
+ LstNode ln;
+ struct stat fs;
+
+ if (!buf) {
+ bufsz = 8 * BUFSIZ;
+ buf = bmake_malloc(bufsz);
+ }
+
+ if (!cwdlen) {
+ if (getcwd(cwd, sizeof(cwd)) == NULL)
+ err(1, "Could not get current working directory");
+ cwdlen = strlen(cwd);
+ }
+ strlcpy(lcwd, cwd, sizeof(lcwd));
+ strlcpy(latestdir, cwd, sizeof(latestdir));
+
+ if (!tmpdir) {
+ tmpdir = getTmpdir();
+ tmplen = strlen(tmpdir);
+ }
+
+ /* we want to track all the .meta we read */
+ Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL);
+
+ ln = Lst_First(gn->commands);
+ while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) {
+ lineno++;
+ if (buf[x - 1] == '\n')
+ buf[x - 1] = '\0';
+ else {
+ warnx("%s: %d: line truncated at %u", fname, lineno, x);
+ oodate = TRUE;
+ break;
+ }
+ link_src = NULL;
+ move_target = NULL;
+ /* Find the start of the build monitor section. */
+ if (!have_filemon) {
+ if (strncmp(buf, "-- filemon", 10) == 0) {
+ have_filemon = TRUE;
+ continue;
+ }
+ if (strncmp(buf, "# buildmon", 10) == 0) {
+ have_filemon = TRUE;
+ continue;
+ }
+ }
+
+ /* Delimit the record type. */
+ p = buf;
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: %s\n", fname, lineno, buf);
+#endif
+ strsep(&p, " ");
+ if (have_filemon) {
+ /*
+ * We are in the 'filemon' output section.
+ * Each record from filemon follows the general form:
+ *
+ * <key> <pid> <data>
+ *
+ * Where:
+ * <key> is a single letter, denoting the syscall.
+ * <pid> is the process that made the syscall.
+ * <data> is the arguments (of interest).
+ */
+ switch(buf[0]) {
+ case '#': /* comment */
+ case 'V': /* version */
+ break;
+ default:
+ /*
+ * We need to track pathnames per-process.
+ *
+ * Each process run by make, starts off in the 'CWD'
+ * recorded in the .meta file, if it chdirs ('C')
+ * elsewhere we need to track that - but only for
+ * that process. If it forks ('F'), we initialize
+ * the child to have the same cwd as its parent.
+ *
+ * We also need to track the 'latestdir' of
+ * interest. This is usually the same as cwd, but
+ * not if a process is reading directories.
+ *
+ * Each time we spot a different process ('pid')
+ * we save the current value of 'latestdir' in a
+ * variable qualified by 'lastpid', and
+ * re-initialize 'latestdir' to any pre-saved
+ * value for the current 'pid' and 'CWD' if none.
+ */
+ CHECK_VALID_META(p);
+ pid = atoi(p);
+ if (pid > 0 && pid != lastpid) {
+ char *ldir;
+ char *tp;
+
+ if (lastpid > 0) {
+ /* We need to remember these. */
+ Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0);
+ Var_Set(ldir_vname, latestdir, VAR_GLOBAL, 0);
+ }
+ snprintf(lcwd_vname, sizeof(lcwd_vname), LCWD_VNAME_FMT, pid);
+ snprintf(ldir_vname, sizeof(ldir_vname), LDIR_VNAME_FMT, pid);
+ lastpid = pid;
+ ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp);
+ if (ldir) {
+ strlcpy(latestdir, ldir, sizeof(latestdir));
+ free(tp);
+ }
+ ldir = Var_Value(lcwd_vname, VAR_GLOBAL, &tp);
+ if (ldir) {
+ strlcpy(lcwd, ldir, sizeof(lcwd));
+ free(tp);
+ }
+ }
+ /* Skip past the pid. */
+ if (strsep(&p, " ") == NULL)
+ continue;
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: %d: %c: cwd=%s lcwd=%s ldir=%s\n",
+ fname, lineno,
+ pid, buf[0], cwd, lcwd, latestdir);
+#endif
+ break;
+ }
+
+ CHECK_VALID_META(p);
+
+ /* Process according to record type. */
+ switch (buf[0]) {
+ case 'X': /* eXit */
+ Var_Delete(lcwd_vname, VAR_GLOBAL);
+ Var_Delete(ldir_vname, VAR_GLOBAL);
+ lastpid = 0; /* no need to save ldir_vname */
+ break;
+
+ case 'F': /* [v]Fork */
+ {
+ char cldir[64];
+ int child;
+
+ child = atoi(p);
+ if (child > 0) {
+ snprintf(cldir, sizeof(cldir), LCWD_VNAME_FMT, child);
+ Var_Set(cldir, lcwd, VAR_GLOBAL, 0);
+ snprintf(cldir, sizeof(cldir), LDIR_VNAME_FMT, child);
+ Var_Set(cldir, latestdir, VAR_GLOBAL, 0);
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: %d: cwd=%s lcwd=%s ldir=%s\n",
+ fname, lineno,
+ child, cwd, lcwd, latestdir);
+#endif
+ }
+ }
+ break;
+
+ case 'C': /* Chdir */
+ /* Update lcwd and latest directory. */
+ strlcpy(latestdir, p, sizeof(latestdir));
+ strlcpy(lcwd, p, sizeof(lcwd));
+ Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0);
+ Var_Set(ldir_vname, lcwd, VAR_GLOBAL, 0);
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: cwd=%s ldir=%s\n", fname, lineno, cwd, lcwd);
+#endif
+ break;
+
+ case 'M': /* renaMe */
+ /*
+ * For 'M'oves we want to check
+ * the src as for 'R'ead
+ * and the target as for 'W'rite.
+ */
+ cp = p; /* save this for a second */
+ /* now get target */
+ if (strsep(&p, " ") == NULL)
+ continue;
+ CHECK_VALID_META(p);
+ move_target = p;
+ p = cp;
+ /* 'L' and 'M' put single quotes around the args */
+ DEQUOTE(p);
+ DEQUOTE(move_target);
+ /* FALLTHROUGH */
+ case 'D': /* unlink */
+ if (*p == '/' && !Lst_IsEmpty(missingFiles)) {
+ /* remove any missingFiles entries that match p */
+ if ((ln = Lst_Find(missingFiles, p,
+ path_match)) != NULL) {
+ LstNode nln;
+ char *tp;
+
+ do {
+ nln = Lst_FindFrom(missingFiles, Lst_Succ(ln),
+ p, path_match);
+ tp = Lst_Datum(ln);
+ Lst_Remove(missingFiles, ln);
+ free(tp);
+ } while ((ln = nln) != NULL);
+ }
+ }
+ if (buf[0] == 'M') {
+ /* the target of the mv is a file 'W'ritten */
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_oodate: M %s -> %s\n",
+ p, move_target);
+#endif
+ p = move_target;
+ goto check_write;
+ }
+ break;
+ case 'L': /* Link */
+ /*
+ * For 'L'inks check
+ * the src as for 'R'ead
+ * and the target as for 'W'rite.
+ */
+ link_src = p;
+ /* now get target */
+ if (strsep(&p, " ") == NULL)
+ continue;
+ CHECK_VALID_META(p);
+ /* 'L' and 'M' put single quotes around the args */
+ DEQUOTE(p);
+ DEQUOTE(link_src);
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_oodate: L %s -> %s\n",
+ link_src, p);
+#endif
+ /* FALLTHROUGH */
+ case 'W': /* Write */
+ check_write:
+ /*
+ * If a file we generated within our bailiwick
+ * but outside of .OBJDIR is missing,
+ * we need to do it again.
+ */
+ /* ignore non-absolute paths */
+ if (*p != '/')
+ break;
+
+ if (Lst_IsEmpty(metaBailiwick))
+ break;
+
+ /* ignore cwd - normal dependencies handle those */
+ if (strncmp(p, cwd, cwdlen) == 0)
+ break;
+
+ if (!Lst_ForEach(metaBailiwick, prefix_match, p))
+ break;
+
+ /* tmpdir might be within */
+ if (tmplen > 0 && strncmp(p, tmpdir, tmplen) == 0)
+ break;
+
+ /* ignore anything containing the string "tmp" */
+ if ((strstr("tmp", p)))
+ break;
+
+ if ((link_src != NULL && cached_lstat(p, &fs) < 0) ||
+ (link_src == NULL && cached_stat(p, &fs) < 0)) {
+ if (!meta_ignore(gn, p)) {
+ if (Lst_Find(missingFiles, p, string_match) == NULL)
+ Lst_AtEnd(missingFiles, bmake_strdup(p));
+ }
+ }
+ break;
+ check_link_src:
+ p = link_src;
+ link_src = NULL;
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "meta_oodate: L src %s\n", p);
+#endif
+ /* FALLTHROUGH */
+ case 'R': /* Read */
+ case 'E': /* Exec */
+ /*
+ * Check for runtime files that can't
+ * be part of the dependencies because
+ * they are _expected_ to change.
+ */
+ if (meta_ignore(gn, p))
+ break;
+
+ /*
+ * The rest of the record is the file name.
+ * Check if it's not an absolute path.
+ */
+ {
+ char *sdirs[4];
+ char **sdp;
+ int sdx = 0;
+ int found = 0;
+
+ if (*p == '/') {
+ sdirs[sdx++] = p; /* done */
+ } else {
+ if (strcmp(".", p) == 0)
+ continue; /* no point */
+
+ /* Check vs latestdir */
+ snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p);
+ sdirs[sdx++] = fname1;
+
+ if (strcmp(latestdir, lcwd) != 0) {
+ /* Check vs lcwd */
+ snprintf(fname2, sizeof(fname2), "%s/%s", lcwd, p);
+ sdirs[sdx++] = fname2;
+ }
+ if (strcmp(lcwd, cwd) != 0) {
+ /* Check vs cwd */
+ snprintf(fname3, sizeof(fname3), "%s/%s", cwd, p);
+ sdirs[sdx++] = fname3;
+ }
+ }
+ sdirs[sdx++] = NULL;
+
+ for (sdp = sdirs; *sdp && !found; sdp++) {
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: looking for: %s\n", fname, lineno, *sdp);
+#endif
+ if (cached_stat(*sdp, &fs) == 0) {
+ found = 1;
+ p = *sdp;
+ }
+ }
+ if (found) {
+#ifdef DEBUG_META_MODE
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: found: %s\n", fname, lineno, p);
+#endif
+ if (!S_ISDIR(fs.st_mode) &&
+ fs.st_mtime > gn->mtime) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p);
+ oodate = TRUE;
+ } else if (S_ISDIR(fs.st_mode)) {
+ /* Update the latest directory. */
+ cached_realpath(p, latestdir);
+ }
+ } else if (errno == ENOENT && *p == '/' &&
+ strncmp(p, cwd, cwdlen) != 0) {
+ /*
+ * A referenced file outside of CWD is missing.
+ * We cannot catch every eventuality here...
+ */
+ if (Lst_Find(missingFiles, p, string_match) == NULL)
+ Lst_AtEnd(missingFiles, bmake_strdup(p));
+ }
+ }
+ if (buf[0] == 'E') {
+ /* previous latestdir is no longer relevant */
+ strlcpy(latestdir, lcwd, sizeof(latestdir));
+ }
+ break;
+ default:
+ break;
+ }
+ if (!oodate && buf[0] == 'L' && link_src != NULL)
+ goto check_link_src;
+ } else if (strcmp(buf, "CMD") == 0) {
+ /*
+ * Compare the current command with the one in the
+ * meta data file.
+ */
+ if (ln == NULL) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: there were more build commands in the meta data file than there are now...\n", fname, lineno);
+ oodate = TRUE;
+ } else {
+ char *cmd = (char *)Lst_Datum(ln);
+ Boolean hasOODATE = FALSE;
+
+ if (strstr(cmd, "$?"))
+ hasOODATE = TRUE;
+ else if ((cp = strstr(cmd, ".OODATE"))) {
+ /* check for $[{(].OODATE[:)}] */
+ if (cp > cmd + 2 && cp[-2] == '$')
+ hasOODATE = TRUE;
+ }
+ if (hasOODATE) {
+ needOODATE = TRUE;
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: cannot compare command using .OODATE\n", fname, lineno);
+ }
+ cmd = Var_Subst(NULL, cmd, gn, VARF_WANTRES|VARF_UNDEFERR);
+
+ if ((cp = strchr(cmd, '\n'))) {
+ int n;
+
+ /*
+ * This command contains newlines, we need to
+ * fetch more from the .meta file before we
+ * attempt a comparison.
+ */
+ /* first put the newline back at buf[x - 1] */
+ buf[x - 1] = '\n';
+ do {
+ /* now fetch the next line */
+ if ((n = fgetLine(&buf, &bufsz, x, fp)) <= 0)
+ break;
+ x = n;
+ lineno++;
+ if (buf[x - 1] != '\n') {
+ warnx("%s: %d: line truncated at %u", fname, lineno, x);
+ break;
+ }
+ cp = strchr(++cp, '\n');
+ } while (cp);
+ if (buf[x - 1] == '\n')
+ buf[x - 1] = '\0';
+ }
+ if (!hasOODATE &&
+ !(gn->type & OP_NOMETA_CMP) &&
+ strcmp(p, cmd) != 0) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd);
+ if (!metaIgnoreCMDs)
+ oodate = TRUE;
+ }
+ free(cmd);
+ ln = Lst_Succ(ln);
+ }
+ } else if (strcmp(buf, "CWD") == 0) {
+ /*
+ * Check if there are extra commands now
+ * that weren't in the meta data file.
+ */
+ if (!oodate && ln != NULL) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: there are extra build commands now that weren't in the meta data file\n", fname, lineno);
+ oodate = TRUE;
+ }
+ if (strcmp(p, cwd) != 0) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: %d: the current working directory has changed from '%s' to '%s'\n", fname, lineno, p, curdir);
+ oodate = TRUE;
+ }
+ }
+ }
+
+ fclose(fp);
+ if (!Lst_IsEmpty(missingFiles)) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: missing files: %s...\n",
+ fname, (char *)Lst_Datum(Lst_First(missingFiles)));
+ oodate = TRUE;
+ }
+ if (!oodate && !have_filemon && filemonMissing) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: missing filemon data\n", fname);
+ oodate = TRUE;
+ }
+ } else {
+ if (writeMeta && metaMissing) {
+ cp = NULL;
+
+ /* if target is in .CURDIR we do not need a meta file */
+ if (gn->path && (cp = strrchr(gn->path, '/')) && cp > gn->path) {
+ if (strncmp(curdir, gn->path, (cp - gn->path)) != 0) {
+ cp = NULL; /* not in .CURDIR */
+ }
+ }
+ if (!cp) {
+ if (DEBUG(META))
+ fprintf(debug_file, "%s: required but missing\n", fname);
+ oodate = TRUE;
+ needOODATE = TRUE; /* assume the worst */
+ }
+ }
+ }
+
+ Lst_Destroy(missingFiles, (FreeProc *)free);
+
+ if (oodate && needOODATE) {
+ /*
+ * Target uses .OODATE which is empty; or we wouldn't be here.
+ * We have decided it is oodate, so .OODATE needs to be set.
+ * All we can sanely do is set it to .ALLSRC.
+ */
+ Var_Delete(OODATE, gn);
+ Var_Set(OODATE, Var_Value(ALLSRC, gn, &cp), gn, 0);
+ free(cp);
+ }
+
+ oodate_out:
+ for (i--; i >= 0; i--) {
+ free(pa[i]);
+ }
+ return oodate;
+}
+
+/* support for compat mode */
+
+static int childPipe[2];
+
+void
+meta_compat_start(void)
+{
+#ifdef USE_FILEMON_ONCE
+ /*
+ * We need to re-open filemon for each cmd.
+ */
+ BuildMon *pbm = &Mybm;
+
+ if (pbm->mfp != NULL && useFilemon) {
+ filemon_open(pbm);
+ } else {
+ pbm->mon_fd = pbm->filemon_fd = -1;
+ }
+#endif
+ if (pipe(childPipe) < 0)
+ Punt("Cannot create pipe: %s", strerror(errno));
+ /* Set close-on-exec flag for both */
+ (void)fcntl(childPipe[0], F_SETFD, FD_CLOEXEC);
+ (void)fcntl(childPipe[1], F_SETFD, FD_CLOEXEC);
+}
+
+void
+meta_compat_child(void)
+{
+ meta_job_child(NULL);
+ if (dup2(childPipe[1], 1) < 0 ||
+ dup2(1, 2) < 0) {
+ execError("dup2", "pipe");
+ _exit(1);
+ }
+}
+
+void
+meta_compat_parent(void)
+{
+ FILE *fp;
+ char buf[BUFSIZ];
+
+ close(childPipe[1]); /* child side */
+ fp = fdopen(childPipe[0], "r");
+ while (fgets(buf, sizeof(buf), fp)) {
+ meta_job_output(NULL, buf, "");
+ printf("%s", buf);
+ fflush(stdout);
+ }
+ fclose(fp);
+}
+
+#endif /* USE_META */
diff --git a/usr.bin/make/meta.h b/usr.bin/make/meta.h
new file mode 100644
index 0000000..8f1018e
--- /dev/null
+++ b/usr.bin/make/meta.h
@@ -0,0 +1,56 @@
+/* $NetBSD: meta.h,v 1.5 2016/05/12 20:28:34 sjg Exp $ */
+
+/*
+ * Things needed for 'meta' mode.
+ */
+/*
+ * Copyright (c) 2009-2010, Juniper Networks, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+typedef struct BuildMon {
+ char meta_fname[MAXPATHLEN];
+ int filemon_fd;
+ int mon_fd;
+ FILE *mfp;
+} BuildMon;
+
+extern Boolean useMeta;
+
+struct Job; /* not defined yet */
+void meta_init(void);
+void meta_finish(void);
+void meta_mode_init(const char *);
+void meta_job_start(struct Job *, GNode *);
+void meta_job_child(struct Job *);
+void meta_job_error(struct Job *, GNode *, int, int);
+void meta_job_output(struct Job *, char *, const char *);
+int meta_cmd_finish(void *);
+int meta_job_finish(struct Job *);
+Boolean meta_oodate(GNode *, Boolean);
+void meta_compat_start(void);
+void meta_compat_child(void);
+void meta_compat_parent(void);
diff --git a/usr.bin/make/metachar.c b/usr.bin/make/metachar.c
new file mode 100644
index 0000000..4960338
--- /dev/null
+++ b/usr.bin/make/metachar.c
@@ -0,0 +1,88 @@
+/* $NetBSD: metachar.c,v 1.5 2015/06/19 08:03:35 mlelstv Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#if defined(MAKE_NATIVE) || defined(HAVE_NBTOOL_CONFIG_H)
+#include <sys/cdefs.h>
+#endif
+
+#if defined(__RCSID) && !defined(lint)
+__RCSID("$NetBSD: metachar.c,v 1.5 2015/06/19 08:03:35 mlelstv Exp $");
+#endif
+
+#include "metachar.h"
+/*
+ * The following array is used to make a fast determination of which
+ * characters are interpreted specially by the shell. If a command
+ * contains any of these characters, it is executed by the shell, not
+ * directly by us.
+ *
+ * perhaps move it to ctype?
+ */
+
+unsigned char _metachar[128] = {
+// nul soh stx etx eot enq ack bel
+ 1, 0, 0, 0, 0, 0, 0, 0,
+// bs ht nl vt np cr so si
+ 0, 0, 1, 0, 0, 0, 0, 0,
+// dle dc1 dc2 dc3 dc4 nak syn etb
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// can em sub esc fs gs rs us
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// sp ! " # $ % & '
+ 0, 1, 1, 1, 1, 0, 1, 1,
+// ( ) * + , - . /
+ 1, 1, 1, 0, 0, 0, 0, 0,
+// 0 1 2 3 4 5 6 7
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// 8 9 : ; < = > ?
+ 0, 0, 0, 1, 1, 0, 1, 1,
+// @ A B C D E F G
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// H I J K L M N O
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// P Q R S T U V W
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// X Y Z [ \ ] ^ _
+ 0, 0, 0, 1, 1, 1, 1, 0,
+// ` a b c d e f g
+ 1, 0, 0, 0, 0, 0, 0, 0,
+// h i j k l m n o
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// p q r s t u v w
+ 0, 0, 0, 0, 0, 0, 0, 0,
+// x y z { | } ~ del
+ 0, 0, 0, 1, 1, 1, 1, 0,
+};
+
diff --git a/usr.bin/make/metachar.h b/usr.bin/make/metachar.h
new file mode 100644
index 0000000..db88d67
--- /dev/null
+++ b/usr.bin/make/metachar.h
@@ -0,0 +1,61 @@
+/* $NetBSD: metachar.h,v 1.4 2015/06/21 20:26:02 christos Exp $ */
+
+/*-
+ * Copyright (c) 2015 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _METACHAR_H
+#define _METACHAR_H
+
+#include <ctype.h>
+
+extern unsigned char _metachar[];
+
+#define ismeta(c) _metachar[(c) & 0x7f]
+
+static inline int
+hasmeta(const char *cmd)
+{
+ while (!ismeta(*cmd))
+ cmd++;
+
+ return *cmd != '\0';
+}
+
+static inline int
+needshell(const char *cmd, int white)
+{
+ while (!ismeta(*cmd) && *cmd != ':' && *cmd != '=') {
+ if (white && isspace((unsigned char)*cmd))
+ break;
+ cmd++;
+ }
+
+ return *cmd != '\0';
+}
+
+#endif /* _METACHAR_H */
diff --git a/usr.bin/make/nonints.h b/usr.bin/make/nonints.h
new file mode 100644
index 0000000..2fe2330
--- /dev/null
+++ b/usr.bin/make/nonints.h
@@ -0,0 +1,198 @@
+/* $NetBSD: nonints.h,v 1.74 2016/09/05 00:40:29 sevan Exp $ */
+
+/*-
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)nonints.h 8.3 (Berkeley) 3/19/94
+ */
+
+/*-
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)nonints.h 8.3 (Berkeley) 3/19/94
+ */
+
+/* arch.c */
+ReturnStatus Arch_ParseArchive(char **, Lst, GNode *);
+void Arch_Touch(GNode *);
+void Arch_TouchLib(GNode *);
+time_t Arch_MTime(GNode *);
+time_t Arch_MemMTime(GNode *);
+void Arch_FindLib(GNode *, Lst);
+Boolean Arch_LibOODate(GNode *);
+void Arch_Init(void);
+void Arch_End(void);
+int Arch_IsLib(GNode *);
+
+/* compat.c */
+int CompatRunCommand(void *, void *);
+void Compat_Run(Lst);
+int Compat_Make(void *, void *);
+
+/* cond.c */
+struct If;
+int Cond_EvalExpression(const struct If *, char *, Boolean *, int, Boolean);
+int Cond_Eval(char *);
+void Cond_restore_depth(unsigned int);
+unsigned int Cond_save_depth(void);
+
+/* for.c */
+int For_Eval(char *);
+int For_Accum(char *);
+void For_Run(int);
+
+/* job.c */
+void JobReapChild(pid_t, int, Boolean);
+
+/* main.c */
+void Main_ParseArgLine(const char *);
+void MakeMode(const char *);
+char *Cmd_Exec(const char *, const char **);
+void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
+void Fatal(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD;
+void Punt(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD;
+void DieHorribly(void) MAKE_ATTR_DEAD;
+int PrintAddr(void *, void *);
+void Finish(int) MAKE_ATTR_DEAD;
+int eunlink(const char *);
+void execError(const char *, const char *);
+char *getTmpdir(void);
+Boolean s2Boolean(const char *, Boolean);
+Boolean getBoolean(const char *, Boolean);
+char *cached_realpath(const char *, char *);
+
+/* parse.c */
+void Parse_Error(int, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3);
+Boolean Parse_AnyExport(void);
+Boolean Parse_IsVar(char *);
+void Parse_DoVar(char *, GNode *);
+void Parse_AddIncludeDir(char *);
+void Parse_File(const char *, int);
+void Parse_Init(void);
+void Parse_End(void);
+void Parse_SetInput(const char *, int, int, char *(*)(void *, size_t *), void *);
+Lst Parse_MainName(void);
+
+/* str.c */
+char *str_concat(const char *, const char *, int);
+char **brk_string(const char *, int *, Boolean, char **);
+char *Str_FindSubstring(const char *, const char *);
+int Str_Match(const char *, const char *);
+char *Str_SYSVMatch(const char *, const char *, int *len);
+void Str_SYSVSubst(Buffer *, char *, char *, int);
+
+/* suff.c */
+void Suff_ClearSuffixes(void);
+Boolean Suff_IsTransform(char *);
+GNode *Suff_AddTransform(char *);
+int Suff_EndTransform(void *, void *);
+void Suff_AddSuffix(char *, GNode **);
+Lst Suff_GetPath(char *);
+void Suff_DoPaths(void);
+void Suff_AddInclude(char *);
+void Suff_AddLib(char *);
+void Suff_FindDeps(GNode *);
+Lst Suff_FindPath(GNode *);
+void Suff_SetNull(char *);
+void Suff_Init(void);
+void Suff_End(void);
+void Suff_PrintAll(void);
+
+/* targ.c */
+void Targ_Init(void);
+void Targ_End(void);
+Lst Targ_List(void);
+GNode *Targ_NewGN(const char *);
+GNode *Targ_FindNode(const char *, int);
+Lst Targ_FindList(Lst, int);
+Boolean Targ_Ignore(GNode *);
+Boolean Targ_Silent(GNode *);
+Boolean Targ_Precious(GNode *);
+void Targ_SetMain(GNode *);
+int Targ_PrintCmd(void *, void *);
+int Targ_PrintNode(void *, void *);
+char *Targ_FmtTime(time_t);
+void Targ_PrintType(int);
+void Targ_PrintGraph(int);
+void Targ_Propagate(void);
+void Targ_Propagate_Wait(void);
+
+/* var.c */
+void Var_Delete(const char *, GNode *);
+void Var_Set(const char *, const char *, GNode *, int);
+void Var_Append(const char *, const char *, GNode *);
+Boolean Var_Exists(const char *, GNode *);
+char *Var_Value(const char *, GNode *, char **);
+char *Var_Parse(const char *, GNode *, int, int *, void **);
+char *Var_Subst(const char *, const char *, GNode *, int);
+char *Var_GetTail(const char *);
+char *Var_GetHead(const char *);
+void Var_Init(void);
+void Var_End(void);
+void Var_Dump(GNode *);
+void Var_ExportVars(void);
+void Var_Export(char *, int);
+void Var_UnExport(char *);
+
+/* util.c */
+void (*bmake_signal(int, void (*)(int)))(int);
diff --git a/usr.bin/make/parse.c b/usr.bin/make/parse.c
new file mode 100644
index 0000000..d7cecec
--- /dev/null
+++ b/usr.bin/make/parse.c
@@ -0,0 +1,3357 @@
+/* $NetBSD: parse.c,v 1.231 2018/12/22 00:36:32 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: parse.c,v 1.231 2018/12/22 00:36:32 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)parse.c 8.3 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: parse.c,v 1.231 2018/12/22 00:36:32 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * parse.c --
+ * Functions to parse a makefile.
+ *
+ * One function, Parse_Init, must be called before any functions
+ * in this module are used. After that, the function Parse_File is the
+ * main entry point and controls most of the other functions in this
+ * module.
+ *
+ * Most important structures are kept in Lsts. Directories for
+ * the .include "..." function are kept in the 'parseIncPath' Lst, while
+ * those for the .include <...> are kept in the 'sysIncPath' Lst. The
+ * targets currently being defined are kept in the 'targets' Lst.
+ *
+ * The variables 'fname' and 'lineno' are used to track the name
+ * of the current file and the line number in that file so that error
+ * messages can be more meaningful.
+ *
+ * Interface:
+ * Parse_Init Initialization function which must be
+ * called before anything else in this module
+ * is used.
+ *
+ * Parse_End Cleanup the module
+ *
+ * Parse_File Function used to parse a makefile. It must
+ * be given the name of the file, which should
+ * already have been opened, and a function
+ * to call to read a character from the file.
+ *
+ * Parse_IsVar Returns TRUE if the given line is a
+ * variable assignment. Used by MainParseArgs
+ * to determine if an argument is a target
+ * or a variable assignment. Used internally
+ * for pretty much the same thing...
+ *
+ * Parse_Error Function called when an error occurs in
+ * parsing. Used by the variable and
+ * conditional modules.
+ * Parse_MainName Returns a Lst of the main target to create.
+ */
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdint.h>
+
+#ifndef MAP_FILE
+#define MAP_FILE 0
+#endif
+#ifndef MAP_COPY
+#define MAP_COPY MAP_PRIVATE
+#endif
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "buf.h"
+#include "pathnames.h"
+
+////////////////////////////////////////////////////////////
+// types and constants
+
+/*
+ * Structure for a file being read ("included file")
+ */
+typedef struct IFile {
+ char *fname; /* name of file */
+ int lineno; /* current line number in file */
+ int first_lineno; /* line number of start of text */
+ int cond_depth; /* 'if' nesting when file opened */
+ Boolean depending; /* state of doing_depend on EOF */
+ char *P_str; /* point to base of string buffer */
+ char *P_ptr; /* point to next char of string buffer */
+ char *P_end; /* point to the end of string buffer */
+ char *(*nextbuf)(void *, size_t *); /* Function to get more data */
+ void *nextbuf_arg; /* Opaque arg for nextbuf() */
+ struct loadedfile *lf; /* loadedfile object, if any */
+} IFile;
+
+
+/*
+ * These values are returned by ParseEOF to tell Parse_File whether to
+ * CONTINUE parsing, i.e. it had only reached the end of an include file,
+ * or if it's DONE.
+ */
+#define CONTINUE 1
+#define DONE 0
+
+/*
+ * Tokens for target attributes
+ */
+typedef enum {
+ Begin, /* .BEGIN */
+ Default, /* .DEFAULT */
+ DeleteOnError, /* .DELETE_ON_ERROR */
+ End, /* .END */
+ dotError, /* .ERROR */
+ Ignore, /* .IGNORE */
+ Includes, /* .INCLUDES */
+ Interrupt, /* .INTERRUPT */
+ Libs, /* .LIBS */
+ Meta, /* .META */
+ MFlags, /* .MFLAGS or .MAKEFLAGS */
+ Main, /* .MAIN and we don't have anything user-specified to
+ * make */
+ NoExport, /* .NOEXPORT */
+ NoMeta, /* .NOMETA */
+ NoMetaCmp, /* .NOMETA_CMP */
+ NoPath, /* .NOPATH */
+ Not, /* Not special */
+ NotParallel, /* .NOTPARALLEL */
+ Null, /* .NULL */
+ ExObjdir, /* .OBJDIR */
+ Order, /* .ORDER */
+ Parallel, /* .PARALLEL */
+ ExPath, /* .PATH */
+ Phony, /* .PHONY */
+#ifdef POSIX
+ Posix, /* .POSIX */
+#endif
+ Precious, /* .PRECIOUS */
+ ExShell, /* .SHELL */
+ Silent, /* .SILENT */
+ SingleShell, /* .SINGLESHELL */
+ Stale, /* .STALE */
+ Suffixes, /* .SUFFIXES */
+ Wait, /* .WAIT */
+ Attribute /* Generic attribute */
+} ParseSpecial;
+
+/*
+ * Other tokens
+ */
+#define LPAREN '('
+#define RPAREN ')'
+
+
+////////////////////////////////////////////////////////////
+// result data
+
+/*
+ * The main target to create. This is the first target on the first
+ * dependency line in the first makefile.
+ */
+static GNode *mainNode;
+
+////////////////////////////////////////////////////////////
+// eval state
+
+/* targets we're working on */
+static Lst targets;
+
+#ifdef CLEANUP
+/* command lines for targets */
+static Lst targCmds;
+#endif
+
+/*
+ * specType contains the SPECial TYPE of the current target. It is
+ * Not if the target is unspecial. If it *is* special, however, the children
+ * are linked as children of the parent but not vice versa. This variable is
+ * set in ParseDoDependency
+ */
+static ParseSpecial specType;
+
+/*
+ * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER
+ * seen, then set to each successive source on the line.
+ */
+static GNode *predecessor;
+
+////////////////////////////////////////////////////////////
+// parser state
+
+/* true if currently in a dependency line or its commands */
+static Boolean inLine;
+
+/* number of fatal errors */
+static int fatals = 0;
+
+/*
+ * Variables for doing includes
+ */
+
+/* current file being read */
+static IFile *curFile;
+
+/* stack of IFiles generated by .includes */
+static Lst includes;
+
+/* include paths (lists of directories) */
+Lst parseIncPath; /* dirs for "..." includes */
+Lst sysIncPath; /* dirs for <...> includes */
+Lst defIncPath; /* default for sysIncPath */
+
+////////////////////////////////////////////////////////////
+// parser tables
+
+/*
+ * The parseKeywords table is searched using binary search when deciding
+ * if a target or source is special. The 'spec' field is the ParseSpecial
+ * type of the keyword ("Not" if the keyword isn't special as a target) while
+ * the 'op' field is the operator to apply to the list of targets if the
+ * keyword is used as a source ("0" if the keyword isn't special as a source)
+ */
+static const struct {
+ const char *name; /* Name of keyword */
+ ParseSpecial spec; /* Type when used as a target */
+ int op; /* Operator when used as a source */
+} parseKeywords[] = {
+{ ".BEGIN", Begin, 0 },
+{ ".DEFAULT", Default, 0 },
+{ ".DELETE_ON_ERROR", DeleteOnError, 0 },
+{ ".END", End, 0 },
+{ ".ERROR", dotError, 0 },
+{ ".EXEC", Attribute, OP_EXEC },
+{ ".IGNORE", Ignore, OP_IGNORE },
+{ ".INCLUDES", Includes, 0 },
+{ ".INTERRUPT", Interrupt, 0 },
+{ ".INVISIBLE", Attribute, OP_INVISIBLE },
+{ ".JOIN", Attribute, OP_JOIN },
+{ ".LIBS", Libs, 0 },
+{ ".MADE", Attribute, OP_MADE },
+{ ".MAIN", Main, 0 },
+{ ".MAKE", Attribute, OP_MAKE },
+{ ".MAKEFLAGS", MFlags, 0 },
+{ ".META", Meta, OP_META },
+{ ".MFLAGS", MFlags, 0 },
+{ ".NOMETA", NoMeta, OP_NOMETA },
+{ ".NOMETA_CMP", NoMetaCmp, OP_NOMETA_CMP },
+{ ".NOPATH", NoPath, OP_NOPATH },
+{ ".NOTMAIN", Attribute, OP_NOTMAIN },
+{ ".NOTPARALLEL", NotParallel, 0 },
+{ ".NO_PARALLEL", NotParallel, 0 },
+{ ".NULL", Null, 0 },
+{ ".OBJDIR", ExObjdir, 0 },
+{ ".OPTIONAL", Attribute, OP_OPTIONAL },
+{ ".ORDER", Order, 0 },
+{ ".PARALLEL", Parallel, 0 },
+{ ".PATH", ExPath, 0 },
+{ ".PHONY", Phony, OP_PHONY },
+#ifdef POSIX
+{ ".POSIX", Posix, 0 },
+#endif
+{ ".PRECIOUS", Precious, OP_PRECIOUS },
+{ ".RECURSIVE", Attribute, OP_MAKE },
+{ ".SHELL", ExShell, 0 },
+{ ".SILENT", Silent, OP_SILENT },
+{ ".SINGLESHELL", SingleShell, 0 },
+{ ".STALE", Stale, 0 },
+{ ".SUFFIXES", Suffixes, 0 },
+{ ".USE", Attribute, OP_USE },
+{ ".USEBEFORE", Attribute, OP_USEBEFORE },
+{ ".WAIT", Wait, 0 },
+};
+
+////////////////////////////////////////////////////////////
+// local functions
+
+static int ParseIsEscaped(const char *, const char *);
+static void ParseErrorInternal(const char *, size_t, int, const char *, ...)
+ MAKE_ATTR_PRINTFLIKE(4,5);
+static void ParseVErrorInternal(FILE *, const char *, size_t, int, const char *, va_list)
+ MAKE_ATTR_PRINTFLIKE(5, 0);
+static int ParseFindKeyword(const char *);
+static int ParseLinkSrc(void *, void *);
+static int ParseDoOp(void *, void *);
+static void ParseDoSrc(int, const char *);
+static int ParseFindMain(void *, void *);
+static int ParseAddDir(void *, void *);
+static int ParseClearPath(void *, void *);
+static void ParseDoDependency(char *);
+static int ParseAddCmd(void *, void *);
+static void ParseHasCommands(void *);
+static void ParseDoInclude(char *);
+static void ParseSetParseFile(const char *);
+static void ParseSetIncludedFile(void);
+#ifdef GMAKEEXPORT
+static void ParseGmakeExport(char *);
+#endif
+static int ParseEOF(void);
+static char *ParseReadLine(void);
+static void ParseFinishLine(void);
+static void ParseMark(GNode *);
+
+////////////////////////////////////////////////////////////
+// file loader
+
+struct loadedfile {
+ const char *path; /* name, for error reports */
+ char *buf; /* contents buffer */
+ size_t len; /* length of contents */
+ size_t maplen; /* length of mmap area, or 0 */
+ Boolean used; /* XXX: have we used the data yet */
+};
+
+/*
+ * Constructor/destructor for loadedfile
+ */
+static struct loadedfile *
+loadedfile_create(const char *path)
+{
+ struct loadedfile *lf;
+
+ lf = bmake_malloc(sizeof(*lf));
+ lf->path = (path == NULL ? "(stdin)" : path);
+ lf->buf = NULL;
+ lf->len = 0;
+ lf->maplen = 0;
+ lf->used = FALSE;
+ return lf;
+}
+
+static void
+loadedfile_destroy(struct loadedfile *lf)
+{
+ if (lf->buf != NULL) {
+ if (lf->maplen > 0) {
+ munmap(lf->buf, lf->maplen);
+ } else {
+ free(lf->buf);
+ }
+ }
+ free(lf);
+}
+
+/*
+ * nextbuf() operation for loadedfile, as needed by the weird and twisted
+ * logic below. Once that's cleaned up, we can get rid of lf->used...
+ */
+static char *
+loadedfile_nextbuf(void *x, size_t *len)
+{
+ struct loadedfile *lf = x;
+
+ if (lf->used) {
+ return NULL;
+ }
+ lf->used = TRUE;
+ *len = lf->len;
+ return lf->buf;
+}
+
+/*
+ * Try to get the size of a file.
+ */
+static ReturnStatus
+load_getsize(int fd, size_t *ret)
+{
+ struct stat st;
+
+ if (fstat(fd, &st) < 0) {
+ return FAILURE;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ return FAILURE;
+ }
+
+ /*
+ * st_size is an off_t, which is 64 bits signed; *ret is
+ * size_t, which might be 32 bits unsigned or 64 bits
+ * unsigned. Rather than being elaborate, just punt on
+ * files that are more than 2^31 bytes. We should never
+ * see a makefile that size in practice...
+ *
+ * While we're at it reject negative sizes too, just in case.
+ */
+ if (st.st_size < 0 || st.st_size > 0x7fffffff) {
+ return FAILURE;
+ }
+
+ *ret = (size_t) st.st_size;
+ return SUCCESS;
+}
+
+/*
+ * Read in a file.
+ *
+ * Until the path search logic can be moved under here instead of
+ * being in the caller in another source file, we need to have the fd
+ * passed in already open. Bleh.
+ *
+ * If the path is NULL use stdin and (to insure against fd leaks)
+ * assert that the caller passed in -1.
+ */
+static struct loadedfile *
+loadfile(const char *path, int fd)
+{
+ struct loadedfile *lf;
+ static long pagesize = 0;
+ ssize_t result;
+ size_t bufpos;
+
+ lf = loadedfile_create(path);
+
+ if (path == NULL) {
+ assert(fd == -1);
+ fd = STDIN_FILENO;
+ } else {
+#if 0 /* notyet */
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ ...
+ Error("%s: %s", path, strerror(errno));
+ exit(1);
+ }
+#endif
+ }
+
+ if (load_getsize(fd, &lf->len) == SUCCESS) {
+ /* found a size, try mmap */
+ if (pagesize == 0)
+ pagesize = sysconf(_SC_PAGESIZE);
+ if (pagesize <= 0) {
+ pagesize = 0x1000;
+ }
+ /* round size up to a page */
+ lf->maplen = pagesize * ((lf->len + pagesize - 1)/pagesize);
+
+ /*
+ * XXX hack for dealing with empty files; remove when
+ * we're no longer limited by interfacing to the old
+ * logic elsewhere in this file.
+ */
+ if (lf->maplen == 0) {
+ lf->maplen = pagesize;
+ }
+
+ /*
+ * FUTURE: remove PROT_WRITE when the parser no longer
+ * needs to scribble on the input.
+ */
+ lf->buf = mmap(NULL, lf->maplen, PROT_READ|PROT_WRITE,
+ MAP_FILE|MAP_COPY, fd, 0);
+ if (lf->buf != MAP_FAILED) {
+ /* succeeded */
+ if (lf->len == lf->maplen && lf->buf[lf->len - 1] != '\n') {
+ char *b = bmake_malloc(lf->len + 1);
+ b[lf->len] = '\n';
+ memcpy(b, lf->buf, lf->len++);
+ munmap(lf->buf, lf->maplen);
+ lf->maplen = 0;
+ lf->buf = b;
+ }
+ goto done;
+ }
+ }
+
+ /* cannot mmap; load the traditional way */
+
+ lf->maplen = 0;
+ lf->len = 1024;
+ lf->buf = bmake_malloc(lf->len);
+
+ bufpos = 0;
+ while (1) {
+ assert(bufpos <= lf->len);
+ if (bufpos == lf->len) {
+ if (lf->len > SIZE_MAX/2) {
+ errno = EFBIG;
+ Error("%s: file too large", path);
+ exit(1);
+ }
+ lf->len *= 2;
+ lf->buf = bmake_realloc(lf->buf, lf->len);
+ }
+ assert(bufpos < lf->len);
+ result = read(fd, lf->buf + bufpos, lf->len - bufpos);
+ if (result < 0) {
+ Error("%s: read error: %s", path, strerror(errno));
+ exit(1);
+ }
+ if (result == 0) {
+ break;
+ }
+ bufpos += result;
+ }
+ assert(bufpos <= lf->len);
+ lf->len = bufpos;
+
+ /* truncate malloc region to actual length (maybe not useful) */
+ if (lf->len > 0) {
+ /* as for mmap case, ensure trailing \n */
+ if (lf->buf[lf->len - 1] != '\n')
+ lf->len++;
+ lf->buf = bmake_realloc(lf->buf, lf->len);
+ lf->buf[lf->len - 1] = '\n';
+ }
+
+done:
+ if (path != NULL) {
+ close(fd);
+ }
+ return lf;
+}
+
+////////////////////////////////////////////////////////////
+// old code
+
+/*-
+ *----------------------------------------------------------------------
+ * ParseIsEscaped --
+ * Check if the current character is escaped on the current line
+ *
+ * Results:
+ * 0 if the character is not backslash escaped, 1 otherwise
+ *
+ * Side Effects:
+ * None
+ *----------------------------------------------------------------------
+ */
+static int
+ParseIsEscaped(const char *line, const char *c)
+{
+ int active = 0;
+ for (;;) {
+ if (line == c)
+ return active;
+ if (*--c != '\\')
+ return active;
+ active = !active;
+ }
+}
+
+/*-
+ *----------------------------------------------------------------------
+ * ParseFindKeyword --
+ * Look in the table of keywords for one matching the given string.
+ *
+ * Input:
+ * str String to find
+ *
+ * Results:
+ * The index of the keyword, or -1 if it isn't there.
+ *
+ * Side Effects:
+ * None
+ *----------------------------------------------------------------------
+ */
+static int
+ParseFindKeyword(const char *str)
+{
+ int start, end, cur;
+ int diff;
+
+ start = 0;
+ end = (sizeof(parseKeywords)/sizeof(parseKeywords[0])) - 1;
+
+ do {
+ cur = start + ((end - start) / 2);
+ diff = strcmp(str, parseKeywords[cur].name);
+
+ if (diff == 0) {
+ return (cur);
+ } else if (diff < 0) {
+ end = cur - 1;
+ } else {
+ start = cur + 1;
+ }
+ } while (start <= end);
+ return (-1);
+}
+
+/*-
+ * ParseVErrorInternal --
+ * Error message abort function for parsing. Prints out the context
+ * of the error (line number and file) as well as the message with
+ * two optional arguments.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * "fatals" is incremented if the level is PARSE_FATAL.
+ */
+/* VARARGS */
+static void
+ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno, int type,
+ const char *fmt, va_list ap)
+{
+ static Boolean fatal_warning_error_printed = FALSE;
+
+ (void)fprintf(f, "%s: ", progname);
+
+ if (cfname != NULL) {
+ (void)fprintf(f, "\"");
+ if (*cfname != '/' && strcmp(cfname, "(stdin)") != 0) {
+ char *cp, *cp2;
+ const char *dir, *fname;
+
+ /*
+ * Nothing is more annoying than not knowing
+ * which Makefile is the culprit; we try ${.PARSEDIR}
+ * and apply realpath(3) if not absolute.
+ */
+ dir = Var_Value(".PARSEDIR", VAR_GLOBAL, &cp);
+ if (dir == NULL)
+ dir = ".";
+ if (*dir != '/') {
+ dir = cp2 = realpath(dir, NULL);
+ free(cp);
+ cp = cp2; /* cp2 set to NULL by Var_Value */
+ }
+ fname = Var_Value(".PARSEFILE", VAR_GLOBAL, &cp2);
+ if (fname == NULL) {
+ if ((fname = strrchr(cfname, '/')))
+ fname++;
+ else
+ fname = cfname;
+ }
+ (void)fprintf(f, "%s/%s", dir, fname);
+ free(cp2);
+ free(cp);
+ } else
+ (void)fprintf(f, "%s", cfname);
+
+ (void)fprintf(f, "\" line %d: ", (int)clineno);
+ }
+ if (type == PARSE_WARNING)
+ (void)fprintf(f, "warning: ");
+ (void)vfprintf(f, fmt, ap);
+ (void)fprintf(f, "\n");
+ (void)fflush(f);
+ if (type == PARSE_INFO)
+ return;
+ if (type == PARSE_FATAL || parseWarnFatal)
+ fatals += 1;
+ if (parseWarnFatal && !fatal_warning_error_printed) {
+ Error("parsing warnings being treated as errors");
+ fatal_warning_error_printed = TRUE;
+ }
+}
+
+/*-
+ * ParseErrorInternal --
+ * Error function
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * None
+ */
+/* VARARGS */
+static void
+ParseErrorInternal(const char *cfname, size_t clineno, int type,
+ const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)fflush(stdout);
+ ParseVErrorInternal(stderr, cfname, clineno, type, fmt, ap);
+ va_end(ap);
+
+ if (debug_file != stderr && debug_file != stdout) {
+ va_start(ap, fmt);
+ ParseVErrorInternal(debug_file, cfname, clineno, type, fmt, ap);
+ va_end(ap);
+ }
+}
+
+/*-
+ * Parse_Error --
+ * External interface to ParseErrorInternal; uses the default filename
+ * Line number.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * None
+ */
+/* VARARGS */
+void
+Parse_Error(int type, const char *fmt, ...)
+{
+ va_list ap;
+ const char *fname;
+ size_t lineno;
+
+ if (curFile == NULL) {
+ fname = NULL;
+ lineno = 0;
+ } else {
+ fname = curFile->fname;
+ lineno = curFile->lineno;
+ }
+
+ va_start(ap, fmt);
+ (void)fflush(stdout);
+ ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap);
+ va_end(ap);
+
+ if (debug_file != stderr && debug_file != stdout) {
+ va_start(ap, fmt);
+ ParseVErrorInternal(debug_file, fname, lineno, type, fmt, ap);
+ va_end(ap);
+ }
+}
+
+
+/*
+ * ParseMessage
+ * Parse a .info .warning or .error directive
+ *
+ * The input is the line minus the ".". We substitute
+ * variables, print the message and exit(1) (for .error) or just print
+ * a warning if the directive is malformed.
+ */
+static Boolean
+ParseMessage(char *line)
+{
+ int mtype;
+
+ switch(*line) {
+ case 'i':
+ mtype = PARSE_INFO;
+ break;
+ case 'w':
+ mtype = PARSE_WARNING;
+ break;
+ case 'e':
+ mtype = PARSE_FATAL;
+ break;
+ default:
+ Parse_Error(PARSE_WARNING, "invalid syntax: \".%s\"", line);
+ return FALSE;
+ }
+
+ while (isalpha((unsigned char)*line))
+ line++;
+ if (!isspace((unsigned char)*line))
+ return FALSE; /* not for us */
+ while (isspace((unsigned char)*line))
+ line++;
+
+ line = Var_Subst(NULL, line, VAR_CMD, VARF_WANTRES);
+ Parse_Error(mtype, "%s", line);
+ free(line);
+
+ if (mtype == PARSE_FATAL) {
+ /* Terminate immediately. */
+ exit(1);
+ }
+ return TRUE;
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseLinkSrc --
+ * Link the parent node to its new child. Used in a Lst_ForEach by
+ * ParseDoDependency. If the specType isn't 'Not', the parent
+ * isn't linked as a parent of the child.
+ *
+ * Input:
+ * pgnp The parent node
+ * cgpn The child node
+ *
+ * Results:
+ * Always = 0
+ *
+ * Side Effects:
+ * New elements are added to the parents list of cgn and the
+ * children list of cgn. the unmade field of pgn is updated
+ * to reflect the additional child.
+ *---------------------------------------------------------------------
+ */
+static int
+ParseLinkSrc(void *pgnp, void *cgnp)
+{
+ GNode *pgn = (GNode *)pgnp;
+ GNode *cgn = (GNode *)cgnp;
+
+ if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (pgn->cohorts))
+ pgn = (GNode *)Lst_Datum(Lst_Last(pgn->cohorts));
+ (void)Lst_AtEnd(pgn->children, cgn);
+ if (specType == Not)
+ (void)Lst_AtEnd(cgn->parents, pgn);
+ pgn->unmade += 1;
+ if (DEBUG(PARSE)) {
+ fprintf(debug_file, "# %s: added child %s - %s\n", __func__,
+ pgn->name, cgn->name);
+ Targ_PrintNode(pgn, 0);
+ Targ_PrintNode(cgn, 0);
+ }
+ return (0);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseDoOp --
+ * Apply the parsed operator to the given target node. Used in a
+ * Lst_ForEach call by ParseDoDependency once all targets have
+ * been found and their operator parsed. If the previous and new
+ * operators are incompatible, a major error is taken.
+ *
+ * Input:
+ * gnp The node to which the operator is to be applied
+ * opp The operator to apply
+ *
+ * Results:
+ * Always 0
+ *
+ * Side Effects:
+ * The type field of the node is altered to reflect any new bits in
+ * the op.
+ *---------------------------------------------------------------------
+ */
+static int
+ParseDoOp(void *gnp, void *opp)
+{
+ GNode *gn = (GNode *)gnp;
+ int op = *(int *)opp;
+ /*
+ * If the dependency mask of the operator and the node don't match and
+ * the node has actually had an operator applied to it before, and
+ * the operator actually has some dependency information in it, complain.
+ */
+ if (((op & OP_OPMASK) != (gn->type & OP_OPMASK)) &&
+ !OP_NOP(gn->type) && !OP_NOP(op))
+ {
+ Parse_Error(PARSE_FATAL, "Inconsistent operator for %s", gn->name);
+ return (1);
+ }
+
+ if ((op == OP_DOUBLEDEP) && ((gn->type & OP_OPMASK) == OP_DOUBLEDEP)) {
+ /*
+ * If the node was the object of a :: operator, we need to create a
+ * new instance of it for the children and commands on this dependency
+ * line. The new instance is placed on the 'cohorts' list of the
+ * initial one (note the initial one is not on its own cohorts list)
+ * and the new instance is linked to all parents of the initial
+ * instance.
+ */
+ GNode *cohort;
+
+ /*
+ * Propagate copied bits to the initial node. They'll be propagated
+ * back to the rest of the cohorts later.
+ */
+ gn->type |= op & ~OP_OPMASK;
+
+ cohort = Targ_FindNode(gn->name, TARG_NOHASH);
+ if (doing_depend)
+ ParseMark(cohort);
+ /*
+ * Make the cohort invisible as well to avoid duplicating it into
+ * other variables. True, parents of this target won't tend to do
+ * anything with their local variables, but better safe than
+ * sorry. (I think this is pointless now, since the relevant list
+ * traversals will no longer see this node anyway. -mycroft)
+ */
+ cohort->type = op | OP_INVISIBLE;
+ (void)Lst_AtEnd(gn->cohorts, cohort);
+ cohort->centurion = gn;
+ gn->unmade_cohorts += 1;
+ snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d",
+ gn->unmade_cohorts);
+ } else {
+ /*
+ * We don't want to nuke any previous flags (whatever they were) so we
+ * just OR the new operator into the old
+ */
+ gn->type |= op;
+ }
+
+ return (0);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseDoSrc --
+ * Given the name of a source, figure out if it is an attribute
+ * and apply it to the targets if it is. Else decide if there is
+ * some attribute which should be applied *to* the source because
+ * of some special target and apply it if so. Otherwise, make the
+ * source be a child of the targets in the list 'targets'
+ *
+ * Input:
+ * tOp operator (if any) from special targets
+ * src name of the source to handle
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Operator bits may be added to the list of targets or to the source.
+ * The targets may have a new source added to their lists of children.
+ *---------------------------------------------------------------------
+ */
+static void
+ParseDoSrc(int tOp, const char *src)
+{
+ GNode *gn = NULL;
+ static int wait_number = 0;
+ char wait_src[16];
+
+ if (*src == '.' && isupper ((unsigned char)src[1])) {
+ int keywd = ParseFindKeyword(src);
+ if (keywd != -1) {
+ int op = parseKeywords[keywd].op;
+ if (op != 0) {
+ Lst_ForEach(targets, ParseDoOp, &op);
+ return;
+ }
+ if (parseKeywords[keywd].spec == Wait) {
+ /*
+ * We add a .WAIT node in the dependency list.
+ * After any dynamic dependencies (and filename globbing)
+ * have happened, it is given a dependency on the each
+ * previous child back to and previous .WAIT node.
+ * The next child won't be scheduled until the .WAIT node
+ * is built.
+ * We give each .WAIT node a unique name (mainly for diag).
+ */
+ snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number);
+ gn = Targ_FindNode(wait_src, TARG_NOHASH);
+ if (doing_depend)
+ ParseMark(gn);
+ gn->type = OP_WAIT | OP_PHONY | OP_DEPENDS | OP_NOTMAIN;
+ Lst_ForEach(targets, ParseLinkSrc, gn);
+ return;
+ }
+ }
+ }
+
+ switch (specType) {
+ case Main:
+ /*
+ * If we have noted the existence of a .MAIN, it means we need
+ * to add the sources of said target to the list of things
+ * to create. The string 'src' is likely to be free, so we
+ * must make a new copy of it. Note that this will only be
+ * invoked if the user didn't specify a target on the command
+ * line. This is to allow #ifmake's to succeed, or something...
+ */
+ (void)Lst_AtEnd(create, bmake_strdup(src));
+ /*
+ * Add the name to the .TARGETS variable as well, so the user can
+ * employ that, if desired.
+ */
+ Var_Append(".TARGETS", src, VAR_GLOBAL);
+ return;
+
+ case Order:
+ /*
+ * Create proper predecessor/successor links between the previous
+ * source and the current one.
+ */
+ gn = Targ_FindNode(src, TARG_CREATE);
+ if (doing_depend)
+ ParseMark(gn);
+ if (predecessor != NULL) {
+ (void)Lst_AtEnd(predecessor->order_succ, gn);
+ (void)Lst_AtEnd(gn->order_pred, predecessor);
+ if (DEBUG(PARSE)) {
+ fprintf(debug_file, "# %s: added Order dependency %s - %s\n",
+ __func__, predecessor->name, gn->name);
+ Targ_PrintNode(predecessor, 0);
+ Targ_PrintNode(gn, 0);
+ }
+ }
+ /*
+ * The current source now becomes the predecessor for the next one.
+ */
+ predecessor = gn;
+ break;
+
+ default:
+ /*
+ * If the source is not an attribute, we need to find/create
+ * a node for it. After that we can apply any operator to it
+ * from a special target or link it to its parents, as
+ * appropriate.
+ *
+ * In the case of a source that was the object of a :: operator,
+ * the attribute is applied to all of its instances (as kept in
+ * the 'cohorts' list of the node) or all the cohorts are linked
+ * to all the targets.
+ */
+
+ /* Find/create the 'src' node and attach to all targets */
+ gn = Targ_FindNode(src, TARG_CREATE);
+ if (doing_depend)
+ ParseMark(gn);
+ if (tOp) {
+ gn->type |= tOp;
+ } else {
+ Lst_ForEach(targets, ParseLinkSrc, gn);
+ }
+ break;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseFindMain --
+ * Find a real target in the list and set it to be the main one.
+ * Called by ParseDoDependency when a main target hasn't been found
+ * yet.
+ *
+ * Input:
+ * gnp Node to examine
+ *
+ * Results:
+ * 0 if main not found yet, 1 if it is.
+ *
+ * Side Effects:
+ * mainNode is changed and Targ_SetMain is called.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ParseFindMain(void *gnp, void *dummy MAKE_ATTR_UNUSED)
+{
+ GNode *gn = (GNode *)gnp;
+ if ((gn->type & OP_NOTARGET) == 0) {
+ mainNode = gn;
+ Targ_SetMain(gn);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseAddDir --
+ * Front-end for Dir_AddDir to make sure Lst_ForEach keeps going
+ *
+ * Results:
+ * === 0
+ *
+ * Side Effects:
+ * See Dir_AddDir.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ParseAddDir(void *path, void *name)
+{
+ (void)Dir_AddDir((Lst) path, (char *)name);
+ return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseClearPath --
+ * Front-end for Dir_ClearPath to make sure Lst_ForEach keeps going
+ *
+ * Results:
+ * === 0
+ *
+ * Side Effects:
+ * See Dir_ClearPath
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ParseClearPath(void *path, void *dummy MAKE_ATTR_UNUSED)
+{
+ Dir_ClearPath((Lst) path);
+ return 0;
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseDoDependency --
+ * Parse the dependency line in line.
+ *
+ * Input:
+ * line the line to parse
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The nodes of the sources are linked as children to the nodes of the
+ * targets. Some nodes may be created.
+ *
+ * We parse a dependency line by first extracting words from the line and
+ * finding nodes in the list of all targets with that name. This is done
+ * until a character is encountered which is an operator character. Currently
+ * these are only ! and :. At this point the operator is parsed and the
+ * pointer into the line advanced until the first source is encountered.
+ * The parsed operator is applied to each node in the 'targets' list,
+ * which is where the nodes found for the targets are kept, by means of
+ * the ParseDoOp function.
+ * The sources are read in much the same way as the targets were except
+ * that now they are expanded using the wildcarding scheme of the C-Shell
+ * and all instances of the resulting words in the list of all targets
+ * are found. Each of the resulting nodes is then linked to each of the
+ * targets as one of its children.
+ * Certain targets are handled specially. These are the ones detailed
+ * by the specType variable.
+ * The storing of transformation rules is also taken care of here.
+ * A target is recognized as a transformation rule by calling
+ * Suff_IsTransform. If it is a transformation rule, its node is gotten
+ * from the suffix module via Suff_AddTransform rather than the standard
+ * Targ_FindNode in the target module.
+ *---------------------------------------------------------------------
+ */
+static void
+ParseDoDependency(char *line)
+{
+ char *cp; /* our current position */
+ GNode *gn = NULL; /* a general purpose temporary node */
+ int op; /* the operator on the line */
+ char savec; /* a place to save a character */
+ Lst paths; /* List of search paths to alter when parsing
+ * a list of .PATH targets */
+ int tOp; /* operator from special target */
+ Lst sources; /* list of archive source names after
+ * expansion */
+ Lst curTargs; /* list of target names to be found and added
+ * to the targets list */
+ char *lstart = line;
+
+ if (DEBUG(PARSE))
+ fprintf(debug_file, "ParseDoDependency(%s)\n", line);
+ tOp = 0;
+
+ specType = Not;
+ paths = NULL;
+
+ curTargs = Lst_Init(FALSE);
+
+ /*
+ * First, grind through the targets.
+ */
+
+ do {
+ /*
+ * Here LINE points to the beginning of the next word, and
+ * LSTART points to the actual beginning of the line.
+ */
+
+ /* Find the end of the next word. */
+ for (cp = line; *cp && (ParseIsEscaped(lstart, cp) ||
+ !(isspace((unsigned char)*cp) ||
+ *cp == '!' || *cp == ':' || *cp == LPAREN));
+ cp++) {
+ if (*cp == '$') {
+ /*
+ * Must be a dynamic source (would have been expanded
+ * otherwise), so call the Var module to parse the puppy
+ * so we can safely advance beyond it...There should be
+ * no errors in this, as they would have been discovered
+ * in the initial Var_Subst and we wouldn't be here.
+ */
+ int length;
+ void *freeIt;
+
+ (void)Var_Parse(cp, VAR_CMD, VARF_UNDEFERR|VARF_WANTRES,
+ &length, &freeIt);
+ free(freeIt);
+ cp += length-1;
+ }
+ }
+
+ /*
+ * If the word is followed by a left parenthesis, it's the
+ * name of an object file inside an archive (ar file).
+ */
+ if (!ParseIsEscaped(lstart, cp) && *cp == LPAREN) {
+ /*
+ * Archives must be handled specially to make sure the OP_ARCHV
+ * flag is set in their 'type' field, for one thing, and because
+ * things like "archive(file1.o file2.o file3.o)" are permissible.
+ * Arch_ParseArchive will set 'line' to be the first non-blank
+ * after the archive-spec. It creates/finds nodes for the members
+ * and places them on the given list, returning SUCCESS if all
+ * went well and FAILURE if there was an error in the
+ * specification. On error, line should remain untouched.
+ */
+ if (Arch_ParseArchive(&line, targets, VAR_CMD) != SUCCESS) {
+ Parse_Error(PARSE_FATAL,
+ "Error in archive specification: \"%s\"", line);
+ goto out;
+ } else {
+ /* Done with this word; on to the next. */
+ cp = line;
+ continue;
+ }
+ }
+
+ if (!*cp) {
+ /*
+ * We got to the end of the line while we were still
+ * looking at targets.
+ *
+ * Ending a dependency line without an operator is a Bozo
+ * no-no. As a heuristic, this is also often triggered by
+ * undetected conflicts from cvs/rcs merges.
+ */
+ if ((strncmp(line, "<<<<<<", 6) == 0) ||
+ (strncmp(line, "======", 6) == 0) ||
+ (strncmp(line, ">>>>>>", 6) == 0))
+ Parse_Error(PARSE_FATAL,
+ "Makefile appears to contain unresolved cvs/rcs/??? merge conflicts");
+ else
+ Parse_Error(PARSE_FATAL, lstart[0] == '.' ? "Unknown directive"
+ : "Need an operator");
+ goto out;
+ }
+
+ /* Insert a null terminator. */
+ savec = *cp;
+ *cp = '\0';
+
+ /*
+ * Got the word. See if it's a special target and if so set
+ * specType to match it.
+ */
+ if (*line == '.' && isupper ((unsigned char)line[1])) {
+ /*
+ * See if the target is a special target that must have it
+ * or its sources handled specially.
+ */
+ int keywd = ParseFindKeyword(line);
+ if (keywd != -1) {
+ if (specType == ExPath && parseKeywords[keywd].spec != ExPath) {
+ Parse_Error(PARSE_FATAL, "Mismatched special targets");
+ goto out;
+ }
+
+ specType = parseKeywords[keywd].spec;
+ tOp = parseKeywords[keywd].op;
+
+ /*
+ * Certain special targets have special semantics:
+ * .PATH Have to set the dirSearchPath
+ * variable too
+ * .MAIN Its sources are only used if
+ * nothing has been specified to
+ * create.
+ * .DEFAULT Need to create a node to hang
+ * commands on, but we don't want
+ * it in the graph, nor do we want
+ * it to be the Main Target, so we
+ * create it, set OP_NOTMAIN and
+ * add it to the list, setting
+ * DEFAULT to the new node for
+ * later use. We claim the node is
+ * A transformation rule to make
+ * life easier later, when we'll
+ * use Make_HandleUse to actually
+ * apply the .DEFAULT commands.
+ * .PHONY The list of targets
+ * .NOPATH Don't search for file in the path
+ * .STALE
+ * .BEGIN
+ * .END
+ * .ERROR
+ * .DELETE_ON_ERROR
+ * .INTERRUPT Are not to be considered the
+ * main target.
+ * .NOTPARALLEL Make only one target at a time.
+ * .SINGLESHELL Create a shell for each command.
+ * .ORDER Must set initial predecessor to NULL
+ */
+ switch (specType) {
+ case ExPath:
+ if (paths == NULL) {
+ paths = Lst_Init(FALSE);
+ }
+ (void)Lst_AtEnd(paths, dirSearchPath);
+ break;
+ case Main:
+ if (!Lst_IsEmpty(create)) {
+ specType = Not;
+ }
+ break;
+ case Begin:
+ case End:
+ case Stale:
+ case dotError:
+ case Interrupt:
+ gn = Targ_FindNode(line, TARG_CREATE);
+ if (doing_depend)
+ ParseMark(gn);
+ gn->type |= OP_NOTMAIN|OP_SPECIAL;
+ (void)Lst_AtEnd(targets, gn);
+ break;
+ case Default:
+ gn = Targ_NewGN(".DEFAULT");
+ gn->type |= (OP_NOTMAIN|OP_TRANSFORM);
+ (void)Lst_AtEnd(targets, gn);
+ DEFAULT = gn;
+ break;
+ case DeleteOnError:
+ deleteOnError = TRUE;
+ break;
+ case NotParallel:
+ maxJobs = 1;
+ break;
+ case SingleShell:
+ compatMake = TRUE;
+ break;
+ case Order:
+ predecessor = NULL;
+ break;
+ default:
+ break;
+ }
+ } else if (strncmp(line, ".PATH", 5) == 0) {
+ /*
+ * .PATH<suffix> has to be handled specially.
+ * Call on the suffix module to give us a path to
+ * modify.
+ */
+ Lst path;
+
+ specType = ExPath;
+ path = Suff_GetPath(&line[5]);
+ if (path == NULL) {
+ Parse_Error(PARSE_FATAL,
+ "Suffix '%s' not defined (yet)",
+ &line[5]);
+ goto out;
+ } else {
+ if (paths == NULL) {
+ paths = Lst_Init(FALSE);
+ }
+ (void)Lst_AtEnd(paths, path);
+ }
+ }
+ }
+
+ /*
+ * Have word in line. Get or create its node and stick it at
+ * the end of the targets list
+ */
+ if ((specType == Not) && (*line != '\0')) {
+ if (Dir_HasWildcards(line)) {
+ /*
+ * Targets are to be sought only in the current directory,
+ * so create an empty path for the thing. Note we need to
+ * use Dir_Destroy in the destruction of the path as the
+ * Dir module could have added a directory to the path...
+ */
+ Lst emptyPath = Lst_Init(FALSE);
+
+ Dir_Expand(line, emptyPath, curTargs);
+
+ Lst_Destroy(emptyPath, Dir_Destroy);
+ } else {
+ /*
+ * No wildcards, but we want to avoid code duplication,
+ * so create a list with the word on it.
+ */
+ (void)Lst_AtEnd(curTargs, line);
+ }
+
+ /* Apply the targets. */
+
+ while(!Lst_IsEmpty(curTargs)) {
+ char *targName = (char *)Lst_DeQueue(curTargs);
+
+ if (!Suff_IsTransform (targName)) {
+ gn = Targ_FindNode(targName, TARG_CREATE);
+ } else {
+ gn = Suff_AddTransform(targName);
+ }
+ if (doing_depend)
+ ParseMark(gn);
+
+ (void)Lst_AtEnd(targets, gn);
+ }
+ } else if (specType == ExPath && *line != '.' && *line != '\0') {
+ Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line);
+ }
+
+ /* Don't need the inserted null terminator any more. */
+ *cp = savec;
+
+ /*
+ * If it is a special type and not .PATH, it's the only target we
+ * allow on this line...
+ */
+ if (specType != Not && specType != ExPath) {
+ Boolean warning = FALSE;
+
+ while (*cp && (ParseIsEscaped(lstart, cp) ||
+ ((*cp != '!') && (*cp != ':')))) {
+ if (ParseIsEscaped(lstart, cp) ||
+ (*cp != ' ' && *cp != '\t')) {
+ warning = TRUE;
+ }
+ cp++;
+ }
+ if (warning) {
+ Parse_Error(PARSE_WARNING, "Extra target ignored");
+ }
+ } else {
+ while (*cp && isspace ((unsigned char)*cp)) {
+ cp++;
+ }
+ }
+ line = cp;
+ } while (*line && (ParseIsEscaped(lstart, line) ||
+ ((*line != '!') && (*line != ':'))));
+
+ /*
+ * Don't need the list of target names anymore...
+ */
+ Lst_Destroy(curTargs, NULL);
+ curTargs = NULL;
+
+ if (!Lst_IsEmpty(targets)) {
+ switch(specType) {
+ default:
+ Parse_Error(PARSE_WARNING, "Special and mundane targets don't mix. Mundane ones ignored");
+ break;
+ case Default:
+ case Stale:
+ case Begin:
+ case End:
+ case dotError:
+ case Interrupt:
+ /*
+ * These four create nodes on which to hang commands, so
+ * targets shouldn't be empty...
+ */
+ case Not:
+ /*
+ * Nothing special here -- targets can be empty if it wants.
+ */
+ break;
+ }
+ }
+
+ /*
+ * Have now parsed all the target names. Must parse the operator next. The
+ * result is left in op .
+ */
+ if (*cp == '!') {
+ op = OP_FORCE;
+ } else if (*cp == ':') {
+ if (cp[1] == ':') {
+ op = OP_DOUBLEDEP;
+ cp++;
+ } else {
+ op = OP_DEPENDS;
+ }
+ } else {
+ Parse_Error(PARSE_FATAL, lstart[0] == '.' ? "Unknown directive"
+ : "Missing dependency operator");
+ goto out;
+ }
+
+ /* Advance beyond the operator */
+ cp++;
+
+ /*
+ * Apply the operator to the target. This is how we remember which
+ * operator a target was defined with. It fails if the operator
+ * used isn't consistent across all references.
+ */
+ Lst_ForEach(targets, ParseDoOp, &op);
+
+ /*
+ * Onward to the sources.
+ *
+ * LINE will now point to the first source word, if any, or the
+ * end of the string if not.
+ */
+ while (*cp && isspace ((unsigned char)*cp)) {
+ cp++;
+ }
+ line = cp;
+
+ /*
+ * Several special targets take different actions if present with no
+ * sources:
+ * a .SUFFIXES line with no sources clears out all old suffixes
+ * a .PRECIOUS line makes all targets precious
+ * a .IGNORE line ignores errors for all targets
+ * a .SILENT line creates silence when making all targets
+ * a .PATH removes all directories from the search path(s).
+ */
+ if (!*line) {
+ switch (specType) {
+ case Suffixes:
+ Suff_ClearSuffixes();
+ break;
+ case Precious:
+ allPrecious = TRUE;
+ break;
+ case Ignore:
+ ignoreErrors = TRUE;
+ break;
+ case Silent:
+ beSilent = TRUE;
+ break;
+ case ExPath:
+ Lst_ForEach(paths, ParseClearPath, NULL);
+ Dir_SetPATH();
+ break;
+#ifdef POSIX
+ case Posix:
+ Var_Set("%POSIX", "1003.2", VAR_GLOBAL, 0);
+ break;
+#endif
+ default:
+ break;
+ }
+ } else if (specType == MFlags) {
+ /*
+ * Call on functions in main.c to deal with these arguments and
+ * set the initial character to a null-character so the loop to
+ * get sources won't get anything
+ */
+ Main_ParseArgLine(line);
+ *line = '\0';
+ } else if (specType == ExShell) {
+ if (Job_ParseShell(line) != SUCCESS) {
+ Parse_Error(PARSE_FATAL, "improper shell specification");
+ goto out;
+ }
+ *line = '\0';
+ } else if ((specType == NotParallel) || (specType == SingleShell) ||
+ (specType == DeleteOnError)) {
+ *line = '\0';
+ }
+
+ /*
+ * NOW GO FOR THE SOURCES
+ */
+ if ((specType == Suffixes) || (specType == ExPath) ||
+ (specType == Includes) || (specType == Libs) ||
+ (specType == Null) || (specType == ExObjdir))
+ {
+ while (*line) {
+ /*
+ * If the target was one that doesn't take files as its sources
+ * but takes something like suffixes, we take each
+ * space-separated word on the line as a something and deal
+ * with it accordingly.
+ *
+ * If the target was .SUFFIXES, we take each source as a
+ * suffix and add it to the list of suffixes maintained by the
+ * Suff module.
+ *
+ * If the target was a .PATH, we add the source as a directory
+ * to search on the search path.
+ *
+ * If it was .INCLUDES, the source is taken to be the suffix of
+ * files which will be #included and whose search path should
+ * be present in the .INCLUDES variable.
+ *
+ * If it was .LIBS, the source is taken to be the suffix of
+ * files which are considered libraries and whose search path
+ * should be present in the .LIBS variable.
+ *
+ * If it was .NULL, the source is the suffix to use when a file
+ * has no valid suffix.
+ *
+ * If it was .OBJDIR, the source is a new definition for .OBJDIR,
+ * and will cause make to do a new chdir to that path.
+ */
+ while (*cp && !isspace ((unsigned char)*cp)) {
+ cp++;
+ }
+ savec = *cp;
+ *cp = '\0';
+ switch (specType) {
+ case Suffixes:
+ Suff_AddSuffix(line, &mainNode);
+ break;
+ case ExPath:
+ Lst_ForEach(paths, ParseAddDir, line);
+ break;
+ case Includes:
+ Suff_AddInclude(line);
+ break;
+ case Libs:
+ Suff_AddLib(line);
+ break;
+ case Null:
+ Suff_SetNull(line);
+ break;
+ case ExObjdir:
+ Main_SetObjdir("%s", line);
+ break;
+ default:
+ break;
+ }
+ *cp = savec;
+ if (savec != '\0') {
+ cp++;
+ }
+ while (*cp && isspace ((unsigned char)*cp)) {
+ cp++;
+ }
+ line = cp;
+ }
+ if (paths) {
+ Lst_Destroy(paths, NULL);
+ paths = NULL;
+ }
+ if (specType == ExPath)
+ Dir_SetPATH();
+ } else {
+ assert(paths == NULL);
+ while (*line) {
+ /*
+ * The targets take real sources, so we must beware of archive
+ * specifications (i.e. things with left parentheses in them)
+ * and handle them accordingly.
+ */
+ for (; *cp && !isspace ((unsigned char)*cp); cp++) {
+ if ((*cp == LPAREN) && (cp > line) && (cp[-1] != '$')) {
+ /*
+ * Only stop for a left parenthesis if it isn't at the
+ * start of a word (that'll be for variable changes
+ * later) and isn't preceded by a dollar sign (a dynamic
+ * source).
+ */
+ break;
+ }
+ }
+
+ if (*cp == LPAREN) {
+ sources = Lst_Init(FALSE);
+ if (Arch_ParseArchive(&line, sources, VAR_CMD) != SUCCESS) {
+ Parse_Error(PARSE_FATAL,
+ "Error in source archive spec \"%s\"", line);
+ goto out;
+ }
+
+ while (!Lst_IsEmpty (sources)) {
+ gn = (GNode *)Lst_DeQueue(sources);
+ ParseDoSrc(tOp, gn->name);
+ }
+ Lst_Destroy(sources, NULL);
+ cp = line;
+ } else {
+ if (*cp) {
+ *cp = '\0';
+ cp += 1;
+ }
+
+ ParseDoSrc(tOp, line);
+ }
+ while (*cp && isspace ((unsigned char)*cp)) {
+ cp++;
+ }
+ line = cp;
+ }
+ }
+
+ if (mainNode == NULL) {
+ /*
+ * If we have yet to decide on a main target to make, in the
+ * absence of any user input, we want the first target on
+ * the first dependency line that is actually a real target
+ * (i.e. isn't a .USE or .EXEC rule) to be made.
+ */
+ Lst_ForEach(targets, ParseFindMain, NULL);
+ }
+
+out:
+ assert(paths == NULL);
+ if (curTargs)
+ Lst_Destroy(curTargs, NULL);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_IsVar --
+ * Return TRUE if the passed line is a variable assignment. A variable
+ * assignment consists of a single word followed by optional whitespace
+ * followed by either a += or an = operator.
+ * This function is used both by the Parse_File function and main when
+ * parsing the command-line arguments.
+ *
+ * Input:
+ * line the line to check
+ *
+ * Results:
+ * TRUE if it is. FALSE if it ain't
+ *
+ * Side Effects:
+ * none
+ *---------------------------------------------------------------------
+ */
+Boolean
+Parse_IsVar(char *line)
+{
+ Boolean wasSpace = FALSE; /* set TRUE if found a space */
+ char ch;
+ int level = 0;
+#define ISEQOPERATOR(c) \
+ (((c) == '+') || ((c) == ':') || ((c) == '?') || ((c) == '!'))
+
+ /*
+ * Skip to variable name
+ */
+ for (;(*line == ' ') || (*line == '\t'); line++)
+ continue;
+
+ /* Scan for one of the assignment operators outside a variable expansion */
+ while ((ch = *line++) != 0) {
+ if (ch == '(' || ch == '{') {
+ level++;
+ continue;
+ }
+ if (ch == ')' || ch == '}') {
+ level--;
+ continue;
+ }
+ if (level != 0)
+ continue;
+ while (ch == ' ' || ch == '\t') {
+ ch = *line++;
+ wasSpace = TRUE;
+ }
+#ifdef SUNSHCMD
+ if (ch == ':' && strncmp(line, "sh", 2) == 0) {
+ line += 2;
+ continue;
+ }
+#endif
+ if (ch == '=')
+ return TRUE;
+ if (*line == '=' && ISEQOPERATOR(ch))
+ return TRUE;
+ if (wasSpace)
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_DoVar --
+ * Take the variable assignment in the passed line and do it in the
+ * global context.
+ *
+ * Note: There is a lexical ambiguity with assignment modifier characters
+ * in variable names. This routine interprets the character before the =
+ * as a modifier. Therefore, an assignment like
+ * C++=/usr/bin/CC
+ * is interpreted as "C+ +=" instead of "C++ =".
+ *
+ * Input:
+ * line a line guaranteed to be a variable assignment.
+ * This reduces error checks
+ * ctxt Context in which to do the assignment
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * the variable structure of the given variable name is altered in the
+ * global context.
+ *---------------------------------------------------------------------
+ */
+void
+Parse_DoVar(char *line, GNode *ctxt)
+{
+ char *cp; /* pointer into line */
+ enum {
+ VAR_SUBST, VAR_APPEND, VAR_SHELL, VAR_NORMAL
+ } type; /* Type of assignment */
+ char *opc; /* ptr to operator character to
+ * null-terminate the variable name */
+ Boolean freeCp = FALSE; /* TRUE if cp needs to be freed,
+ * i.e. if any variable expansion was
+ * performed */
+ int depth;
+
+ /*
+ * Skip to variable name
+ */
+ while ((*line == ' ') || (*line == '\t')) {
+ line++;
+ }
+
+ /*
+ * Skip to operator character, nulling out whitespace as we go
+ * XXX Rather than counting () and {} we should look for $ and
+ * then expand the variable.
+ */
+ for (depth = 0, cp = line + 1; depth > 0 || *cp != '='; cp++) {
+ if (*cp == '(' || *cp == '{') {
+ depth++;
+ continue;
+ }
+ if (*cp == ')' || *cp == '}') {
+ depth--;
+ continue;
+ }
+ if (depth == 0 && isspace ((unsigned char)*cp)) {
+ *cp = '\0';
+ }
+ }
+ opc = cp-1; /* operator is the previous character */
+ *cp++ = '\0'; /* nuke the = */
+
+ /*
+ * Check operator type
+ */
+ switch (*opc) {
+ case '+':
+ type = VAR_APPEND;
+ *opc = '\0';
+ break;
+
+ case '?':
+ /*
+ * If the variable already has a value, we don't do anything.
+ */
+ *opc = '\0';
+ if (Var_Exists(line, ctxt)) {
+ return;
+ } else {
+ type = VAR_NORMAL;
+ }
+ break;
+
+ case ':':
+ type = VAR_SUBST;
+ *opc = '\0';
+ break;
+
+ case '!':
+ type = VAR_SHELL;
+ *opc = '\0';
+ break;
+
+ default:
+#ifdef SUNSHCMD
+ while (opc > line && *opc != ':')
+ opc--;
+
+ if (strncmp(opc, ":sh", 3) == 0) {
+ type = VAR_SHELL;
+ *opc = '\0';
+ break;
+ }
+#endif
+ type = VAR_NORMAL;
+ break;
+ }
+
+ while (isspace ((unsigned char)*cp)) {
+ cp++;
+ }
+
+ if (type == VAR_APPEND) {
+ Var_Append(line, cp, ctxt);
+ } else if (type == VAR_SUBST) {
+ /*
+ * Allow variables in the old value to be undefined, but leave their
+ * invocation alone -- this is done by forcing oldVars to be false.
+ * XXX: This can cause recursive variables, but that's not hard to do,
+ * and this allows someone to do something like
+ *
+ * CFLAGS = $(.INCLUDES)
+ * CFLAGS := -I.. $(CFLAGS)
+ *
+ * And not get an error.
+ */
+ Boolean oldOldVars = oldVars;
+
+ oldVars = FALSE;
+
+ /*
+ * make sure that we set the variable the first time to nothing
+ * so that it gets substituted!
+ */
+ if (!Var_Exists(line, ctxt))
+ Var_Set(line, "", ctxt, 0);
+
+ cp = Var_Subst(NULL, cp, ctxt, VARF_WANTRES|VARF_ASSIGN);
+ oldVars = oldOldVars;
+ freeCp = TRUE;
+
+ Var_Set(line, cp, ctxt, 0);
+ } else if (type == VAR_SHELL) {
+ char *res;
+ const char *error;
+
+ if (strchr(cp, '$') != NULL) {
+ /*
+ * There's a dollar sign in the command, so perform variable
+ * expansion on the whole thing. The resulting string will need
+ * freeing when we're done, so set freeCmd to TRUE.
+ */
+ cp = Var_Subst(NULL, cp, VAR_CMD, VARF_UNDEFERR|VARF_WANTRES);
+ freeCp = TRUE;
+ }
+
+ res = Cmd_Exec(cp, &error);
+ Var_Set(line, res, ctxt, 0);
+ free(res);
+
+ if (error)
+ Parse_Error(PARSE_WARNING, error, cp);
+ } else {
+ /*
+ * Normal assignment -- just do it.
+ */
+ Var_Set(line, cp, ctxt, 0);
+ }
+ if (strcmp(line, MAKEOVERRIDES) == 0)
+ Main_ExportMAKEFLAGS(FALSE); /* re-export MAKEFLAGS */
+ else if (strcmp(line, ".CURDIR") == 0) {
+ /*
+ * Somone is being (too?) clever...
+ * Let's pretend they know what they are doing and
+ * re-initialize the 'cur' Path.
+ */
+ Dir_InitCur(cp);
+ Dir_SetPATH();
+ } else if (strcmp(line, MAKE_JOB_PREFIX) == 0) {
+ Job_SetPrefix();
+ } else if (strcmp(line, MAKE_EXPORTED) == 0) {
+ Var_Export(cp, 0);
+ }
+ if (freeCp)
+ free(cp);
+}
+
+
+/*
+ * ParseMaybeSubMake --
+ * Scan the command string to see if it a possible submake node
+ * Input:
+ * cmd the command to scan
+ * Results:
+ * TRUE if the command is possibly a submake, FALSE if not.
+ */
+static Boolean
+ParseMaybeSubMake(const char *cmd)
+{
+ size_t i;
+ static struct {
+ const char *name;
+ size_t len;
+ } vals[] = {
+#define MKV(A) { A, sizeof(A) - 1 }
+ MKV("${MAKE}"),
+ MKV("${.MAKE}"),
+ MKV("$(MAKE)"),
+ MKV("$(.MAKE)"),
+ MKV("make"),
+ };
+ for (i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) {
+ char *ptr;
+ if ((ptr = strstr(cmd, vals[i].name)) == NULL)
+ continue;
+ if ((ptr == cmd || !isalnum((unsigned char)ptr[-1]))
+ && !isalnum((unsigned char)ptr[vals[i].len]))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/*-
+ * ParseAddCmd --
+ * Lst_ForEach function to add a command line to all targets
+ *
+ * Input:
+ * gnp the node to which the command is to be added
+ * cmd the command to add
+ *
+ * Results:
+ * Always 0
+ *
+ * Side Effects:
+ * A new element is added to the commands list of the node,
+ * and the node can be marked as a submake node if the command is
+ * determined to be that.
+ */
+static int
+ParseAddCmd(void *gnp, void *cmd)
+{
+ GNode *gn = (GNode *)gnp;
+
+ /* Add to last (ie current) cohort for :: targets */
+ if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts))
+ gn = (GNode *)Lst_Datum(Lst_Last(gn->cohorts));
+
+ /* if target already supplied, ignore commands */
+ if (!(gn->type & OP_HAS_COMMANDS)) {
+ (void)Lst_AtEnd(gn->commands, cmd);
+ if (ParseMaybeSubMake(cmd))
+ gn->type |= OP_SUBMAKE;
+ ParseMark(gn);
+ } else {
+#ifdef notyet
+ /* XXX: We cannot do this until we fix the tree */
+ (void)Lst_AtEnd(gn->commands, cmd);
+ Parse_Error(PARSE_WARNING,
+ "overriding commands for target \"%s\"; "
+ "previous commands defined at %s: %d ignored",
+ gn->name, gn->fname, gn->lineno);
+#else
+ Parse_Error(PARSE_WARNING,
+ "duplicate script for target \"%s\" ignored",
+ gn->name);
+ ParseErrorInternal(gn->fname, gn->lineno, PARSE_WARNING,
+ "using previous script for \"%s\" defined here",
+ gn->name);
+#endif
+ }
+ return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseHasCommands --
+ * Callback procedure for Parse_File when destroying the list of
+ * targets on the last dependency line. Marks a target as already
+ * having commands if it does, to keep from having shell commands
+ * on multiple dependency lines.
+ *
+ * Input:
+ * gnp Node to examine
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * OP_HAS_COMMANDS may be set for the target.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+ParseHasCommands(void *gnp)
+{
+ GNode *gn = (GNode *)gnp;
+ if (!Lst_IsEmpty(gn->commands)) {
+ gn->type |= OP_HAS_COMMANDS;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Parse_AddIncludeDir --
+ * Add a directory to the path searched for included makefiles
+ * bracketed by double-quotes. Used by functions in main.c
+ *
+ * Input:
+ * dir The name of the directory to add
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The directory is appended to the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Parse_AddIncludeDir(char *dir)
+{
+ (void)Dir_AddDir(parseIncPath, dir);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseDoInclude --
+ * Push to another file.
+ *
+ * The input is the line minus the `.'. A file spec is a string
+ * enclosed in <> or "". The former is looked for only in sysIncPath.
+ * The latter in . and the directories specified by -I command line
+ * options
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * A structure is added to the includes Lst and readProc, lineno,
+ * fname and curFILE are altered for the new file
+ *---------------------------------------------------------------------
+ */
+
+static void
+Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent)
+{
+ struct loadedfile *lf;
+ char *fullname; /* full pathname of file */
+ char *newName;
+ char *prefEnd, *incdir;
+ int fd;
+ int i;
+
+ /*
+ * Now we know the file's name and its search path, we attempt to
+ * find the durn thing. A return of NULL indicates the file don't
+ * exist.
+ */
+ fullname = file[0] == '/' ? bmake_strdup(file) : NULL;
+
+ if (fullname == NULL && !isSystem) {
+ /*
+ * Include files contained in double-quotes are first searched for
+ * relative to the including file's location. We don't want to
+ * cd there, of course, so we just tack on the old file's
+ * leading path components and call Dir_FindFile to see if
+ * we can locate the beast.
+ */
+
+ incdir = bmake_strdup(curFile->fname);
+ prefEnd = strrchr(incdir, '/');
+ if (prefEnd != NULL) {
+ *prefEnd = '\0';
+ /* Now do lexical processing of leading "../" on the filename */
+ for (i = 0; strncmp(file + i, "../", 3) == 0; i += 3) {
+ prefEnd = strrchr(incdir + 1, '/');
+ if (prefEnd == NULL || strcmp(prefEnd, "/..") == 0)
+ break;
+ *prefEnd = '\0';
+ }
+ newName = str_concat(incdir, file + i, STR_ADDSLASH);
+ fullname = Dir_FindFile(newName, parseIncPath);
+ if (fullname == NULL)
+ fullname = Dir_FindFile(newName, dirSearchPath);
+ free(newName);
+ }
+ free(incdir);
+
+ if (fullname == NULL) {
+ /*
+ * Makefile wasn't found in same directory as included makefile.
+ * Search for it first on the -I search path,
+ * then on the .PATH search path, if not found in a -I directory.
+ * If we have a suffix specific path we should use that.
+ */
+ char *suff;
+ Lst suffPath = NULL;
+
+ if ((suff = strrchr(file, '.'))) {
+ suffPath = Suff_GetPath(suff);
+ if (suffPath != NULL) {
+ fullname = Dir_FindFile(file, suffPath);
+ }
+ }
+ if (fullname == NULL) {
+ fullname = Dir_FindFile(file, parseIncPath);
+ if (fullname == NULL) {
+ fullname = Dir_FindFile(file, dirSearchPath);
+ }
+ }
+ }
+ }
+
+ /* Looking for a system file or file still not found */
+ if (fullname == NULL) {
+ /*
+ * Look for it on the system path
+ */
+ fullname = Dir_FindFile(file,
+ Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath);
+ }
+
+ if (fullname == NULL) {
+ if (!silent)
+ Parse_Error(PARSE_FATAL, "Could not find %s", file);
+ return;
+ }
+
+ /* Actually open the file... */
+ fd = open(fullname, O_RDONLY);
+ if (fd == -1) {
+ if (!silent)
+ Parse_Error(PARSE_FATAL, "Cannot open %s", fullname);
+ free(fullname);
+ return;
+ }
+
+ /* load it */
+ lf = loadfile(fullname, fd);
+
+ ParseSetIncludedFile();
+ /* Start reading from this file next */
+ Parse_SetInput(fullname, 0, -1, loadedfile_nextbuf, lf);
+ curFile->lf = lf;
+ if (depinc)
+ doing_depend = depinc; /* only turn it on */
+}
+
+static void
+ParseDoInclude(char *line)
+{
+ char endc; /* the character which ends the file spec */
+ char *cp; /* current position in file spec */
+ int silent = (*line != 'i') ? 1 : 0;
+ char *file = &line[7 + silent];
+
+ /* Skip to delimiter character so we know where to look */
+ while (*file == ' ' || *file == '\t')
+ file++;
+
+ if (*file != '"' && *file != '<') {
+ Parse_Error(PARSE_FATAL,
+ ".include filename must be delimited by '\"' or '<'");
+ return;
+ }
+
+ /*
+ * Set the search path on which to find the include file based on the
+ * characters which bracket its name. Angle-brackets imply it's
+ * a system Makefile while double-quotes imply it's a user makefile
+ */
+ if (*file == '<') {
+ endc = '>';
+ } else {
+ endc = '"';
+ }
+
+ /* Skip to matching delimiter */
+ for (cp = ++file; *cp && *cp != endc; cp++)
+ continue;
+
+ if (*cp != endc) {
+ Parse_Error(PARSE_FATAL,
+ "Unclosed %cinclude filename. '%c' expected",
+ '.', endc);
+ return;
+ }
+ *cp = '\0';
+
+ /*
+ * Substitute for any variables in the file name before trying to
+ * find the thing.
+ */
+ file = Var_Subst(NULL, file, VAR_CMD, VARF_WANTRES);
+
+ Parse_include_file(file, endc == '>', (*line == 'd'), silent);
+ free(file);
+}
+
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseSetIncludedFile --
+ * Set the .INCLUDEDFROMFILE variable to the contents of .PARSEFILE
+ * and the .INCLUDEDFROMDIR variable to the contents of .PARSEDIR
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The .INCLUDEDFROMFILE variable is overwritten by the contents
+ * of .PARSEFILE and the .INCLUDEDFROMDIR variable is overwriten
+ * by the contents of .PARSEDIR
+ *---------------------------------------------------------------------
+ */
+static void
+ParseSetIncludedFile(void)
+{
+ char *pf, *fp = NULL;
+ char *pd, *dp = NULL;
+
+ pf = Var_Value(".PARSEFILE", VAR_GLOBAL, &fp);
+ Var_Set(".INCLUDEDFROMFILE", pf, VAR_GLOBAL, 0);
+ pd = Var_Value(".PARSEDIR", VAR_GLOBAL, &dp);
+ Var_Set(".INCLUDEDFROMDIR", pd, VAR_GLOBAL, 0);
+
+ if (DEBUG(PARSE))
+ fprintf(debug_file, "%s: ${.INCLUDEDFROMDIR} = `%s' "
+ "${.INCLUDEDFROMFILE} = `%s'\n", __func__, pd, pf);
+
+ free(fp);
+ free(dp);
+}
+/*-
+ *---------------------------------------------------------------------
+ * ParseSetParseFile --
+ * Set the .PARSEDIR and .PARSEFILE variables to the dirname and
+ * basename of the given filename
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The .PARSEDIR and .PARSEFILE variables are overwritten by the
+ * dirname and basename of the given filename.
+ *---------------------------------------------------------------------
+ */
+static void
+ParseSetParseFile(const char *filename)
+{
+ char *slash, *dirname;
+ const char *pd, *pf;
+ int len;
+
+ slash = strrchr(filename, '/');
+ if (slash == NULL) {
+ Var_Set(".PARSEDIR", pd = curdir, VAR_GLOBAL, 0);
+ Var_Set(".PARSEFILE", pf = filename, VAR_GLOBAL, 0);
+ dirname= NULL;
+ } else {
+ len = slash - filename;
+ dirname = bmake_malloc(len + 1);
+ memcpy(dirname, filename, len);
+ dirname[len] = '\0';
+ Var_Set(".PARSEDIR", pd = dirname, VAR_GLOBAL, 0);
+ Var_Set(".PARSEFILE", pf = slash + 1, VAR_GLOBAL, 0);
+ }
+ if (DEBUG(PARSE))
+ fprintf(debug_file, "%s: ${.PARSEDIR} = `%s' ${.PARSEFILE} = `%s'\n",
+ __func__, pd, pf);
+ free(dirname);
+}
+
+/*
+ * Track the makefiles we read - so makefiles can
+ * set dependencies on them.
+ * Avoid adding anything more than once.
+ */
+
+static void
+ParseTrackInput(const char *name)
+{
+ char *old;
+ char *ep;
+ char *fp = NULL;
+ size_t name_len = strlen(name);
+
+ old = Var_Value(MAKE_MAKEFILES, VAR_GLOBAL, &fp);
+ if (old) {
+ ep = old + strlen(old) - name_len;
+ /* does it contain name? */
+ for (; old != NULL; old = strchr(old, ' ')) {
+ if (*old == ' ')
+ old++;
+ if (old >= ep)
+ break; /* cannot contain name */
+ if (memcmp(old, name, name_len) == 0
+ && (old[name_len] == 0 || old[name_len] == ' '))
+ goto cleanup;
+ }
+ }
+ Var_Append (MAKE_MAKEFILES, name, VAR_GLOBAL);
+ cleanup:
+ if (fp) {
+ free(fp);
+ }
+}
+
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_setInput --
+ * Start Parsing from the given source
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * A structure is added to the includes Lst and readProc, lineno,
+ * fname and curFile are altered for the new file
+ *---------------------------------------------------------------------
+ */
+void
+Parse_SetInput(const char *name, int line, int fd,
+ char *(*nextbuf)(void *, size_t *), void *arg)
+{
+ char *buf;
+ size_t len;
+
+ if (name == NULL)
+ name = curFile->fname;
+ else
+ ParseTrackInput(name);
+
+ if (DEBUG(PARSE))
+ fprintf(debug_file, "%s: file %s, line %d, fd %d, nextbuf %p, arg %p\n",
+ __func__, name, line, fd, nextbuf, arg);
+
+ if (fd == -1 && nextbuf == NULL)
+ /* sanity */
+ return;
+
+ if (curFile != NULL)
+ /* Save exiting file info */
+ Lst_AtFront(includes, curFile);
+
+ /* Allocate and fill in new structure */
+ curFile = bmake_malloc(sizeof *curFile);
+
+ /*
+ * Once the previous state has been saved, we can get down to reading
+ * the new file. We set up the name of the file to be the absolute
+ * name of the include file so error messages refer to the right
+ * place.
+ */
+ curFile->fname = bmake_strdup(name);
+ curFile->lineno = line;
+ curFile->first_lineno = line;
+ curFile->nextbuf = nextbuf;
+ curFile->nextbuf_arg = arg;
+ curFile->lf = NULL;
+ curFile->depending = doing_depend; /* restore this on EOF */
+
+ assert(nextbuf != NULL);
+
+ /* Get first block of input data */
+ buf = curFile->nextbuf(curFile->nextbuf_arg, &len);
+ if (buf == NULL) {
+ /* Was all a waste of time ... */
+ if (curFile->fname)
+ free(curFile->fname);
+ free(curFile);
+ return;
+ }
+ curFile->P_str = buf;
+ curFile->P_ptr = buf;
+ curFile->P_end = buf+len;
+
+ curFile->cond_depth = Cond_save_depth();
+ ParseSetParseFile(name);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * IsInclude --
+ * Check if the line is an include directive
+ *
+ * Results:
+ * TRUE if it is.
+ *
+ * Side Effects:
+ * None
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+IsInclude(const char *line, Boolean sysv)
+{
+ static const char inc[] = "include";
+ static const size_t inclen = sizeof(inc) - 1;
+
+ // 'd' is not valid for sysv
+ int o = strchr(&("ds-"[sysv]), *line) != NULL;
+
+ if (strncmp(line + o, inc, inclen) != 0)
+ return FALSE;
+
+ // Space is not mandatory for BSD .include
+ return !sysv || isspace((unsigned char)line[inclen + o]);
+}
+
+
+#ifdef SYSVINCLUDE
+/*-
+ *-----------------------------------------------------------------------
+ * IsSysVInclude --
+ * Check if the line is a SYSV include directive
+ *
+ * Results:
+ * TRUE if it is.
+ *
+ * Side Effects:
+ * None
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+IsSysVInclude(const char *line)
+{
+ const char *p;
+
+ if (!IsInclude(line, TRUE))
+ return FALSE;
+
+ /* Avoid interpeting a dependency line as an include */
+ for (p = line; (p = strchr(p, ':')) != NULL;) {
+ if (*++p == '\0') {
+ /* end of line -> dependency */
+ return FALSE;
+ }
+ if (*p == ':' || isspace((unsigned char)*p)) {
+ /* :: operator or ': ' -> dependency */
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseTraditionalInclude --
+ * Push to another file.
+ *
+ * The input is the current line. The file name(s) are
+ * following the "include".
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * A structure is added to the includes Lst and readProc, lineno,
+ * fname and curFILE are altered for the new file
+ *---------------------------------------------------------------------
+ */
+static void
+ParseTraditionalInclude(char *line)
+{
+ char *cp; /* current position in file spec */
+ int done = 0;
+ int silent = (line[0] != 'i') ? 1 : 0;
+ char *file = &line[silent + 7];
+ char *all_files;
+
+ if (DEBUG(PARSE)) {
+ fprintf(debug_file, "%s: %s\n", __func__, file);
+ }
+
+ /*
+ * Skip over whitespace
+ */
+ while (isspace((unsigned char)*file))
+ file++;
+
+ /*
+ * Substitute for any variables in the file name before trying to
+ * find the thing.
+ */
+ all_files = Var_Subst(NULL, file, VAR_CMD, VARF_WANTRES);
+
+ if (*file == '\0') {
+ Parse_Error(PARSE_FATAL,
+ "Filename missing from \"include\"");
+ goto out;
+ }
+
+ for (file = all_files; !done; file = cp + 1) {
+ /* Skip to end of line or next whitespace */
+ for (cp = file; *cp && !isspace((unsigned char) *cp); cp++)
+ continue;
+
+ if (*cp)
+ *cp = '\0';
+ else
+ done = 1;
+
+ Parse_include_file(file, FALSE, FALSE, silent);
+ }
+out:
+ free(all_files);
+}
+#endif
+
+#ifdef GMAKEEXPORT
+/*-
+ *---------------------------------------------------------------------
+ * ParseGmakeExport --
+ * Parse export <variable>=<value>
+ *
+ * And set the environment with it.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * None
+ *---------------------------------------------------------------------
+ */
+static void
+ParseGmakeExport(char *line)
+{
+ char *variable = &line[6];
+ char *value;
+
+ if (DEBUG(PARSE)) {
+ fprintf(debug_file, "%s: %s\n", __func__, variable);
+ }
+
+ /*
+ * Skip over whitespace
+ */
+ while (isspace((unsigned char)*variable))
+ variable++;
+
+ for (value = variable; *value && *value != '='; value++)
+ continue;
+
+ if (*value != '=') {
+ Parse_Error(PARSE_FATAL,
+ "Variable/Value missing from \"export\"");
+ return;
+ }
+ *value++ = '\0'; /* terminate variable */
+
+ /*
+ * Expand the value before putting it in the environment.
+ */
+ value = Var_Subst(NULL, value, VAR_CMD, VARF_WANTRES);
+ setenv(variable, value, 1);
+ free(value);
+}
+#endif
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseEOF --
+ * Called when EOF is reached in the current file. If we were reading
+ * an include file, the includes stack is popped and things set up
+ * to go back to reading the previous file at the previous location.
+ *
+ * Results:
+ * CONTINUE if there's more to do. DONE if not.
+ *
+ * Side Effects:
+ * The old curFILE, is closed. The includes list is shortened.
+ * lineno, curFILE, and fname are changed if CONTINUE is returned.
+ *---------------------------------------------------------------------
+ */
+static int
+ParseEOF(void)
+{
+ char *ptr;
+ size_t len;
+
+ assert(curFile->nextbuf != NULL);
+
+ doing_depend = curFile->depending; /* restore this */
+ /* get next input buffer, if any */
+ ptr = curFile->nextbuf(curFile->nextbuf_arg, &len);
+ curFile->P_ptr = ptr;
+ curFile->P_str = ptr;
+ curFile->P_end = ptr + len;
+ curFile->lineno = curFile->first_lineno;
+ if (ptr != NULL) {
+ /* Iterate again */
+ return CONTINUE;
+ }
+
+ /* Ensure the makefile (or loop) didn't have mismatched conditionals */
+ Cond_restore_depth(curFile->cond_depth);
+
+ if (curFile->lf != NULL) {
+ loadedfile_destroy(curFile->lf);
+ curFile->lf = NULL;
+ }
+
+ /* Dispose of curFile info */
+ /* Leak curFile->fname because all the gnodes have pointers to it */
+ free(curFile->P_str);
+ free(curFile);
+
+ curFile = Lst_DeQueue(includes);
+
+ if (curFile == NULL) {
+ /* We've run out of input */
+ Var_Delete(".PARSEDIR", VAR_GLOBAL);
+ Var_Delete(".PARSEFILE", VAR_GLOBAL);
+ Var_Delete(".INCLUDEDFROMDIR", VAR_GLOBAL);
+ Var_Delete(".INCLUDEDFROMFILE", VAR_GLOBAL);
+ return DONE;
+ }
+
+ if (DEBUG(PARSE))
+ fprintf(debug_file, "ParseEOF: returning to file %s, line %d\n",
+ curFile->fname, curFile->lineno);
+
+ /* Restore the PARSEDIR/PARSEFILE variables */
+ ParseSetParseFile(curFile->fname);
+ return (CONTINUE);
+}
+
+#define PARSE_RAW 1
+#define PARSE_SKIP 2
+
+static char *
+ParseGetLine(int flags, int *length)
+{
+ IFile *cf = curFile;
+ char *ptr;
+ char ch;
+ char *line;
+ char *line_end;
+ char *escaped;
+ char *comment;
+ char *tp;
+
+ /* Loop through blank lines and comment lines */
+ for (;;) {
+ cf->lineno++;
+ line = cf->P_ptr;
+ ptr = line;
+ line_end = line;
+ escaped = NULL;
+ comment = NULL;
+ for (;;) {
+ if (cf->P_end != NULL && ptr == cf->P_end) {
+ /* end of buffer */
+ ch = 0;
+ break;
+ }
+ ch = *ptr;
+ if (ch == 0 || (ch == '\\' && ptr[1] == 0)) {
+ if (cf->P_end == NULL)
+ /* End of string (aka for loop) data */
+ break;
+ /* see if there is more we can parse */
+ while (ptr++ < cf->P_end) {
+ if ((ch = *ptr) == '\n') {
+ if (ptr > line && ptr[-1] == '\\')
+ continue;
+ Parse_Error(PARSE_WARNING,
+ "Zero byte read from file, skipping rest of line.");
+ break;
+ }
+ }
+ if (cf->nextbuf != NULL) {
+ /*
+ * End of this buffer; return EOF and outer logic
+ * will get the next one. (eww)
+ */
+ break;
+ }
+ Parse_Error(PARSE_FATAL, "Zero byte read from file");
+ return NULL;
+ }
+
+ if (ch == '\\') {
+ /* Don't treat next character as special, remember first one */
+ if (escaped == NULL)
+ escaped = ptr;
+ if (ptr[1] == '\n')
+ cf->lineno++;
+ ptr += 2;
+ line_end = ptr;
+ continue;
+ }
+ if (ch == '#' && comment == NULL) {
+ /* Remember first '#' for comment stripping */
+ /* Unless previous char was '[', as in modifier :[#] */
+ if (!(ptr > line && ptr[-1] == '['))
+ comment = line_end;
+ }
+ ptr++;
+ if (ch == '\n')
+ break;
+ if (!isspace((unsigned char)ch))
+ /* We are not interested in trailing whitespace */
+ line_end = ptr;
+ }
+
+ /* Save next 'to be processed' location */
+ cf->P_ptr = ptr;
+
+ /* Check we have a non-comment, non-blank line */
+ if (line_end == line || comment == line) {
+ if (ch == 0)
+ /* At end of file */
+ return NULL;
+ /* Parse another line */
+ continue;
+ }
+
+ /* We now have a line of data */
+ *line_end = 0;
+
+ if (flags & PARSE_RAW) {
+ /* Leave '\' (etc) in line buffer (eg 'for' lines) */
+ *length = line_end - line;
+ return line;
+ }
+
+ if (flags & PARSE_SKIP) {
+ /* Completely ignore non-directives */
+ if (line[0] != '.')
+ continue;
+ /* We could do more of the .else/.elif/.endif checks here */
+ }
+ break;
+ }
+
+ /* Brutally ignore anything after a non-escaped '#' in non-commands */
+ if (comment != NULL && line[0] != '\t') {
+ line_end = comment;
+ *line_end = 0;
+ }
+
+ /* If we didn't see a '\\' then the in-situ data is fine */
+ if (escaped == NULL) {
+ *length = line_end - line;
+ return line;
+ }
+
+ /* Remove escapes from '\n' and '#' */
+ tp = ptr = escaped;
+ escaped = line;
+ for (; ; *tp++ = ch) {
+ ch = *ptr++;
+ if (ch != '\\') {
+ if (ch == 0)
+ break;
+ continue;
+ }
+
+ ch = *ptr++;
+ if (ch == 0) {
+ /* Delete '\\' at end of buffer */
+ tp--;
+ break;
+ }
+
+ if (ch == '#' && line[0] != '\t')
+ /* Delete '\\' from before '#' on non-command lines */
+ continue;
+
+ if (ch != '\n') {
+ /* Leave '\\' in buffer for later */
+ *tp++ = '\\';
+ /* Make sure we don't delete an escaped ' ' from the line end */
+ escaped = tp + 1;
+ continue;
+ }
+
+ /* Escaped '\n' replace following whitespace with a single ' ' */
+ while (ptr[0] == ' ' || ptr[0] == '\t')
+ ptr++;
+ ch = ' ';
+ }
+
+ /* Delete any trailing spaces - eg from empty continuations */
+ while (tp > escaped && isspace((unsigned char)tp[-1]))
+ tp--;
+
+ *tp = 0;
+ *length = tp - line;
+ return line;
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseReadLine --
+ * Read an entire line from the input file. Called only by Parse_File.
+ *
+ * Results:
+ * A line w/o its newline
+ *
+ * Side Effects:
+ * Only those associated with reading a character
+ *---------------------------------------------------------------------
+ */
+static char *
+ParseReadLine(void)
+{
+ char *line; /* Result */
+ int lineLength; /* Length of result */
+ int lineno; /* Saved line # */
+ int rval;
+
+ for (;;) {
+ line = ParseGetLine(0, &lineLength);
+ if (line == NULL)
+ return NULL;
+
+ if (line[0] != '.')
+ return line;
+
+ /*
+ * The line might be a conditional. Ask the conditional module
+ * about it and act accordingly
+ */
+ switch (Cond_Eval(line)) {
+ case COND_SKIP:
+ /* Skip to next conditional that evaluates to COND_PARSE. */
+ do {
+ line = ParseGetLine(PARSE_SKIP, &lineLength);
+ } while (line && Cond_Eval(line) != COND_PARSE);
+ if (line == NULL)
+ break;
+ continue;
+ case COND_PARSE:
+ continue;
+ case COND_INVALID: /* Not a conditional line */
+ /* Check for .for loops */
+ rval = For_Eval(line);
+ if (rval == 0)
+ /* Not a .for line */
+ break;
+ if (rval < 0)
+ /* Syntax error - error printed, ignore line */
+ continue;
+ /* Start of a .for loop */
+ lineno = curFile->lineno;
+ /* Accumulate loop lines until matching .endfor */
+ do {
+ line = ParseGetLine(PARSE_RAW, &lineLength);
+ if (line == NULL) {
+ Parse_Error(PARSE_FATAL,
+ "Unexpected end of file in for loop.");
+ break;
+ }
+ } while (For_Accum(line));
+ /* Stash each iteration as a new 'input file' */
+ For_Run(lineno);
+ /* Read next line from for-loop buffer */
+ continue;
+ }
+ return (line);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseFinishLine --
+ * Handle the end of a dependency group.
+ *
+ * Results:
+ * Nothing.
+ *
+ * Side Effects:
+ * inLine set FALSE. 'targets' list destroyed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+ParseFinishLine(void)
+{
+ if (inLine) {
+ Lst_ForEach(targets, Suff_EndTransform, NULL);
+ Lst_Destroy(targets, ParseHasCommands);
+ targets = NULL;
+ inLine = FALSE;
+ }
+}
+
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_File --
+ * Parse a file into its component parts, incorporating it into the
+ * current dependency graph. This is the main function and controls
+ * almost every other function in this module
+ *
+ * Input:
+ * name the name of the file being read
+ * fd Open file to makefile to parse
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * closes fd.
+ * Loads. Nodes are added to the list of all targets, nodes and links
+ * are added to the dependency graph. etc. etc. etc.
+ *---------------------------------------------------------------------
+ */
+void
+Parse_File(const char *name, int fd)
+{
+ char *cp; /* pointer into the line */
+ char *line; /* the line we're working on */
+ struct loadedfile *lf;
+
+ lf = loadfile(name, fd);
+
+ inLine = FALSE;
+ fatals = 0;
+
+ if (name == NULL) {
+ name = "(stdin)";
+ }
+
+ Parse_SetInput(name, 0, -1, loadedfile_nextbuf, lf);
+ curFile->lf = lf;
+
+ do {
+ for (; (line = ParseReadLine()) != NULL; ) {
+ if (DEBUG(PARSE))
+ fprintf(debug_file, "ParseReadLine (%d): '%s'\n",
+ curFile->lineno, line);
+ if (*line == '.') {
+ /*
+ * Lines that begin with the special character may be
+ * include or undef directives.
+ * On the other hand they can be suffix rules (.c.o: ...)
+ * or just dependencies for filenames that start '.'.
+ */
+ for (cp = line + 1; isspace((unsigned char)*cp); cp++) {
+ continue;
+ }
+ if (IsInclude(cp, FALSE)) {
+ ParseDoInclude(cp);
+ continue;
+ }
+ if (strncmp(cp, "undef", 5) == 0) {
+ char *cp2;
+ for (cp += 5; isspace((unsigned char) *cp); cp++)
+ continue;
+ for (cp2 = cp; !isspace((unsigned char) *cp2) &&
+ (*cp2 != '\0'); cp2++)
+ continue;
+ *cp2 = '\0';
+ Var_Delete(cp, VAR_GLOBAL);
+ continue;
+ } else if (strncmp(cp, "export", 6) == 0) {
+ for (cp += 6; isspace((unsigned char) *cp); cp++)
+ continue;
+ Var_Export(cp, 1);
+ continue;
+ } else if (strncmp(cp, "unexport", 8) == 0) {
+ Var_UnExport(cp);
+ continue;
+ } else if (strncmp(cp, "info", 4) == 0 ||
+ strncmp(cp, "error", 5) == 0 ||
+ strncmp(cp, "warning", 7) == 0) {
+ if (ParseMessage(cp))
+ continue;
+ }
+ }
+
+ if (*line == '\t') {
+ /*
+ * If a line starts with a tab, it can only hope to be
+ * a creation command.
+ */
+ cp = line + 1;
+ shellCommand:
+ for (; isspace ((unsigned char)*cp); cp++) {
+ continue;
+ }
+ if (*cp) {
+ if (!inLine)
+ Parse_Error(PARSE_FATAL,
+ "Unassociated shell command \"%s\"",
+ cp);
+ /*
+ * So long as it's not a blank line and we're actually
+ * in a dependency spec, add the command to the list of
+ * commands of all targets in the dependency spec
+ */
+ if (targets) {
+ cp = bmake_strdup(cp);
+ Lst_ForEach(targets, ParseAddCmd, cp);
+#ifdef CLEANUP
+ Lst_AtEnd(targCmds, cp);
+#endif
+ }
+ }
+ continue;
+ }
+
+#ifdef SYSVINCLUDE
+ if (IsSysVInclude(line)) {
+ /*
+ * It's an S3/S5-style "include".
+ */
+ ParseTraditionalInclude(line);
+ continue;
+ }
+#endif
+#ifdef GMAKEEXPORT
+ if (strncmp(line, "export", 6) == 0 &&
+ isspace((unsigned char) line[6]) &&
+ strchr(line, ':') == NULL) {
+ /*
+ * It's a Gmake "export".
+ */
+ ParseGmakeExport(line);
+ continue;
+ }
+#endif
+ if (Parse_IsVar(line)) {
+ ParseFinishLine();
+ Parse_DoVar(line, VAR_GLOBAL);
+ continue;
+ }
+
+#ifndef POSIX
+ /*
+ * To make life easier on novices, if the line is indented we
+ * first make sure the line has a dependency operator in it.
+ * If it doesn't have an operator and we're in a dependency
+ * line's script, we assume it's actually a shell command
+ * and add it to the current list of targets.
+ */
+ cp = line;
+ if (isspace((unsigned char) line[0])) {
+ while ((*cp != '\0') && isspace((unsigned char) *cp))
+ cp++;
+ while (*cp && (ParseIsEscaped(line, cp) ||
+ (*cp != ':') && (*cp != '!'))) {
+ cp++;
+ }
+ if (*cp == '\0') {
+ if (inLine) {
+ Parse_Error(PARSE_WARNING,
+ "Shell command needs a leading tab");
+ goto shellCommand;
+ }
+ }
+ }
+#endif
+ ParseFinishLine();
+
+ /*
+ * For some reason - probably to make the parser impossible -
+ * a ';' can be used to separate commands from dependencies.
+ * Attempt to avoid ';' inside substitution patterns.
+ */
+ {
+ int level = 0;
+
+ for (cp = line; *cp != 0; cp++) {
+ if (*cp == '\\' && cp[1] != 0) {
+ cp++;
+ continue;
+ }
+ if (*cp == '$' &&
+ (cp[1] == '(' || cp[1] == '{')) {
+ level++;
+ continue;
+ }
+ if (level > 0) {
+ if (*cp == ')' || *cp == '}') {
+ level--;
+ continue;
+ }
+ } else if (*cp == ';') {
+ break;
+ }
+ }
+ }
+ if (*cp != 0)
+ /* Terminate the dependency list at the ';' */
+ *cp++ = 0;
+ else
+ cp = NULL;
+
+ /*
+ * We now know it's a dependency line so it needs to have all
+ * variables expanded before being parsed. Tell the variable
+ * module to complain if some variable is undefined...
+ */
+ line = Var_Subst(NULL, line, VAR_CMD, VARF_UNDEFERR|VARF_WANTRES);
+
+ /*
+ * Need a non-circular list for the target nodes
+ */
+ if (targets)
+ Lst_Destroy(targets, NULL);
+
+ targets = Lst_Init(FALSE);
+ inLine = TRUE;
+
+ ParseDoDependency(line);
+ free(line);
+
+ /* If there were commands after a ';', add them now */
+ if (cp != NULL) {
+ goto shellCommand;
+ }
+ }
+ /*
+ * Reached EOF, but it may be just EOF of an include file...
+ */
+ } while (ParseEOF() == CONTINUE);
+
+ if (fatals) {
+ (void)fflush(stdout);
+ (void)fprintf(stderr,
+ "%s: Fatal errors encountered -- cannot continue",
+ progname);
+ PrintOnError(NULL, NULL);
+ exit(1);
+ }
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_Init --
+ * initialize the parsing module
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * the parseIncPath list is initialized...
+ *---------------------------------------------------------------------
+ */
+void
+Parse_Init(void)
+{
+ mainNode = NULL;
+ parseIncPath = Lst_Init(FALSE);
+ sysIncPath = Lst_Init(FALSE);
+ defIncPath = Lst_Init(FALSE);
+ includes = Lst_Init(FALSE);
+#ifdef CLEANUP
+ targCmds = Lst_Init(FALSE);
+#endif
+}
+
+void
+Parse_End(void)
+{
+#ifdef CLEANUP
+ Lst_Destroy(targCmds, (FreeProc *)free);
+ if (targets)
+ Lst_Destroy(targets, NULL);
+ Lst_Destroy(defIncPath, Dir_Destroy);
+ Lst_Destroy(sysIncPath, Dir_Destroy);
+ Lst_Destroy(parseIncPath, Dir_Destroy);
+ Lst_Destroy(includes, NULL); /* Should be empty now */
+#endif
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Parse_MainName --
+ * Return a Lst of the main target to create for main()'s sake. If
+ * no such target exists, we Punt with an obnoxious error message.
+ *
+ * Results:
+ * A Lst of the single node to create.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+Lst
+Parse_MainName(void)
+{
+ Lst mainList; /* result list */
+
+ mainList = Lst_Init(FALSE);
+
+ if (mainNode == NULL) {
+ Punt("no target to make.");
+ /*NOTREACHED*/
+ } else if (mainNode->type & OP_DOUBLEDEP) {
+ (void)Lst_AtEnd(mainList, mainNode);
+ Lst_Concat(mainList, mainNode->cohorts, LST_CONCNEW);
+ }
+ else
+ (void)Lst_AtEnd(mainList, mainNode);
+ Var_Append(".TARGETS", mainNode->name, VAR_GLOBAL);
+ return (mainList);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseMark --
+ * Add the filename and lineno to the GNode so that we remember
+ * where it was first defined.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+ParseMark(GNode *gn)
+{
+ gn->fname = curFile->fname;
+ gn->lineno = curFile->lineno;
+}
diff --git a/usr.bin/make/pathnames.h b/usr.bin/make/pathnames.h
new file mode 100644
index 0000000..12c4f3d
--- /dev/null
+++ b/usr.bin/make/pathnames.h
@@ -0,0 +1,53 @@
+/* $NetBSD: pathnames.h,v 1.17 2009/04/11 09:41:18 apb Exp $ */
+
+/*
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)pathnames.h 5.2 (Berkeley) 6/1/90
+ */
+
+#ifndef MAKE_NATIVE
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#else
+#include <paths.h>
+#endif
+
+#define _PATH_OBJDIR "obj"
+#define _PATH_OBJDIRPREFIX "/usr/obj"
+#ifndef _PATH_DEFSHELLDIR
+#define _PATH_DEFSHELLDIR "/bin"
+#endif
+#define _PATH_DEFSYSMK "sys.mk"
+#ifndef _PATH_DEFSYSPATH
+#define _PATH_DEFSYSPATH "/usr/share/mk"
+#endif
+#ifndef _PATH_TMP
+#define _PATH_TMP "/tmp/" /* with trailing slash */
+#endif
diff --git a/usr.bin/make/sprite.h b/usr.bin/make/sprite.h
new file mode 100644
index 0000000..cdcffd9
--- /dev/null
+++ b/usr.bin/make/sprite.h
@@ -0,0 +1,116 @@
+/* $NetBSD: sprite.h,v 1.14 2017/05/31 22:02:06 maya Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)sprite.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)sprite.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * sprite.h --
+ *
+ * Common constants and type declarations for Sprite.
+ */
+
+#ifndef MAKE_SPRITE_H
+#define MAKE_SPRITE_H
+
+
+/*
+ * A boolean type is defined as an integer, not an enum. This allows a
+ * boolean argument to be an expression that isn't strictly 0 or 1 valued.
+ */
+
+typedef int Boolean;
+#ifndef TRUE
+#define TRUE 1
+#endif /* TRUE */
+#ifndef FALSE
+#define FALSE 0
+#endif /* FALSE */
+
+/*
+ * Functions that must return a status can return a ReturnStatus to
+ * indicate success or type of failure.
+ */
+
+typedef int ReturnStatus;
+
+/*
+ * The following statuses overlap with the first 2 generic statuses
+ * defined in status.h:
+ *
+ * SUCCESS There was no error.
+ * FAILURE There was a general error.
+ */
+
+#define SUCCESS 0x00000000
+#define FAILURE 0x00000001
+
+#endif /* MAKE_SPRITE_H */
diff --git a/usr.bin/make/str.c b/usr.bin/make/str.c
new file mode 100644
index 0000000..b5255bc
--- /dev/null
+++ b/usr.bin/make/str.c
@@ -0,0 +1,526 @@
+/* $NetBSD: str.c,v 1.38 2017/04/21 22:15:44 sjg Exp $ */
+
+/*-
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: str.c,v 1.38 2017/04/21 22:15:44 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)str.c 5.8 (Berkeley) 6/1/90";
+#else
+__RCSID("$NetBSD: str.c,v 1.38 2017/04/21 22:15:44 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+#include "make.h"
+
+/*-
+ * str_concat --
+ * concatenate the two strings, inserting a space or slash between them,
+ * freeing them if requested.
+ *
+ * returns --
+ * the resulting string in allocated space.
+ */
+char *
+str_concat(const char *s1, const char *s2, int flags)
+{
+ int len1, len2;
+ char *result;
+
+ /* get the length of both strings */
+ len1 = strlen(s1);
+ len2 = strlen(s2);
+
+ /* allocate length plus separator plus EOS */
+ result = bmake_malloc((unsigned int)(len1 + len2 + 2));
+
+ /* copy first string into place */
+ memcpy(result, s1, len1);
+
+ /* add separator character */
+ if (flags & STR_ADDSPACE) {
+ result[len1] = ' ';
+ ++len1;
+ } else if (flags & STR_ADDSLASH) {
+ result[len1] = '/';
+ ++len1;
+ }
+
+ /* copy second string plus EOS into place */
+ memcpy(result + len1, s2, len2 + 1);
+
+ return(result);
+}
+
+/*-
+ * brk_string --
+ * Fracture a string into an array of words (as delineated by tabs or
+ * spaces) taking quotation marks into account. Leading tabs/spaces
+ * are ignored.
+ *
+ * If expand is TRUE, quotes are removed and escape sequences
+ * such as \r, \t, etc... are expanded.
+ *
+ * returns --
+ * Pointer to the array of pointers to the words.
+ * Memory containing the actual words in *buffer.
+ * Both of these must be free'd by the caller.
+ * Number of words in *store_argc.
+ */
+char **
+brk_string(const char *str, int *store_argc, Boolean expand, char **buffer)
+{
+ int argc, ch;
+ char inquote, *start, *t;
+ const char *p;
+ int len;
+ int argmax = 50, curlen = 0;
+ char **argv;
+
+ /* skip leading space chars. */
+ for (; *str == ' ' || *str == '\t'; ++str)
+ continue;
+
+ /* allocate room for a copy of the string */
+ if ((len = strlen(str) + 1) > curlen)
+ *buffer = bmake_malloc(curlen = len);
+
+ /*
+ * initial argmax based on len
+ */
+ argmax = MAX((len / 5), 50);
+ argv = bmake_malloc((argmax + 1) * sizeof(char *));
+
+ /*
+ * copy the string; at the same time, parse backslashes,
+ * quotes and build the argument list.
+ */
+ argc = 0;
+ inquote = '\0';
+ for (p = str, start = t = *buffer;; ++p) {
+ switch(ch = *p) {
+ case '"':
+ case '\'':
+ if (inquote) {
+ if (inquote == ch)
+ inquote = '\0';
+ else
+ break;
+ }
+ else {
+ inquote = (char) ch;
+ /* Don't miss "" or '' */
+ if (start == NULL && p[1] == inquote) {
+ if (!expand) {
+ start = t;
+ *t++ = ch;
+ } else
+ start = t + 1;
+ p++;
+ inquote = '\0';
+ break;
+ }
+ }
+ if (!expand) {
+ if (!start)
+ start = t;
+ *t++ = ch;
+ }
+ continue;
+ case ' ':
+ case '\t':
+ case '\n':
+ if (inquote)
+ break;
+ if (!start)
+ continue;
+ /* FALLTHROUGH */
+ case '\0':
+ /*
+ * end of a token -- make sure there's enough argv
+ * space and save off a pointer.
+ */
+ if (!start)
+ goto done;
+
+ *t++ = '\0';
+ if (argc == argmax) {
+ argmax *= 2; /* ramp up fast */
+ argv = (char **)bmake_realloc(argv,
+ (argmax + 1) * sizeof(char *));
+ }
+ argv[argc++] = start;
+ start = NULL;
+ if (ch == '\n' || ch == '\0') {
+ if (expand && inquote) {
+ free(argv);
+ free(*buffer);
+ *buffer = NULL;
+ return NULL;
+ }
+ goto done;
+ }
+ continue;
+ case '\\':
+ if (!expand) {
+ if (!start)
+ start = t;
+ *t++ = '\\';
+ if (*(p+1) == '\0') /* catch '\' at end of line */
+ continue;
+ ch = *++p;
+ break;
+ }
+
+ switch (ch = *++p) {
+ case '\0':
+ case '\n':
+ /* hmmm; fix it up as best we can */
+ ch = '\\';
+ --p;
+ break;
+ case 'b':
+ ch = '\b';
+ break;
+ case 'f':
+ ch = '\f';
+ break;
+ case 'n':
+ ch = '\n';
+ break;
+ case 'r':
+ ch = '\r';
+ break;
+ case 't':
+ ch = '\t';
+ break;
+ }
+ break;
+ }
+ if (!start)
+ start = t;
+ *t++ = (char) ch;
+ }
+done: argv[argc] = NULL;
+ *store_argc = argc;
+ return(argv);
+}
+
+/*
+ * Str_FindSubstring -- See if a string contains a particular substring.
+ *
+ * Input:
+ * string String to search.
+ * substring Substring to find in string.
+ *
+ * Results: If string contains substring, the return value is the location of
+ * the first matching instance of substring in string. If string doesn't
+ * contain substring, the return value is NULL. Matching is done on an exact
+ * character-for-character basis with no wildcards or special characters.
+ *
+ * Side effects: None.
+ */
+char *
+Str_FindSubstring(const char *string, const char *substring)
+{
+ const char *a, *b;
+
+ /*
+ * First scan quickly through the two strings looking for a single-
+ * character match. When it's found, then compare the rest of the
+ * substring.
+ */
+
+ for (b = substring; *string != 0; string += 1) {
+ if (*string != *b)
+ continue;
+ a = string;
+ for (;;) {
+ if (*b == 0)
+ return UNCONST(string);
+ if (*a++ != *b++)
+ break;
+ }
+ b = substring;
+ }
+ return NULL;
+}
+
+/*
+ * Str_Match --
+ *
+ * See if a particular string matches a particular pattern.
+ *
+ * Results: Non-zero is returned if string matches pattern, 0 otherwise. The
+ * matching operation permits the following special characters in the
+ * pattern: *?\[] (see the man page for details on what these mean).
+ *
+ * XXX this function does not detect or report malformed patterns.
+ *
+ * Side effects: None.
+ */
+int
+Str_Match(const char *string, const char *pattern)
+{
+ char c2;
+
+ for (;;) {
+ /*
+ * See if we're at the end of both the pattern and the
+ * string. If, we succeeded. If we're at the end of the
+ * pattern but not at the end of the string, we failed.
+ */
+ if (*pattern == 0)
+ return(!*string);
+ if (*string == 0 && *pattern != '*')
+ return(0);
+ /*
+ * Check for a "*" as the next pattern character. It matches
+ * any substring. We handle this by calling ourselves
+ * recursively for each postfix of string, until either we
+ * match or we reach the end of the string.
+ */
+ if (*pattern == '*') {
+ pattern += 1;
+ if (*pattern == 0)
+ return(1);
+ while (*string != 0) {
+ if (Str_Match(string, pattern))
+ return(1);
+ ++string;
+ }
+ return(0);
+ }
+ /*
+ * Check for a "?" as the next pattern character. It matches
+ * any single character.
+ */
+ if (*pattern == '?')
+ goto thisCharOK;
+ /*
+ * Check for a "[" as the next pattern character. It is
+ * followed by a list of characters that are acceptable, or
+ * by a range (two characters separated by "-").
+ */
+ if (*pattern == '[') {
+ int nomatch;
+
+ ++pattern;
+ if (*pattern == '^') {
+ ++pattern;
+ nomatch = 1;
+ } else
+ nomatch = 0;
+ for (;;) {
+ if ((*pattern == ']') || (*pattern == 0)) {
+ if (nomatch)
+ break;
+ return(0);
+ }
+ if (*pattern == *string)
+ break;
+ if (pattern[1] == '-') {
+ c2 = pattern[2];
+ if (c2 == 0)
+ return(nomatch);
+ if ((*pattern <= *string) &&
+ (c2 >= *string))
+ break;
+ if ((*pattern >= *string) &&
+ (c2 <= *string))
+ break;
+ pattern += 2;
+ }
+ ++pattern;
+ }
+ if (nomatch && (*pattern != ']') && (*pattern != 0))
+ return 0;
+ while ((*pattern != ']') && (*pattern != 0))
+ ++pattern;
+ goto thisCharOK;
+ }
+ /*
+ * If the next pattern character is '/', just strip off the
+ * '/' so we do exact matching on the character that follows.
+ */
+ if (*pattern == '\\') {
+ ++pattern;
+ if (*pattern == 0)
+ return(0);
+ }
+ /*
+ * There's no special character. Just make sure that the
+ * next characters of each string match.
+ */
+ if (*pattern != *string)
+ return(0);
+thisCharOK: ++pattern;
+ ++string;
+ }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Str_SYSVMatch --
+ * Check word against pattern for a match (% is wild),
+ *
+ * Input:
+ * word Word to examine
+ * pattern Pattern to examine against
+ * len Number of characters to substitute
+ *
+ * Results:
+ * Returns the beginning position of a match or null. The number
+ * of characters matched is returned in len.
+ *
+ * Side Effects:
+ * None
+ *
+ *-----------------------------------------------------------------------
+ */
+char *
+Str_SYSVMatch(const char *word, const char *pattern, int *len)
+{
+ const char *p = pattern;
+ const char *w = word;
+ const char *m;
+
+ if (*p == '\0') {
+ /* Null pattern is the whole string */
+ *len = strlen(w);
+ return UNCONST(w);
+ }
+
+ if ((m = strchr(p, '%')) != NULL) {
+ /* check that the prefix matches */
+ for (; p != m && *w && *w == *p; w++, p++)
+ continue;
+
+ if (p != m)
+ return NULL; /* No match */
+
+ if (*++p == '\0') {
+ /* No more pattern, return the rest of the string */
+ *len = strlen(w);
+ return UNCONST(w);
+ }
+ }
+
+ m = w;
+
+ /* Find a matching tail */
+ do
+ if (strcmp(p, w) == 0) {
+ *len = w - m;
+ return UNCONST(m);
+ }
+ while (*w++ != '\0');
+
+ return NULL;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Str_SYSVSubst --
+ * Substitute '%' on the pattern with len characters from src.
+ * If the pattern does not contain a '%' prepend len characters
+ * from src.
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Places result on buf
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Str_SYSVSubst(Buffer *buf, char *pat, char *src, int len)
+{
+ char *m;
+
+ if ((m = strchr(pat, '%')) != NULL) {
+ /* Copy the prefix */
+ Buf_AddBytes(buf, m - pat, pat);
+ /* skip the % */
+ pat = m + 1;
+ }
+
+ /* Copy the pattern */
+ Buf_AddBytes(buf, len, src);
+
+ /* append the rest */
+ Buf_AddBytes(buf, strlen(pat), pat);
+}
diff --git a/usr.bin/make/strlist.c b/usr.bin/make/strlist.c
new file mode 100644
index 0000000..3fb2f7d
--- /dev/null
+++ b/usr.bin/make/strlist.c
@@ -0,0 +1,93 @@
+/* $NetBSD: strlist.c,v 1.4 2009/01/24 11:59:39 dsl Exp $ */
+
+/*-
+ * Copyright (c) 2008 - 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David Laight.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: strlist.c,v 1.4 2009/01/24 11:59:39 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: strlist.c,v 1.4 2009/01/24 11:59:39 dsl Exp $");
+#endif /* not lint */
+#endif
+
+#include <stddef.h>
+#include <stdlib.h>
+#include "strlist.h"
+#include "make_malloc.h"
+
+void
+strlist_init(strlist_t *sl)
+{
+ sl->sl_num = 0;
+ sl->sl_max = 0;
+ sl->sl_items = NULL;
+}
+
+void
+strlist_clean(strlist_t *sl)
+{
+ char *str;
+ int i;
+
+ STRLIST_FOREACH(str, sl, i)
+ free(str);
+ free(sl->sl_items);
+
+ sl->sl_num = 0;
+ sl->sl_max = 0;
+ sl->sl_items = NULL;
+}
+
+void
+strlist_add_str(strlist_t *sl, char *str, unsigned int info)
+{
+ unsigned int n;
+ strlist_item_t *items;
+
+ if (str == NULL)
+ return;
+
+ n = sl->sl_num + 1;
+ sl->sl_num = n;
+ items = sl->sl_items;
+ if (n >= sl->sl_max) {
+ items = bmake_realloc(items, (n + 7) * sizeof *sl->sl_items);
+ sl->sl_items = items;
+ sl->sl_max = n + 6;
+ }
+ items += n - 1;
+ items->si_str = str;
+ items->si_info = info;
+ items[1].si_str = NULL; /* STRLIST_FOREACH() terminator */
+}
diff --git a/usr.bin/make/strlist.h b/usr.bin/make/strlist.h
new file mode 100644
index 0000000..2fc049e
--- /dev/null
+++ b/usr.bin/make/strlist.h
@@ -0,0 +1,62 @@
+/* $NetBSD: strlist.h,v 1.3 2009/01/16 21:15:34 dsl Exp $ */
+
+/*-
+ * Copyright (c) 2008 - 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David Laight.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _STRLIST_H
+#define _STRLIST_H
+
+typedef struct {
+ char *si_str;
+ unsigned int si_info;
+} strlist_item_t;
+
+typedef struct {
+ unsigned int sl_num;
+ unsigned int sl_max;
+ strlist_item_t *sl_items;
+} strlist_t;
+
+void strlist_init(strlist_t *);
+void strlist_clean(strlist_t *);
+void strlist_add_str(strlist_t *, char *, unsigned int);
+
+#define strlist_num(sl) ((sl)->sl_num)
+#define strlist_str(sl, n) ((sl)->sl_items[n].si_str)
+#define strlist_info(sl, n) ((sl)->sl_items[n].si_info)
+#define strlist_set_info(sl, n, v) ((void)((sl)->sl_items[n].si_info = (v)))
+
+#define STRLIST_FOREACH(v, sl, index) \
+ if ((sl)->sl_items != NULL) \
+ for (index = 0; (v = strlist_str(sl, index)) != NULL; index++)
+
+#endif /* _STRLIST_H */
diff --git a/usr.bin/make/suff.c b/usr.bin/make/suff.c
new file mode 100644
index 0000000..df0306a
--- /dev/null
+++ b/usr.bin/make/suff.c
@@ -0,0 +1,2676 @@
+/* $NetBSD: suff.c,v 1.86 2017/04/16 20:38:18 riastradh Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: suff.c,v 1.86 2017/04/16 20:38:18 riastradh Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)suff.c 8.4 (Berkeley) 3/21/94";
+#else
+__RCSID("$NetBSD: suff.c,v 1.86 2017/04/16 20:38:18 riastradh Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * suff.c --
+ * Functions to maintain suffix lists and find implicit dependents
+ * using suffix transformation rules
+ *
+ * Interface:
+ * Suff_Init Initialize all things to do with suffixes.
+ *
+ * Suff_End Cleanup the module
+ *
+ * Suff_DoPaths This function is used to make life easier
+ * when searching for a file according to its
+ * suffix. It takes the global search path,
+ * as defined using the .PATH: target, and appends
+ * its directories to the path of each of the
+ * defined suffixes, as specified using
+ * .PATH<suffix>: targets. In addition, all
+ * directories given for suffixes labeled as
+ * include files or libraries, using the .INCLUDES
+ * or .LIBS targets, are played with using
+ * Dir_MakeFlags to create the .INCLUDES and
+ * .LIBS global variables.
+ *
+ * Suff_ClearSuffixes Clear out all the suffixes and defined
+ * transformations.
+ *
+ * Suff_IsTransform Return TRUE if the passed string is the lhs
+ * of a transformation rule.
+ *
+ * Suff_AddSuffix Add the passed string as another known suffix.
+ *
+ * Suff_GetPath Return the search path for the given suffix.
+ *
+ * Suff_AddInclude Mark the given suffix as denoting an include
+ * file.
+ *
+ * Suff_AddLib Mark the given suffix as denoting a library.
+ *
+ * Suff_AddTransform Add another transformation to the suffix
+ * graph. Returns GNode suitable for framing, I
+ * mean, tacking commands, attributes, etc. on.
+ *
+ * Suff_SetNull Define the suffix to consider the suffix of
+ * any file that doesn't have a known one.
+ *
+ * Suff_FindDeps Find implicit sources for and the location of
+ * a target based on its suffix. Returns the
+ * bottom-most node added to the graph or NULL
+ * if the target had no implicit sources.
+ *
+ * Suff_FindPath Return the appropriate path to search in
+ * order to find the node.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+
+static Lst sufflist; /* Lst of suffixes */
+#ifdef CLEANUP
+static Lst suffClean; /* Lst of suffixes to be cleaned */
+#endif
+static Lst srclist; /* Lst of sources */
+static Lst transforms; /* Lst of transformation rules */
+
+static int sNum = 0; /* Counter for assigning suffix numbers */
+
+/*
+ * Structure describing an individual suffix.
+ */
+typedef struct _Suff {
+ char *name; /* The suffix itself */
+ int nameLen; /* Length of the suffix */
+ short flags; /* Type of suffix */
+#define SUFF_INCLUDE 0x01 /* One which is #include'd */
+#define SUFF_LIBRARY 0x02 /* One which contains a library */
+#define SUFF_NULL 0x04 /* The empty suffix */
+ Lst searchPath; /* The path along which files of this suffix
+ * may be found */
+ int sNum; /* The suffix number */
+ int refCount; /* Reference count of list membership */
+ Lst parents; /* Suffixes we have a transformation to */
+ Lst children; /* Suffixes we have a transformation from */
+ Lst ref; /* List of lists this suffix is referenced */
+} Suff;
+
+/*
+ * for SuffSuffIsSuffix
+ */
+typedef struct {
+ char *ename; /* The end of the name */
+ int len; /* Length of the name */
+} SuffixCmpData;
+
+/*
+ * Structure used in the search for implied sources.
+ */
+typedef struct _Src {
+ char *file; /* The file to look for */
+ char *pref; /* Prefix from which file was formed */
+ Suff *suff; /* The suffix on the file */
+ struct _Src *parent; /* The Src for which this is a source */
+ GNode *node; /* The node describing the file */
+ int children; /* Count of existing children (so we don't free
+ * this thing too early or never nuke it) */
+#ifdef DEBUG_SRC
+ Lst cp; /* Debug; children list */
+#endif
+} Src;
+
+/*
+ * A structure for passing more than one argument to the Lst-library-invoked
+ * function...
+ */
+typedef struct {
+ Lst l;
+ Src *s;
+} LstSrc;
+
+typedef struct {
+ GNode **gn;
+ Suff *s;
+ Boolean r;
+} GNodeSuff;
+
+static Suff *suffNull; /* The NULL suffix for this run */
+static Suff *emptySuff; /* The empty suffix required for POSIX
+ * single-suffix transformation rules */
+
+
+static const char *SuffStrIsPrefix(const char *, const char *);
+static char *SuffSuffIsSuffix(const Suff *, const SuffixCmpData *);
+static int SuffSuffIsSuffixP(const void *, const void *);
+static int SuffSuffHasNameP(const void *, const void *);
+static int SuffSuffIsPrefix(const void *, const void *);
+static int SuffGNHasNameP(const void *, const void *);
+static void SuffUnRef(void *, void *);
+static void SuffFree(void *);
+static void SuffInsert(Lst, Suff *);
+static void SuffRemove(Lst, Suff *);
+static Boolean SuffParseTransform(char *, Suff **, Suff **);
+static int SuffRebuildGraph(void *, void *);
+static int SuffScanTargets(void *, void *);
+static int SuffAddSrc(void *, void *);
+static int SuffRemoveSrc(Lst);
+static void SuffAddLevel(Lst, Src *);
+static Src *SuffFindThem(Lst, Lst);
+static Src *SuffFindCmds(Src *, Lst);
+static void SuffExpandChildren(LstNode, GNode *);
+static void SuffExpandWildcards(LstNode, GNode *);
+static Boolean SuffApplyTransform(GNode *, GNode *, Suff *, Suff *);
+static void SuffFindDeps(GNode *, Lst);
+static void SuffFindArchiveDeps(GNode *, Lst);
+static void SuffFindNormalDeps(GNode *, Lst);
+static int SuffPrintName(void *, void *);
+static int SuffPrintSuff(void *, void *);
+static int SuffPrintTrans(void *, void *);
+
+ /*************** Lst Predicates ****************/
+/*-
+ *-----------------------------------------------------------------------
+ * SuffStrIsPrefix --
+ * See if pref is a prefix of str.
+ *
+ * Input:
+ * pref possible prefix
+ * str string to check
+ *
+ * Results:
+ * NULL if it ain't, pointer to character in str after prefix if so
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static const char *
+SuffStrIsPrefix(const char *pref, const char *str)
+{
+ while (*str && *pref == *str) {
+ pref++;
+ str++;
+ }
+
+ return (*pref ? NULL : str);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffSuffIsSuffix --
+ * See if suff is a suffix of str. sd->ename should point to THE END
+ * of the string to check. (THE END == the null byte)
+ *
+ * Input:
+ * s possible suffix
+ * sd string to examine
+ *
+ * Results:
+ * NULL if it ain't, pointer to character in str before suffix if
+ * it is.
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static char *
+SuffSuffIsSuffix(const Suff *s, const SuffixCmpData *sd)
+{
+ char *p1; /* Pointer into suffix name */
+ char *p2; /* Pointer into string being examined */
+
+ if (sd->len < s->nameLen)
+ return NULL; /* this string is shorter than the suffix */
+
+ p1 = s->name + s->nameLen;
+ p2 = sd->ename;
+
+ while (p1 >= s->name && *p1 == *p2) {
+ p1--;
+ p2--;
+ }
+
+ return (p1 == s->name - 1 ? p2 : NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffSuffIsSuffixP --
+ * Predicate form of SuffSuffIsSuffix. Passed as the callback function
+ * to Lst_Find.
+ *
+ * Results:
+ * 0 if the suffix is the one desired, non-zero if not.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffSuffIsSuffixP(const void *s, const void *sd)
+{
+ return(!SuffSuffIsSuffix(s, sd));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffSuffHasNameP --
+ * Callback procedure for finding a suffix based on its name. Used by
+ * Suff_GetPath.
+ *
+ * Input:
+ * s Suffix to check
+ * sd Desired name
+ *
+ * Results:
+ * 0 if the suffix is of the given name. non-zero otherwise.
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffSuffHasNameP(const void *s, const void *sname)
+{
+ return (strcmp(sname, ((const Suff *)s)->name));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffSuffIsPrefix --
+ * See if the suffix described by s is a prefix of the string. Care
+ * must be taken when using this to search for transformations and
+ * what-not, since there could well be two suffixes, one of which
+ * is a prefix of the other...
+ *
+ * Input:
+ * s suffix to compare
+ * str string to examine
+ *
+ * Results:
+ * 0 if s is a prefix of str. non-zero otherwise
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffSuffIsPrefix(const void *s, const void *str)
+{
+ return SuffStrIsPrefix(((const Suff *)s)->name, str) == NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffGNHasNameP --
+ * See if the graph node has the desired name
+ *
+ * Input:
+ * gn current node we're looking at
+ * name name we're looking for
+ *
+ * Results:
+ * 0 if it does. non-zero if it doesn't
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffGNHasNameP(const void *gn, const void *name)
+{
+ return (strcmp(name, ((const GNode *)gn)->name));
+}
+
+ /*********** Maintenance Functions ************/
+
+static void
+SuffUnRef(void *lp, void *sp)
+{
+ Lst l = (Lst) lp;
+
+ LstNode ln = Lst_Member(l, sp);
+ if (ln != NULL) {
+ Lst_Remove(l, ln);
+ ((Suff *)sp)->refCount--;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFree --
+ * Free up all memory associated with the given suffix structure.
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * the suffix entry is detroyed
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffFree(void *sp)
+{
+ Suff *s = (Suff *)sp;
+
+ if (s == suffNull)
+ suffNull = NULL;
+
+ if (s == emptySuff)
+ emptySuff = NULL;
+
+#ifdef notdef
+ /* We don't delete suffixes in order, so we cannot use this */
+ if (s->refCount)
+ Punt("Internal error deleting suffix `%s' with refcount = %d", s->name,
+ s->refCount);
+#endif
+
+ Lst_Destroy(s->ref, NULL);
+ Lst_Destroy(s->children, NULL);
+ Lst_Destroy(s->parents, NULL);
+ Lst_Destroy(s->searchPath, Dir_Destroy);
+
+ free(s->name);
+ free(s);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffRemove --
+ * Remove the suffix into the list
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The reference count for the suffix is decremented and the
+ * suffix is possibly freed
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffRemove(Lst l, Suff *s)
+{
+ SuffUnRef(l, s);
+ if (s->refCount == 0) {
+ SuffUnRef(sufflist, s);
+ SuffFree(s);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffInsert --
+ * Insert the suffix into the list keeping the list ordered by suffix
+ * numbers.
+ *
+ * Input:
+ * l the list where in s should be inserted
+ * s the suffix to insert
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The reference count of the suffix is incremented
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffInsert(Lst l, Suff *s)
+{
+ LstNode ln; /* current element in l we're examining */
+ Suff *s2 = NULL; /* the suffix descriptor in this element */
+
+ if (Lst_Open(l) == FAILURE) {
+ return;
+ }
+ while ((ln = Lst_Next(l)) != NULL) {
+ s2 = (Suff *)Lst_Datum(ln);
+ if (s2->sNum >= s->sNum) {
+ break;
+ }
+ }
+
+ Lst_Close(l);
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "inserting %s(%d)...", s->name, s->sNum);
+ }
+ if (ln == NULL) {
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "at end of list\n");
+ }
+ (void)Lst_AtEnd(l, s);
+ s->refCount++;
+ (void)Lst_AtEnd(s->ref, l);
+ } else if (s2->sNum != s->sNum) {
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "before %s(%d)\n", s2->name, s2->sNum);
+ }
+ (void)Lst_InsertBefore(l, ln, s);
+ s->refCount++;
+ (void)Lst_AtEnd(s->ref, l);
+ } else if (DEBUG(SUFF)) {
+ fprintf(debug_file, "already there\n");
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_ClearSuffixes --
+ * This is gross. Nuke the list of suffixes but keep all transformation
+ * rules around. The transformation graph is destroyed in this process,
+ * but we leave the list of rules so when a new graph is formed the rules
+ * will remain.
+ * This function is called from the parse module when a
+ * .SUFFIXES:\n line is encountered.
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * the sufflist and its graph nodes are destroyed
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_ClearSuffixes(void)
+{
+#ifdef CLEANUP
+ Lst_Concat(suffClean, sufflist, LST_CONCLINK);
+#endif
+ sufflist = Lst_Init(FALSE);
+ sNum = 0;
+ if (suffNull)
+ SuffFree(suffNull);
+ emptySuff = suffNull = bmake_malloc(sizeof(Suff));
+
+ suffNull->name = bmake_strdup("");
+ suffNull->nameLen = 0;
+ suffNull->searchPath = Lst_Init(FALSE);
+ Dir_Concat(suffNull->searchPath, dirSearchPath);
+ suffNull->children = Lst_Init(FALSE);
+ suffNull->parents = Lst_Init(FALSE);
+ suffNull->ref = Lst_Init(FALSE);
+ suffNull->sNum = sNum++;
+ suffNull->flags = SUFF_NULL;
+ suffNull->refCount = 1;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffParseTransform --
+ * Parse a transformation string to find its two component suffixes.
+ *
+ * Input:
+ * str String being parsed
+ * srcPtr Place to store source of trans.
+ * targPtr Place to store target of trans.
+ *
+ * Results:
+ * TRUE if the string is a valid transformation and FALSE otherwise.
+ *
+ * Side Effects:
+ * The passed pointers are overwritten.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+SuffParseTransform(char *str, Suff **srcPtr, Suff **targPtr)
+{
+ LstNode srcLn; /* element in suffix list of trans source*/
+ Suff *src; /* Source of transformation */
+ LstNode targLn; /* element in suffix list of trans target*/
+ char *str2; /* Extra pointer (maybe target suffix) */
+ LstNode singleLn; /* element in suffix list of any suffix
+ * that exactly matches str */
+ Suff *single = NULL;/* Source of possible transformation to
+ * null suffix */
+
+ srcLn = NULL;
+ singleLn = NULL;
+
+ /*
+ * Loop looking first for a suffix that matches the start of the
+ * string and then for one that exactly matches the rest of it. If
+ * we can find two that meet these criteria, we've successfully
+ * parsed the string.
+ */
+ for (;;) {
+ if (srcLn == NULL) {
+ srcLn = Lst_Find(sufflist, str, SuffSuffIsPrefix);
+ } else {
+ srcLn = Lst_FindFrom(sufflist, Lst_Succ(srcLn), str,
+ SuffSuffIsPrefix);
+ }
+ if (srcLn == NULL) {
+ /*
+ * Ran out of source suffixes -- no such rule
+ */
+ if (singleLn != NULL) {
+ /*
+ * Not so fast Mr. Smith! There was a suffix that encompassed
+ * the entire string, so we assume it was a transformation
+ * to the null suffix (thank you POSIX). We still prefer to
+ * find a double rule over a singleton, hence we leave this
+ * check until the end.
+ *
+ * XXX: Use emptySuff over suffNull?
+ */
+ *srcPtr = single;
+ *targPtr = suffNull;
+ return(TRUE);
+ }
+ return (FALSE);
+ }
+ src = (Suff *)Lst_Datum(srcLn);
+ str2 = str + src->nameLen;
+ if (*str2 == '\0') {
+ single = src;
+ singleLn = srcLn;
+ } else {
+ targLn = Lst_Find(sufflist, str2, SuffSuffHasNameP);
+ if (targLn != NULL) {
+ *srcPtr = src;
+ *targPtr = (Suff *)Lst_Datum(targLn);
+ return (TRUE);
+ }
+ }
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_IsTransform --
+ * Return TRUE if the given string is a transformation rule
+ *
+ *
+ * Input:
+ * str string to check
+ *
+ * Results:
+ * TRUE if the string is a concatenation of two known suffixes.
+ * FALSE otherwise
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Suff_IsTransform(char *str)
+{
+ Suff *src, *targ;
+
+ return (SuffParseTransform(str, &src, &targ));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_AddTransform --
+ * Add the transformation rule described by the line to the
+ * list of rules and place the transformation itself in the graph
+ *
+ * Input:
+ * line name of transformation to add
+ *
+ * Results:
+ * The node created for the transformation in the transforms list
+ *
+ * Side Effects:
+ * The node is placed on the end of the transforms Lst and links are
+ * made between the two suffixes mentioned in the target name
+ *-----------------------------------------------------------------------
+ */
+GNode *
+Suff_AddTransform(char *line)
+{
+ GNode *gn; /* GNode of transformation rule */
+ Suff *s, /* source suffix */
+ *t; /* target suffix */
+ LstNode ln; /* Node for existing transformation */
+
+ ln = Lst_Find(transforms, line, SuffGNHasNameP);
+ if (ln == NULL) {
+ /*
+ * Make a new graph node for the transformation. It will be filled in
+ * by the Parse module.
+ */
+ gn = Targ_NewGN(line);
+ (void)Lst_AtEnd(transforms, gn);
+ } else {
+ /*
+ * New specification for transformation rule. Just nuke the old list
+ * of commands so they can be filled in again... We don't actually
+ * free the commands themselves, because a given command can be
+ * attached to several different transformations.
+ */
+ gn = (GNode *)Lst_Datum(ln);
+ Lst_Destroy(gn->commands, NULL);
+ Lst_Destroy(gn->children, NULL);
+ gn->commands = Lst_Init(FALSE);
+ gn->children = Lst_Init(FALSE);
+ }
+
+ gn->type = OP_TRANSFORM;
+
+ (void)SuffParseTransform(line, &s, &t);
+
+ /*
+ * link the two together in the proper relationship and order
+ */
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "defining transformation from `%s' to `%s'\n",
+ s->name, t->name);
+ }
+ SuffInsert(t->children, s);
+ SuffInsert(s->parents, t);
+
+ return (gn);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_EndTransform --
+ * Handle the finish of a transformation definition, removing the
+ * transformation from the graph if it has neither commands nor
+ * sources. This is a callback procedure for the Parse module via
+ * Lst_ForEach
+ *
+ * Input:
+ * gnp Node for transformation
+ * dummy Node for transformation
+ *
+ * Results:
+ * === 0
+ *
+ * Side Effects:
+ * If the node has no commands or children, the children and parents
+ * lists of the affected suffixes are altered.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Suff_EndTransform(void *gnp, void *dummy MAKE_ATTR_UNUSED)
+{
+ GNode *gn = (GNode *)gnp;
+
+ if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts))
+ gn = (GNode *)Lst_Datum(Lst_Last(gn->cohorts));
+ if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) &&
+ Lst_IsEmpty(gn->children))
+ {
+ Suff *s, *t;
+
+ /*
+ * SuffParseTransform() may fail for special rules which are not
+ * actual transformation rules. (e.g. .DEFAULT)
+ */
+ if (SuffParseTransform(gn->name, &s, &t)) {
+ Lst p;
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "deleting transformation from `%s' to `%s'\n",
+ s->name, t->name);
+ }
+
+ /*
+ * Store s->parents because s could be deleted in SuffRemove
+ */
+ p = s->parents;
+
+ /*
+ * Remove the source from the target's children list. We check for a
+ * nil return to handle a beanhead saying something like
+ * .c.o .c.o:
+ *
+ * We'll be called twice when the next target is seen, but .c and .o
+ * are only linked once...
+ */
+ SuffRemove(t->children, s);
+
+ /*
+ * Remove the target from the source's parents list
+ */
+ SuffRemove(p, t);
+ }
+ } else if ((gn->type & OP_TRANSFORM) && DEBUG(SUFF)) {
+ fprintf(debug_file, "transformation %s complete\n", gn->name);
+ }
+
+ return 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffRebuildGraph --
+ * Called from Suff_AddSuffix via Lst_ForEach to search through the
+ * list of existing transformation rules and rebuild the transformation
+ * graph when it has been destroyed by Suff_ClearSuffixes. If the
+ * given rule is a transformation involving this suffix and another,
+ * existing suffix, the proper relationship is established between
+ * the two.
+ *
+ * Input:
+ * transformp Transformation to test
+ * sp Suffix to rebuild
+ *
+ * Results:
+ * Always 0.
+ *
+ * Side Effects:
+ * The appropriate links will be made between this suffix and
+ * others if transformation rules exist for it.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffRebuildGraph(void *transformp, void *sp)
+{
+ GNode *transform = (GNode *)transformp;
+ Suff *s = (Suff *)sp;
+ char *cp;
+ LstNode ln;
+ Suff *s2;
+ SuffixCmpData sd;
+
+ /*
+ * First see if it is a transformation from this suffix.
+ */
+ cp = UNCONST(SuffStrIsPrefix(s->name, transform->name));
+ if (cp != NULL) {
+ ln = Lst_Find(sufflist, cp, SuffSuffHasNameP);
+ if (ln != NULL) {
+ /*
+ * Found target. Link in and return, since it can't be anything
+ * else.
+ */
+ s2 = (Suff *)Lst_Datum(ln);
+ SuffInsert(s2->children, s);
+ SuffInsert(s->parents, s2);
+ return(0);
+ }
+ }
+
+ /*
+ * Not from, maybe to?
+ */
+ sd.len = strlen(transform->name);
+ sd.ename = transform->name + sd.len;
+ cp = SuffSuffIsSuffix(s, &sd);
+ if (cp != NULL) {
+ /*
+ * Null-terminate the source suffix in order to find it.
+ */
+ cp[1] = '\0';
+ ln = Lst_Find(sufflist, transform->name, SuffSuffHasNameP);
+ /*
+ * Replace the start of the target suffix
+ */
+ cp[1] = s->name[0];
+ if (ln != NULL) {
+ /*
+ * Found it -- establish the proper relationship
+ */
+ s2 = (Suff *)Lst_Datum(ln);
+ SuffInsert(s->children, s2);
+ SuffInsert(s2->parents, s);
+ }
+ }
+ return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffScanTargets --
+ * Called from Suff_AddSuffix via Lst_ForEach to search through the
+ * list of existing targets and find if any of the existing targets
+ * can be turned into a transformation rule.
+ *
+ * Results:
+ * 1 if a new main target has been selected, 0 otherwise.
+ *
+ * Side Effects:
+ * If such a target is found and the target is the current main
+ * target, the main target is set to NULL and the next target
+ * examined (if that exists) becomes the main target.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffScanTargets(void *targetp, void *gsp)
+{
+ GNode *target = (GNode *)targetp;
+ GNodeSuff *gs = (GNodeSuff *)gsp;
+ Suff *s, *t;
+ char *ptr;
+
+ if (*gs->gn == NULL && gs->r && (target->type & OP_NOTARGET) == 0) {
+ *gs->gn = target;
+ Targ_SetMain(target);
+ return 1;
+ }
+
+ if ((unsigned int)target->type == OP_TRANSFORM)
+ return 0;
+
+ if ((ptr = strstr(target->name, gs->s->name)) == NULL ||
+ ptr == target->name)
+ return 0;
+
+ if (SuffParseTransform(target->name, &s, &t)) {
+ if (*gs->gn == target) {
+ gs->r = TRUE;
+ *gs->gn = NULL;
+ Targ_SetMain(NULL);
+ }
+ Lst_Destroy(target->children, NULL);
+ target->children = Lst_Init(FALSE);
+ target->type = OP_TRANSFORM;
+ /*
+ * link the two together in the proper relationship and order
+ */
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "defining transformation from `%s' to `%s'\n",
+ s->name, t->name);
+ }
+ SuffInsert(t->children, s);
+ SuffInsert(s->parents, t);
+ }
+ return 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_AddSuffix --
+ * Add the suffix in string to the end of the list of known suffixes.
+ * Should we restructure the suffix graph? Make doesn't...
+ *
+ * Input:
+ * str the name of the suffix to add
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * A GNode is created for the suffix and a Suff structure is created and
+ * added to the suffixes list unless the suffix was already known.
+ * The mainNode passed can be modified if a target mutated into a
+ * transform and that target happened to be the main target.
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_AddSuffix(char *str, GNode **gn)
+{
+ Suff *s; /* new suffix descriptor */
+ LstNode ln;
+ GNodeSuff gs;
+
+ ln = Lst_Find(sufflist, str, SuffSuffHasNameP);
+ if (ln == NULL) {
+ s = bmake_malloc(sizeof(Suff));
+
+ s->name = bmake_strdup(str);
+ s->nameLen = strlen(s->name);
+ s->searchPath = Lst_Init(FALSE);
+ s->children = Lst_Init(FALSE);
+ s->parents = Lst_Init(FALSE);
+ s->ref = Lst_Init(FALSE);
+ s->sNum = sNum++;
+ s->flags = 0;
+ s->refCount = 1;
+
+ (void)Lst_AtEnd(sufflist, s);
+ /*
+ * We also look at our existing targets list to see if adding
+ * this suffix will make one of our current targets mutate into
+ * a suffix rule. This is ugly, but other makes treat all targets
+ * that start with a . as suffix rules.
+ */
+ gs.gn = gn;
+ gs.s = s;
+ gs.r = FALSE;
+ Lst_ForEach(Targ_List(), SuffScanTargets, &gs);
+ /*
+ * Look for any existing transformations from or to this suffix.
+ * XXX: Only do this after a Suff_ClearSuffixes?
+ */
+ Lst_ForEach(transforms, SuffRebuildGraph, s);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_GetPath --
+ * Return the search path for the given suffix, if it's defined.
+ *
+ * Results:
+ * The searchPath for the desired suffix or NULL if the suffix isn't
+ * defined.
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+Lst
+Suff_GetPath(char *sname)
+{
+ LstNode ln;
+ Suff *s;
+
+ ln = Lst_Find(sufflist, sname, SuffSuffHasNameP);
+ if (ln == NULL) {
+ return NULL;
+ } else {
+ s = (Suff *)Lst_Datum(ln);
+ return (s->searchPath);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_DoPaths --
+ * Extend the search paths for all suffixes to include the default
+ * search path.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The searchPath field of all the suffixes is extended by the
+ * directories in dirSearchPath. If paths were specified for the
+ * ".h" suffix, the directories are stuffed into a global variable
+ * called ".INCLUDES" with each directory preceded by a -I. The same
+ * is done for the ".a" suffix, except the variable is called
+ * ".LIBS" and the flag is -L.
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_DoPaths(void)
+{
+ Suff *s;
+ LstNode ln;
+ char *ptr;
+ Lst inIncludes; /* Cumulative .INCLUDES path */
+ Lst inLibs; /* Cumulative .LIBS path */
+
+ if (Lst_Open(sufflist) == FAILURE) {
+ return;
+ }
+
+ inIncludes = Lst_Init(FALSE);
+ inLibs = Lst_Init(FALSE);
+
+ while ((ln = Lst_Next(sufflist)) != NULL) {
+ s = (Suff *)Lst_Datum(ln);
+ if (!Lst_IsEmpty (s->searchPath)) {
+#ifdef INCLUDES
+ if (s->flags & SUFF_INCLUDE) {
+ Dir_Concat(inIncludes, s->searchPath);
+ }
+#endif /* INCLUDES */
+#ifdef LIBRARIES
+ if (s->flags & SUFF_LIBRARY) {
+ Dir_Concat(inLibs, s->searchPath);
+ }
+#endif /* LIBRARIES */
+ Dir_Concat(s->searchPath, dirSearchPath);
+ } else {
+ Lst_Destroy(s->searchPath, Dir_Destroy);
+ s->searchPath = Lst_Duplicate(dirSearchPath, Dir_CopyDir);
+ }
+ }
+
+ Var_Set(".INCLUDES", ptr = Dir_MakeFlags("-I", inIncludes), VAR_GLOBAL, 0);
+ free(ptr);
+ Var_Set(".LIBS", ptr = Dir_MakeFlags("-L", inLibs), VAR_GLOBAL, 0);
+ free(ptr);
+
+ Lst_Destroy(inIncludes, Dir_Destroy);
+ Lst_Destroy(inLibs, Dir_Destroy);
+
+ Lst_Close(sufflist);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_AddInclude --
+ * Add the given suffix as a type of file which gets included.
+ * Called from the parse module when a .INCLUDES line is parsed.
+ * The suffix must have already been defined.
+ *
+ * Input:
+ * sname Name of the suffix to mark
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The SUFF_INCLUDE bit is set in the suffix's flags field
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_AddInclude(char *sname)
+{
+ LstNode ln;
+ Suff *s;
+
+ ln = Lst_Find(sufflist, sname, SuffSuffHasNameP);
+ if (ln != NULL) {
+ s = (Suff *)Lst_Datum(ln);
+ s->flags |= SUFF_INCLUDE;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_AddLib --
+ * Add the given suffix as a type of file which is a library.
+ * Called from the parse module when parsing a .LIBS line. The
+ * suffix must have been defined via .SUFFIXES before this is
+ * called.
+ *
+ * Input:
+ * sname Name of the suffix to mark
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The SUFF_LIBRARY bit is set in the suffix's flags field
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_AddLib(char *sname)
+{
+ LstNode ln;
+ Suff *s;
+
+ ln = Lst_Find(sufflist, sname, SuffSuffHasNameP);
+ if (ln != NULL) {
+ s = (Suff *)Lst_Datum(ln);
+ s->flags |= SUFF_LIBRARY;
+ }
+}
+
+ /********** Implicit Source Search Functions *********/
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffAddSrc --
+ * Add a suffix as a Src structure to the given list with its parent
+ * being the given Src structure. If the suffix is the null suffix,
+ * the prefix is used unaltered as the file name in the Src structure.
+ *
+ * Input:
+ * sp suffix for which to create a Src structure
+ * lsp list and parent for the new Src
+ *
+ * Results:
+ * always returns 0
+ *
+ * Side Effects:
+ * A Src structure is created and tacked onto the end of the list
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffAddSrc(void *sp, void *lsp)
+{
+ Suff *s = (Suff *)sp;
+ LstSrc *ls = (LstSrc *)lsp;
+ Src *s2; /* new Src structure */
+ Src *targ; /* Target structure */
+
+ targ = ls->s;
+
+ if ((s->flags & SUFF_NULL) && (*s->name != '\0')) {
+ /*
+ * If the suffix has been marked as the NULL suffix, also create a Src
+ * structure for a file with no suffix attached. Two birds, and all
+ * that...
+ */
+ s2 = bmake_malloc(sizeof(Src));
+ s2->file = bmake_strdup(targ->pref);
+ s2->pref = targ->pref;
+ s2->parent = targ;
+ s2->node = NULL;
+ s2->suff = s;
+ s->refCount++;
+ s2->children = 0;
+ targ->children += 1;
+ (void)Lst_AtEnd(ls->l, s2);
+#ifdef DEBUG_SRC
+ s2->cp = Lst_Init(FALSE);
+ Lst_AtEnd(targ->cp, s2);
+ fprintf(debug_file, "1 add %p %p to %p:", targ, s2, ls->l);
+ Lst_ForEach(ls->l, PrintAddr, NULL);
+ fprintf(debug_file, "\n");
+#endif
+ }
+ s2 = bmake_malloc(sizeof(Src));
+ s2->file = str_concat(targ->pref, s->name, 0);
+ s2->pref = targ->pref;
+ s2->parent = targ;
+ s2->node = NULL;
+ s2->suff = s;
+ s->refCount++;
+ s2->children = 0;
+ targ->children += 1;
+ (void)Lst_AtEnd(ls->l, s2);
+#ifdef DEBUG_SRC
+ s2->cp = Lst_Init(FALSE);
+ Lst_AtEnd(targ->cp, s2);
+ fprintf(debug_file, "2 add %p %p to %p:", targ, s2, ls->l);
+ Lst_ForEach(ls->l, PrintAddr, NULL);
+ fprintf(debug_file, "\n");
+#endif
+
+ return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffAddLevel --
+ * Add all the children of targ as Src structures to the given list
+ *
+ * Input:
+ * l list to which to add the new level
+ * targ Src structure to use as the parent
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Lots of structures are created and added to the list
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffAddLevel(Lst l, Src *targ)
+{
+ LstSrc ls;
+
+ ls.s = targ;
+ ls.l = l;
+
+ Lst_ForEach(targ->suff->children, SuffAddSrc, &ls);
+}
+
+/*-
+ *----------------------------------------------------------------------
+ * SuffRemoveSrc --
+ * Free all src structures in list that don't have a reference count
+ *
+ * Results:
+ * Ture if an src was removed
+ *
+ * Side Effects:
+ * The memory is free'd.
+ *----------------------------------------------------------------------
+ */
+static int
+SuffRemoveSrc(Lst l)
+{
+ LstNode ln;
+ Src *s;
+ int t = 0;
+
+ if (Lst_Open(l) == FAILURE) {
+ return 0;
+ }
+#ifdef DEBUG_SRC
+ fprintf(debug_file, "cleaning %lx: ", (unsigned long) l);
+ Lst_ForEach(l, PrintAddr, NULL);
+ fprintf(debug_file, "\n");
+#endif
+
+
+ while ((ln = Lst_Next(l)) != NULL) {
+ s = (Src *)Lst_Datum(ln);
+ if (s->children == 0) {
+ free(s->file);
+ if (!s->parent)
+ free(s->pref);
+ else {
+#ifdef DEBUG_SRC
+ LstNode ln2 = Lst_Member(s->parent->cp, s);
+ if (ln2 != NULL)
+ Lst_Remove(s->parent->cp, ln2);
+#endif
+ --s->parent->children;
+ }
+#ifdef DEBUG_SRC
+ fprintf(debug_file, "free: [l=%p] p=%p %d\n", l, s, s->children);
+ Lst_Destroy(s->cp, NULL);
+#endif
+ Lst_Remove(l, ln);
+ free(s);
+ t |= 1;
+ Lst_Close(l);
+ return TRUE;
+ }
+#ifdef DEBUG_SRC
+ else {
+ fprintf(debug_file, "keep: [l=%p] p=%p %d: ", l, s, s->children);
+ Lst_ForEach(s->cp, PrintAddr, NULL);
+ fprintf(debug_file, "\n");
+ }
+#endif
+ }
+
+ Lst_Close(l);
+
+ return t;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFindThem --
+ * Find the first existing file/target in the list srcs
+ *
+ * Input:
+ * srcs list of Src structures to search through
+ *
+ * Results:
+ * The lowest structure in the chain of transformations
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static Src *
+SuffFindThem(Lst srcs, Lst slst)
+{
+ Src *s; /* current Src */
+ Src *rs; /* returned Src */
+ char *ptr;
+
+ rs = NULL;
+
+ while (!Lst_IsEmpty (srcs)) {
+ s = (Src *)Lst_DeQueue(srcs);
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "\ttrying %s...", s->file);
+ }
+
+ /*
+ * A file is considered to exist if either a node exists in the
+ * graph for it or the file actually exists.
+ */
+ if (Targ_FindNode(s->file, TARG_NOCREATE) != NULL) {
+#ifdef DEBUG_SRC
+ fprintf(debug_file, "remove %p from %p\n", s, srcs);
+#endif
+ rs = s;
+ break;
+ }
+
+ if ((ptr = Dir_FindFile(s->file, s->suff->searchPath)) != NULL) {
+ rs = s;
+#ifdef DEBUG_SRC
+ fprintf(debug_file, "remove %p from %p\n", s, srcs);
+#endif
+ free(ptr);
+ break;
+ }
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "not there\n");
+ }
+
+ SuffAddLevel(srcs, s);
+ Lst_AtEnd(slst, s);
+ }
+
+ if (DEBUG(SUFF) && rs) {
+ fprintf(debug_file, "got it\n");
+ }
+ return (rs);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFindCmds --
+ * See if any of the children of the target in the Src structure is
+ * one from which the target can be transformed. If there is one,
+ * a Src structure is put together for it and returned.
+ *
+ * Input:
+ * targ Src structure to play with
+ *
+ * Results:
+ * The Src structure of the "winning" child, or NULL if no such beast.
+ *
+ * Side Effects:
+ * A Src structure may be allocated.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Src *
+SuffFindCmds(Src *targ, Lst slst)
+{
+ LstNode ln; /* General-purpose list node */
+ GNode *t, /* Target GNode */
+ *s; /* Source GNode */
+ int prefLen;/* The length of the defined prefix */
+ Suff *suff; /* Suffix on matching beastie */
+ Src *ret; /* Return value */
+ char *cp;
+
+ t = targ->node;
+ (void)Lst_Open(t->children);
+ prefLen = strlen(targ->pref);
+
+ for (;;) {
+ ln = Lst_Next(t->children);
+ if (ln == NULL) {
+ Lst_Close(t->children);
+ return NULL;
+ }
+ s = (GNode *)Lst_Datum(ln);
+
+ if (s->type & OP_OPTIONAL && Lst_IsEmpty(t->commands)) {
+ /*
+ * We haven't looked to see if .OPTIONAL files exist yet, so
+ * don't use one as the implicit source.
+ * This allows us to use .OPTIONAL in .depend files so make won't
+ * complain "don't know how to make xxx.h' when a dependent file
+ * has been moved/deleted.
+ */
+ continue;
+ }
+
+ cp = strrchr(s->name, '/');
+ if (cp == NULL) {
+ cp = s->name;
+ } else {
+ cp++;
+ }
+ if (strncmp(cp, targ->pref, prefLen) != 0)
+ continue;
+ /*
+ * The node matches the prefix ok, see if it has a known
+ * suffix.
+ */
+ ln = Lst_Find(sufflist, &cp[prefLen], SuffSuffHasNameP);
+ if (ln == NULL)
+ continue;
+ /*
+ * It even has a known suffix, see if there's a transformation
+ * defined between the node's suffix and the target's suffix.
+ *
+ * XXX: Handle multi-stage transformations here, too.
+ */
+ suff = (Suff *)Lst_Datum(ln);
+
+ if (Lst_Member(suff->parents, targ->suff) != NULL)
+ break;
+ }
+
+ /*
+ * Hot Damn! Create a new Src structure to describe
+ * this transformation (making sure to duplicate the
+ * source node's name so Suff_FindDeps can free it
+ * again (ick)), and return the new structure.
+ */
+ ret = bmake_malloc(sizeof(Src));
+ ret->file = bmake_strdup(s->name);
+ ret->pref = targ->pref;
+ ret->suff = suff;
+ suff->refCount++;
+ ret->parent = targ;
+ ret->node = s;
+ ret->children = 0;
+ targ->children += 1;
+#ifdef DEBUG_SRC
+ ret->cp = Lst_Init(FALSE);
+ fprintf(debug_file, "3 add %p %p\n", targ, ret);
+ Lst_AtEnd(targ->cp, ret);
+#endif
+ Lst_AtEnd(slst, ret);
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "\tusing existing source %s\n", s->name);
+ }
+ return (ret);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffExpandChildren --
+ * Expand the names of any children of a given node that contain
+ * variable invocations or file wildcards into actual targets.
+ *
+ * Input:
+ * cln Child to examine
+ * pgn Parent node being processed
+ *
+ * Results:
+ * === 0 (continue)
+ *
+ * Side Effects:
+ * The expanded node is removed from the parent's list of children,
+ * and the parent's unmade counter is decremented, but other nodes
+ * may be added.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffExpandChildren(LstNode cln, GNode *pgn)
+{
+ GNode *cgn = (GNode *)Lst_Datum(cln);
+ GNode *gn; /* New source 8) */
+ char *cp; /* Expanded value */
+
+ if (!Lst_IsEmpty(cgn->order_pred) || !Lst_IsEmpty(cgn->order_succ))
+ /* It is all too hard to process the result of .ORDER */
+ return;
+
+ if (cgn->type & OP_WAIT)
+ /* Ignore these (& OP_PHONY ?) */
+ return;
+
+ /*
+ * First do variable expansion -- this takes precedence over
+ * wildcard expansion. If the result contains wildcards, they'll be gotten
+ * to later since the resulting words are tacked on to the end of
+ * the children list.
+ */
+ if (strchr(cgn->name, '$') == NULL) {
+ SuffExpandWildcards(cln, pgn);
+ return;
+ }
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "Expanding \"%s\"...", cgn->name);
+ }
+ cp = Var_Subst(NULL, cgn->name, pgn, VARF_UNDEFERR|VARF_WANTRES);
+
+ if (cp != NULL) {
+ Lst members = Lst_Init(FALSE);
+
+ if (cgn->type & OP_ARCHV) {
+ /*
+ * Node was an archive(member) target, so we want to call
+ * on the Arch module to find the nodes for us, expanding
+ * variables in the parent's context.
+ */
+ char *sacrifice = cp;
+
+ (void)Arch_ParseArchive(&sacrifice, members, pgn);
+ } else {
+ /*
+ * Break the result into a vector of strings whose nodes
+ * we can find, then add those nodes to the members list.
+ * Unfortunately, we can't use brk_string b/c it
+ * doesn't understand about variable specifications with
+ * spaces in them...
+ */
+ char *start;
+ char *initcp = cp; /* For freeing... */
+
+ for (start = cp; *start == ' ' || *start == '\t'; start++)
+ continue;
+ for (cp = start; *cp != '\0'; cp++) {
+ if (*cp == ' ' || *cp == '\t') {
+ /*
+ * White-space -- terminate element, find the node,
+ * add it, skip any further spaces.
+ */
+ *cp++ = '\0';
+ gn = Targ_FindNode(start, TARG_CREATE);
+ (void)Lst_AtEnd(members, gn);
+ while (*cp == ' ' || *cp == '\t') {
+ cp++;
+ }
+ /*
+ * Adjust cp for increment at start of loop, but
+ * set start to first non-space.
+ */
+ start = cp--;
+ } else if (*cp == '$') {
+ /*
+ * Start of a variable spec -- contact variable module
+ * to find the end so we can skip over it.
+ */
+ char *junk;
+ int len;
+ void *freeIt;
+
+ junk = Var_Parse(cp, pgn, VARF_UNDEFERR|VARF_WANTRES,
+ &len, &freeIt);
+ if (junk != var_Error) {
+ cp += len - 1;
+ }
+
+ free(freeIt);
+ } else if (*cp == '\\' && cp[1] != '\0') {
+ /*
+ * Escaped something -- skip over it
+ */
+ cp++;
+ }
+ }
+
+ if (cp != start) {
+ /*
+ * Stuff left over -- add it to the list too
+ */
+ gn = Targ_FindNode(start, TARG_CREATE);
+ (void)Lst_AtEnd(members, gn);
+ }
+ /*
+ * Point cp back at the beginning again so the variable value
+ * can be freed.
+ */
+ cp = initcp;
+ }
+
+ /*
+ * Add all elements of the members list to the parent node.
+ */
+ while(!Lst_IsEmpty(members)) {
+ gn = (GNode *)Lst_DeQueue(members);
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "%s...", gn->name);
+ }
+ /* Add gn to the parents child list before the original child */
+ (void)Lst_InsertBefore(pgn->children, cln, gn);
+ (void)Lst_AtEnd(gn->parents, pgn);
+ pgn->unmade++;
+ /* Expand wildcards on new node */
+ SuffExpandWildcards(Lst_Prev(cln), pgn);
+ }
+ Lst_Destroy(members, NULL);
+
+ /*
+ * Free the result
+ */
+ free(cp);
+ }
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "\n");
+ }
+
+ /*
+ * Now the source is expanded, remove it from the list of children to
+ * keep it from being processed.
+ */
+ pgn->unmade--;
+ Lst_Remove(pgn->children, cln);
+ Lst_Remove(cgn->parents, Lst_Member(cgn->parents, pgn));
+}
+
+static void
+SuffExpandWildcards(LstNode cln, GNode *pgn)
+{
+ GNode *cgn = (GNode *)Lst_Datum(cln);
+ GNode *gn; /* New source 8) */
+ char *cp; /* Expanded value */
+ Lst explist; /* List of expansions */
+
+ if (!Dir_HasWildcards(cgn->name))
+ return;
+
+ /*
+ * Expand the word along the chosen path
+ */
+ explist = Lst_Init(FALSE);
+ Dir_Expand(cgn->name, Suff_FindPath(cgn), explist);
+
+ while (!Lst_IsEmpty(explist)) {
+ /*
+ * Fetch next expansion off the list and find its GNode
+ */
+ cp = (char *)Lst_DeQueue(explist);
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "%s...", cp);
+ }
+ gn = Targ_FindNode(cp, TARG_CREATE);
+
+ /* Add gn to the parents child list before the original child */
+ (void)Lst_InsertBefore(pgn->children, cln, gn);
+ (void)Lst_AtEnd(gn->parents, pgn);
+ pgn->unmade++;
+ }
+
+ /*
+ * Nuke what's left of the list
+ */
+ Lst_Destroy(explist, NULL);
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "\n");
+ }
+
+ /*
+ * Now the source is expanded, remove it from the list of children to
+ * keep it from being processed.
+ */
+ pgn->unmade--;
+ Lst_Remove(pgn->children, cln);
+ Lst_Remove(cgn->parents, Lst_Member(cgn->parents, pgn));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_FindPath --
+ * Find a path along which to expand the node.
+ *
+ * If the word has a known suffix, use that path.
+ * If it has no known suffix, use the default system search path.
+ *
+ * Input:
+ * gn Node being examined
+ *
+ * Results:
+ * The appropriate path to search for the GNode.
+ *
+ * Side Effects:
+ * XXX: We could set the suffix here so that we don't have to scan
+ * again.
+ *
+ *-----------------------------------------------------------------------
+ */
+Lst
+Suff_FindPath(GNode* gn)
+{
+ Suff *suff = gn->suffix;
+
+ if (suff == NULL) {
+ SuffixCmpData sd; /* Search string data */
+ LstNode ln;
+ sd.len = strlen(gn->name);
+ sd.ename = gn->name + sd.len;
+ ln = Lst_Find(sufflist, &sd, SuffSuffIsSuffixP);
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "Wildcard expanding \"%s\"...", gn->name);
+ }
+ if (ln != NULL)
+ suff = (Suff *)Lst_Datum(ln);
+ /* XXX: Here we can save the suffix so we don't have to do this again */
+ }
+
+ if (suff != NULL) {
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "suffix is \"%s\"...", suff->name);
+ }
+ return suff->searchPath;
+ } else {
+ /*
+ * Use default search path
+ */
+ return dirSearchPath;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffApplyTransform --
+ * Apply a transformation rule, given the source and target nodes
+ * and suffixes.
+ *
+ * Input:
+ * tGn Target node
+ * sGn Source node
+ * t Target suffix
+ * s Source suffix
+ *
+ * Results:
+ * TRUE if successful, FALSE if not.
+ *
+ * Side Effects:
+ * The source and target are linked and the commands from the
+ * transformation are added to the target node's commands list.
+ * All attributes but OP_DEPMASK and OP_TRANSFORM are applied
+ * to the target. The target also inherits all the sources for
+ * the transformation rule.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+SuffApplyTransform(GNode *tGn, GNode *sGn, Suff *t, Suff *s)
+{
+ LstNode ln, nln; /* General node */
+ char *tname; /* Name of transformation rule */
+ GNode *gn; /* Node for same */
+
+ /*
+ * Form the proper links between the target and source.
+ */
+ (void)Lst_AtEnd(tGn->children, sGn);
+ (void)Lst_AtEnd(sGn->parents, tGn);
+ tGn->unmade += 1;
+
+ /*
+ * Locate the transformation rule itself
+ */
+ tname = str_concat(s->name, t->name, 0);
+ ln = Lst_Find(transforms, tname, SuffGNHasNameP);
+ free(tname);
+
+ if (ln == NULL) {
+ /*
+ * Not really such a transformation rule (can happen when we're
+ * called to link an OP_MEMBER and OP_ARCHV node), so return
+ * FALSE.
+ */
+ return(FALSE);
+ }
+
+ gn = (GNode *)Lst_Datum(ln);
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "\tapplying %s -> %s to \"%s\"\n", s->name, t->name, tGn->name);
+ }
+
+ /*
+ * Record last child for expansion purposes
+ */
+ ln = Lst_Last(tGn->children);
+
+ /*
+ * Pass the buck to Make_HandleUse to apply the rule
+ */
+ (void)Make_HandleUse(gn, tGn);
+
+ /*
+ * Deal with wildcards and variables in any acquired sources
+ */
+ for (ln = Lst_Succ(ln); ln != NULL; ln = nln) {
+ nln = Lst_Succ(ln);
+ SuffExpandChildren(ln, tGn);
+ }
+
+ /*
+ * Keep track of another parent to which this beast is transformed so
+ * the .IMPSRC variable can be set correctly for the parent.
+ */
+ (void)Lst_AtEnd(sGn->iParents, tGn);
+
+ return(TRUE);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFindArchiveDeps --
+ * Locate dependencies for an OP_ARCHV node.
+ *
+ * Input:
+ * gn Node for which to locate dependencies
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Same as Suff_FindDeps
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffFindArchiveDeps(GNode *gn, Lst slst)
+{
+ char *eoarch; /* End of archive portion */
+ char *eoname; /* End of member portion */
+ GNode *mem; /* Node for member */
+ static const char *copy[] = {
+ /* Variables to be copied from the member node */
+ TARGET, /* Must be first */
+ PREFIX, /* Must be second */
+ };
+ LstNode ln, nln; /* Next suffix node to check */
+ int i; /* Index into copy and vals */
+ Suff *ms; /* Suffix descriptor for member */
+ char *name; /* Start of member's name */
+
+ /*
+ * The node is an archive(member) pair. so we must find a
+ * suffix for both of them.
+ */
+ eoarch = strchr(gn->name, '(');
+ eoname = strchr(eoarch, ')');
+
+ /*
+ * Caller guarantees the format `libname(member)', via
+ * Arch_ParseArchive.
+ */
+ assert(eoarch != NULL);
+ assert(eoname != NULL);
+
+ *eoname = '\0'; /* Nuke parentheses during suffix search */
+ *eoarch = '\0'; /* So a suffix can be found */
+
+ name = eoarch + 1;
+
+ /*
+ * To simplify things, call Suff_FindDeps recursively on the member now,
+ * so we can simply compare the member's .PREFIX and .TARGET variables
+ * to locate its suffix. This allows us to figure out the suffix to
+ * use for the archive without having to do a quadratic search over the
+ * suffix list, backtracking for each one...
+ */
+ mem = Targ_FindNode(name, TARG_CREATE);
+ SuffFindDeps(mem, slst);
+
+ /*
+ * Create the link between the two nodes right off
+ */
+ (void)Lst_AtEnd(gn->children, mem);
+ (void)Lst_AtEnd(mem->parents, gn);
+ gn->unmade += 1;
+
+ /*
+ * Copy in the variables from the member node to this one.
+ */
+ for (i = (sizeof(copy)/sizeof(copy[0]))-1; i >= 0; i--) {
+ char *p1;
+ Var_Set(copy[i], Var_Value(copy[i], mem, &p1), gn, 0);
+ free(p1);
+
+ }
+
+ ms = mem->suffix;
+ if (ms == NULL) {
+ /*
+ * Didn't know what it was -- use .NULL suffix if not in make mode
+ */
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "using null suffix\n");
+ }
+ ms = suffNull;
+ }
+
+
+ /*
+ * Set the other two local variables required for this target.
+ */
+ Var_Set(MEMBER, name, gn, 0);
+ Var_Set(ARCHIVE, gn->name, gn, 0);
+
+ /*
+ * Set $@ for compatibility with other makes
+ */
+ Var_Set(TARGET, gn->name, gn, 0);
+
+ /*
+ * Now we've got the important local variables set, expand any sources
+ * that still contain variables or wildcards in their names.
+ */
+ for (ln = Lst_First(gn->children); ln != NULL; ln = nln) {
+ nln = Lst_Succ(ln);
+ SuffExpandChildren(ln, gn);
+ }
+
+ if (ms != NULL) {
+ /*
+ * Member has a known suffix, so look for a transformation rule from
+ * it to a possible suffix of the archive. Rather than searching
+ * through the entire list, we just look at suffixes to which the
+ * member's suffix may be transformed...
+ */
+ SuffixCmpData sd; /* Search string data */
+
+ /*
+ * Use first matching suffix...
+ */
+ sd.len = eoarch - gn->name;
+ sd.ename = eoarch;
+ ln = Lst_Find(ms->parents, &sd, SuffSuffIsSuffixP);
+
+ if (ln != NULL) {
+ /*
+ * Got one -- apply it
+ */
+ if (!SuffApplyTransform(gn, mem, (Suff *)Lst_Datum(ln), ms) &&
+ DEBUG(SUFF))
+ {
+ fprintf(debug_file, "\tNo transformation from %s -> %s\n",
+ ms->name, ((Suff *)Lst_Datum(ln))->name);
+ }
+ }
+ }
+
+ /*
+ * Replace the opening and closing parens now we've no need of the separate
+ * pieces.
+ */
+ *eoarch = '('; *eoname = ')';
+
+ /*
+ * Pretend gn appeared to the left of a dependency operator so
+ * the user needn't provide a transformation from the member to the
+ * archive.
+ */
+ if (OP_NOP(gn->type)) {
+ gn->type |= OP_DEPENDS;
+ }
+
+ /*
+ * Flag the member as such so we remember to look in the archive for
+ * its modification time. The OP_JOIN | OP_MADE is needed because this
+ * target should never get made.
+ */
+ mem->type |= OP_MEMBER | OP_JOIN | OP_MADE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFindNormalDeps --
+ * Locate implicit dependencies for regular targets.
+ *
+ * Input:
+ * gn Node for which to find sources
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * Same as Suff_FindDeps...
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffFindNormalDeps(GNode *gn, Lst slst)
+{
+ char *eoname; /* End of name */
+ char *sopref; /* Start of prefix */
+ LstNode ln, nln; /* Next suffix node to check */
+ Lst srcs; /* List of sources at which to look */
+ Lst targs; /* List of targets to which things can be
+ * transformed. They all have the same file,
+ * but different suff and pref fields */
+ Src *bottom; /* Start of found transformation path */
+ Src *src; /* General Src pointer */
+ char *pref; /* Prefix to use */
+ Src *targ; /* General Src target pointer */
+ SuffixCmpData sd; /* Search string data */
+
+
+ sd.len = strlen(gn->name);
+ sd.ename = eoname = gn->name + sd.len;
+
+ sopref = gn->name;
+
+ /*
+ * Begin at the beginning...
+ */
+ ln = Lst_First(sufflist);
+ srcs = Lst_Init(FALSE);
+ targs = Lst_Init(FALSE);
+
+ /*
+ * We're caught in a catch-22 here. On the one hand, we want to use any
+ * transformation implied by the target's sources, but we can't examine
+ * the sources until we've expanded any variables/wildcards they may hold,
+ * and we can't do that until we've set up the target's local variables
+ * and we can't do that until we know what the proper suffix for the
+ * target is (in case there are two suffixes one of which is a suffix of
+ * the other) and we can't know that until we've found its implied
+ * source, which we may not want to use if there's an existing source
+ * that implies a different transformation.
+ *
+ * In an attempt to get around this, which may not work all the time,
+ * but should work most of the time, we look for implied sources first,
+ * checking transformations to all possible suffixes of the target,
+ * use what we find to set the target's local variables, expand the
+ * children, then look for any overriding transformations they imply.
+ * Should we find one, we discard the one we found before.
+ */
+ bottom = NULL;
+ targ = NULL;
+
+ if (!(gn->type & OP_PHONY)) {
+
+ while (ln != NULL) {
+ /*
+ * Look for next possible suffix...
+ */
+ ln = Lst_FindFrom(sufflist, ln, &sd, SuffSuffIsSuffixP);
+
+ if (ln != NULL) {
+ int prefLen; /* Length of the prefix */
+
+ /*
+ * Allocate a Src structure to which things can be transformed
+ */
+ targ = bmake_malloc(sizeof(Src));
+ targ->file = bmake_strdup(gn->name);
+ targ->suff = (Suff *)Lst_Datum(ln);
+ targ->suff->refCount++;
+ targ->node = gn;
+ targ->parent = NULL;
+ targ->children = 0;
+#ifdef DEBUG_SRC
+ targ->cp = Lst_Init(FALSE);
+#endif
+
+ /*
+ * Allocate room for the prefix, whose end is found by
+ * subtracting the length of the suffix from
+ * the end of the name.
+ */
+ prefLen = (eoname - targ->suff->nameLen) - sopref;
+ targ->pref = bmake_malloc(prefLen + 1);
+ memcpy(targ->pref, sopref, prefLen);
+ targ->pref[prefLen] = '\0';
+
+ /*
+ * Add nodes from which the target can be made
+ */
+ SuffAddLevel(srcs, targ);
+
+ /*
+ * Record the target so we can nuke it
+ */
+ (void)Lst_AtEnd(targs, targ);
+
+ /*
+ * Search from this suffix's successor...
+ */
+ ln = Lst_Succ(ln);
+ }
+ }
+
+ /*
+ * Handle target of unknown suffix...
+ */
+ if (Lst_IsEmpty(targs) && suffNull != NULL) {
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "\tNo known suffix on %s. Using .NULL suffix\n", gn->name);
+ }
+
+ targ = bmake_malloc(sizeof(Src));
+ targ->file = bmake_strdup(gn->name);
+ targ->suff = suffNull;
+ targ->suff->refCount++;
+ targ->node = gn;
+ targ->parent = NULL;
+ targ->children = 0;
+ targ->pref = bmake_strdup(sopref);
+#ifdef DEBUG_SRC
+ targ->cp = Lst_Init(FALSE);
+#endif
+
+ /*
+ * Only use the default suffix rules if we don't have commands
+ * defined for this gnode; traditional make programs used to
+ * not define suffix rules if the gnode had children but we
+ * don't do this anymore.
+ */
+ if (Lst_IsEmpty(gn->commands))
+ SuffAddLevel(srcs, targ);
+ else {
+ if (DEBUG(SUFF))
+ fprintf(debug_file, "not ");
+ }
+
+ if (DEBUG(SUFF))
+ fprintf(debug_file, "adding suffix rules\n");
+
+ (void)Lst_AtEnd(targs, targ);
+ }
+
+ /*
+ * Using the list of possible sources built up from the target
+ * suffix(es), try and find an existing file/target that matches.
+ */
+ bottom = SuffFindThem(srcs, slst);
+
+ if (bottom == NULL) {
+ /*
+ * No known transformations -- use the first suffix found
+ * for setting the local variables.
+ */
+ if (!Lst_IsEmpty(targs)) {
+ targ = (Src *)Lst_Datum(Lst_First(targs));
+ } else {
+ targ = NULL;
+ }
+ } else {
+ /*
+ * Work up the transformation path to find the suffix of the
+ * target to which the transformation was made.
+ */
+ for (targ = bottom; targ->parent != NULL; targ = targ->parent)
+ continue;
+ }
+ }
+
+ Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0);
+
+ pref = (targ != NULL) ? targ->pref : gn->name;
+ Var_Set(PREFIX, pref, gn, 0);
+
+ /*
+ * Now we've got the important local variables set, expand any sources
+ * that still contain variables or wildcards in their names.
+ */
+ for (ln = Lst_First(gn->children); ln != NULL; ln = nln) {
+ nln = Lst_Succ(ln);
+ SuffExpandChildren(ln, gn);
+ }
+
+ if (targ == NULL) {
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "\tNo valid suffix on %s\n", gn->name);
+ }
+
+sfnd_abort:
+ /*
+ * Deal with finding the thing on the default search path. We
+ * always do that, not only if the node is only a source (not
+ * on the lhs of a dependency operator or [XXX] it has neither
+ * children or commands) as the old pmake did.
+ */
+ if ((gn->type & (OP_PHONY|OP_NOPATH)) == 0) {
+ free(gn->path);
+ gn->path = Dir_FindFile(gn->name,
+ (targ == NULL ? dirSearchPath :
+ targ->suff->searchPath));
+ if (gn->path != NULL) {
+ char *ptr;
+ Var_Set(TARGET, gn->path, gn, 0);
+
+ if (targ != NULL) {
+ /*
+ * Suffix known for the thing -- trim the suffix off
+ * the path to form the proper .PREFIX variable.
+ */
+ int savep = strlen(gn->path) - targ->suff->nameLen;
+ char savec;
+
+ if (gn->suffix)
+ gn->suffix->refCount--;
+ gn->suffix = targ->suff;
+ gn->suffix->refCount++;
+
+ savec = gn->path[savep];
+ gn->path[savep] = '\0';
+
+ if ((ptr = strrchr(gn->path, '/')) != NULL)
+ ptr++;
+ else
+ ptr = gn->path;
+
+ Var_Set(PREFIX, ptr, gn, 0);
+
+ gn->path[savep] = savec;
+ } else {
+ /*
+ * The .PREFIX gets the full path if the target has
+ * no known suffix.
+ */
+ if (gn->suffix)
+ gn->suffix->refCount--;
+ gn->suffix = NULL;
+
+ if ((ptr = strrchr(gn->path, '/')) != NULL)
+ ptr++;
+ else
+ ptr = gn->path;
+
+ Var_Set(PREFIX, ptr, gn, 0);
+ }
+ }
+ }
+
+ goto sfnd_return;
+ }
+
+ /*
+ * If the suffix indicates that the target is a library, mark that in
+ * the node's type field.
+ */
+ if (targ->suff->flags & SUFF_LIBRARY) {
+ gn->type |= OP_LIB;
+ }
+
+ /*
+ * Check for overriding transformation rule implied by sources
+ */
+ if (!Lst_IsEmpty(gn->children)) {
+ src = SuffFindCmds(targ, slst);
+
+ if (src != NULL) {
+ /*
+ * Free up all the Src structures in the transformation path
+ * up to, but not including, the parent node.
+ */
+ while (bottom && bottom->parent != NULL) {
+ if (Lst_Member(slst, bottom) == NULL) {
+ Lst_AtEnd(slst, bottom);
+ }
+ bottom = bottom->parent;
+ }
+ bottom = src;
+ }
+ }
+
+ if (bottom == NULL) {
+ /*
+ * No idea from where it can come -- return now.
+ */
+ goto sfnd_abort;
+ }
+
+ /*
+ * We now have a list of Src structures headed by 'bottom' and linked via
+ * their 'parent' pointers. What we do next is create links between
+ * source and target nodes (which may or may not have been created)
+ * and set the necessary local variables in each target. The
+ * commands for each target are set from the commands of the
+ * transformation rule used to get from the src suffix to the targ
+ * suffix. Note that this causes the commands list of the original
+ * node, gn, to be replaced by the commands of the final
+ * transformation rule. Also, the unmade field of gn is incremented.
+ * Etc.
+ */
+ if (bottom->node == NULL) {
+ bottom->node = Targ_FindNode(bottom->file, TARG_CREATE);
+ }
+
+ for (src = bottom; src->parent != NULL; src = src->parent) {
+ targ = src->parent;
+
+ if (src->node->suffix)
+ src->node->suffix->refCount--;
+ src->node->suffix = src->suff;
+ src->node->suffix->refCount++;
+
+ if (targ->node == NULL) {
+ targ->node = Targ_FindNode(targ->file, TARG_CREATE);
+ }
+
+ SuffApplyTransform(targ->node, src->node,
+ targ->suff, src->suff);
+
+ if (targ->node != gn) {
+ /*
+ * Finish off the dependency-search process for any nodes
+ * between bottom and gn (no point in questing around the
+ * filesystem for their implicit source when it's already
+ * known). Note that the node can't have any sources that
+ * need expanding, since SuffFindThem will stop on an existing
+ * node, so all we need to do is set the standard and System V
+ * variables.
+ */
+ targ->node->type |= OP_DEPS_FOUND;
+
+ Var_Set(PREFIX, targ->pref, targ->node, 0);
+
+ Var_Set(TARGET, targ->node->name, targ->node, 0);
+ }
+ }
+
+ if (gn->suffix)
+ gn->suffix->refCount--;
+ gn->suffix = src->suff;
+ gn->suffix->refCount++;
+
+ /*
+ * Nuke the transformation path and the Src structures left over in the
+ * two lists.
+ */
+sfnd_return:
+ if (bottom)
+ if (Lst_Member(slst, bottom) == NULL)
+ Lst_AtEnd(slst, bottom);
+
+ while (SuffRemoveSrc(srcs) || SuffRemoveSrc(targs))
+ continue;
+
+ Lst_Concat(slst, srcs, LST_CONCLINK);
+ Lst_Concat(slst, targs, LST_CONCLINK);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_FindDeps --
+ * Find implicit sources for the target described by the graph node
+ * gn
+ *
+ * Results:
+ * Nothing.
+ *
+ * Side Effects:
+ * Nodes are added to the graph below the passed-in node. The nodes
+ * are marked to have their IMPSRC variable filled in. The
+ * PREFIX variable is set for the given node and all its
+ * implied children.
+ *
+ * Notes:
+ * The path found by this target is the shortest path in the
+ * transformation graph, which may pass through non-existent targets,
+ * to an existing target. The search continues on all paths from the
+ * root suffix until a file is found. I.e. if there's a path
+ * .o -> .c -> .l -> .l,v from the root and the .l,v file exists but
+ * the .c and .l files don't, the search will branch out in
+ * all directions from .o and again from all the nodes on the
+ * next level until the .l,v node is encountered.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+void
+Suff_FindDeps(GNode *gn)
+{
+
+ SuffFindDeps(gn, srclist);
+ while (SuffRemoveSrc(srclist))
+ continue;
+}
+
+
+/*
+ * Input:
+ * gn node we're dealing with
+ *
+ */
+static void
+SuffFindDeps(GNode *gn, Lst slst)
+{
+ if (gn->type & OP_DEPS_FOUND) {
+ /*
+ * If dependencies already found, no need to do it again...
+ */
+ return;
+ } else {
+ gn->type |= OP_DEPS_FOUND;
+ }
+ /*
+ * Make sure we have these set, may get revised below.
+ */
+ Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0);
+ Var_Set(PREFIX, gn->name, gn, 0);
+
+ if (DEBUG(SUFF)) {
+ fprintf(debug_file, "SuffFindDeps (%s)\n", gn->name);
+ }
+
+ if (gn->type & OP_ARCHV) {
+ SuffFindArchiveDeps(gn, slst);
+ } else if (gn->type & OP_LIB) {
+ /*
+ * If the node is a library, it is the arch module's job to find it
+ * and set the TARGET variable accordingly. We merely provide the
+ * search path, assuming all libraries end in ".a" (if the suffix
+ * hasn't been defined, there's nothing we can do for it, so we just
+ * set the TARGET variable to the node's name in order to give it a
+ * value).
+ */
+ LstNode ln;
+ Suff *s;
+
+ ln = Lst_Find(sufflist, LIBSUFF, SuffSuffHasNameP);
+ if (gn->suffix)
+ gn->suffix->refCount--;
+ if (ln != NULL) {
+ gn->suffix = s = (Suff *)Lst_Datum(ln);
+ gn->suffix->refCount++;
+ Arch_FindLib(gn, s->searchPath);
+ } else {
+ gn->suffix = NULL;
+ Var_Set(TARGET, gn->name, gn, 0);
+ }
+ /*
+ * Because a library (-lfoo) target doesn't follow the standard
+ * filesystem conventions, we don't set the regular variables for
+ * the thing. .PREFIX is simply made empty...
+ */
+ Var_Set(PREFIX, "", gn, 0);
+ } else {
+ SuffFindNormalDeps(gn, slst);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_SetNull --
+ * Define which suffix is the null suffix.
+ *
+ * Input:
+ * name Name of null suffix
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * 'suffNull' is altered.
+ *
+ * Notes:
+ * Need to handle the changing of the null suffix gracefully so the
+ * old transformation rules don't just go away.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_SetNull(char *name)
+{
+ Suff *s;
+ LstNode ln;
+
+ ln = Lst_Find(sufflist, name, SuffSuffHasNameP);
+ if (ln != NULL) {
+ s = (Suff *)Lst_Datum(ln);
+ if (suffNull != NULL) {
+ suffNull->flags &= ~SUFF_NULL;
+ }
+ s->flags |= SUFF_NULL;
+ /*
+ * XXX: Here's where the transformation mangling would take place
+ */
+ suffNull = s;
+ } else {
+ Parse_Error(PARSE_WARNING, "Desired null suffix %s not defined.",
+ name);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_Init --
+ * Initialize suffixes module
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * Many
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_Init(void)
+{
+#ifdef CLEANUP
+ suffClean = Lst_Init(FALSE);
+#endif
+ srclist = Lst_Init(FALSE);
+ transforms = Lst_Init(FALSE);
+
+ /*
+ * Create null suffix for single-suffix rules (POSIX). The thing doesn't
+ * actually go on the suffix list or everyone will think that's its
+ * suffix.
+ */
+ Suff_ClearSuffixes();
+}
+
+
+/*-
+ *----------------------------------------------------------------------
+ * Suff_End --
+ * Cleanup the this module
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The memory is free'd.
+ *----------------------------------------------------------------------
+ */
+
+void
+Suff_End(void)
+{
+#ifdef CLEANUP
+ Lst_Destroy(sufflist, SuffFree);
+ Lst_Destroy(suffClean, SuffFree);
+ if (suffNull)
+ SuffFree(suffNull);
+ Lst_Destroy(srclist, NULL);
+ Lst_Destroy(transforms, NULL);
+#endif
+}
+
+
+/********************* DEBUGGING FUNCTIONS **********************/
+
+static int SuffPrintName(void *s, void *dummy MAKE_ATTR_UNUSED)
+{
+
+ fprintf(debug_file, "%s ", ((Suff *)s)->name);
+ return 0;
+}
+
+static int
+SuffPrintSuff(void *sp, void *dummy MAKE_ATTR_UNUSED)
+{
+ Suff *s = (Suff *)sp;
+ int flags;
+ int flag;
+
+ fprintf(debug_file, "# `%s' [%d] ", s->name, s->refCount);
+
+ flags = s->flags;
+ if (flags) {
+ fputs(" (", debug_file);
+ while (flags) {
+ flag = 1 << (ffs(flags) - 1);
+ flags &= ~flag;
+ switch (flag) {
+ case SUFF_NULL:
+ fprintf(debug_file, "NULL");
+ break;
+ case SUFF_INCLUDE:
+ fprintf(debug_file, "INCLUDE");
+ break;
+ case SUFF_LIBRARY:
+ fprintf(debug_file, "LIBRARY");
+ break;
+ }
+ fputc(flags ? '|' : ')', debug_file);
+ }
+ }
+ fputc('\n', debug_file);
+ fprintf(debug_file, "#\tTo: ");
+ Lst_ForEach(s->parents, SuffPrintName, NULL);
+ fputc('\n', debug_file);
+ fprintf(debug_file, "#\tFrom: ");
+ Lst_ForEach(s->children, SuffPrintName, NULL);
+ fputc('\n', debug_file);
+ fprintf(debug_file, "#\tSearch Path: ");
+ Dir_PrintPath(s->searchPath);
+ fputc('\n', debug_file);
+ return 0;
+}
+
+static int
+SuffPrintTrans(void *tp, void *dummy MAKE_ATTR_UNUSED)
+{
+ GNode *t = (GNode *)tp;
+
+ fprintf(debug_file, "%-16s: ", t->name);
+ Targ_PrintType(t->type);
+ fputc('\n', debug_file);
+ Lst_ForEach(t->commands, Targ_PrintCmd, NULL);
+ fputc('\n', debug_file);
+ return 0;
+}
+
+void
+Suff_PrintAll(void)
+{
+ fprintf(debug_file, "#*** Suffixes:\n");
+ Lst_ForEach(sufflist, SuffPrintSuff, NULL);
+
+ fprintf(debug_file, "#*** Transformations:\n");
+ Lst_ForEach(transforms, SuffPrintTrans, NULL);
+}
diff --git a/usr.bin/make/targ.c b/usr.bin/make/targ.c
new file mode 100644
index 0000000..01c3d1c
--- /dev/null
+++ b/usr.bin/make/targ.c
@@ -0,0 +1,846 @@
+/* $NetBSD: targ.c,v 1.62 2017/04/16 19:53:58 riastradh Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: targ.c,v 1.62 2017/04/16 19:53:58 riastradh Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)targ.c 8.2 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: targ.c,v 1.62 2017/04/16 19:53:58 riastradh Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * targ.c --
+ * Functions for maintaining the Lst allTargets. Target nodes are
+ * kept in two structures: a Lst, maintained by the list library, and a
+ * hash table, maintained by the hash library.
+ *
+ * Interface:
+ * Targ_Init Initialization procedure.
+ *
+ * Targ_End Cleanup the module
+ *
+ * Targ_List Return the list of all targets so far.
+ *
+ * Targ_NewGN Create a new GNode for the passed target
+ * (string). The node is *not* placed in the
+ * hash table, though all its fields are
+ * initialized.
+ *
+ * Targ_FindNode Find the node for a given target, creating
+ * and storing it if it doesn't exist and the
+ * flags are right (TARG_CREATE)
+ *
+ * Targ_FindList Given a list of names, find nodes for all
+ * of them. If a name doesn't exist and the
+ * TARG_NOCREATE flag was given, an error message
+ * is printed. Else, if a name doesn't exist,
+ * its node is created.
+ *
+ * Targ_Ignore Return TRUE if errors should be ignored when
+ * creating the given target.
+ *
+ * Targ_Silent Return TRUE if we should be silent when
+ * creating the given target.
+ *
+ * Targ_Precious Return TRUE if the target is precious and
+ * should not be removed if we are interrupted.
+ *
+ * Targ_Propagate Propagate information between related
+ * nodes. Should be called after the
+ * makefiles are parsed but before any
+ * action is taken.
+ *
+ * Debugging:
+ * Targ_PrintGraph Print out the entire graphm all variables
+ * and statistics for the directory cache. Should
+ * print something for suffixes, too, but...
+ */
+
+#include <stdio.h>
+#include <time.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+
+static Lst allTargets; /* the list of all targets found so far */
+#ifdef CLEANUP
+static Lst allGNs; /* List of all the GNodes */
+#endif
+static Hash_Table targets; /* a hash table of same */
+
+#define HTSIZE 191 /* initial size of hash table */
+
+static int TargPrintOnlySrc(void *, void *);
+static int TargPrintName(void *, void *);
+#ifdef CLEANUP
+static void TargFreeGN(void *);
+#endif
+static int TargPropagateCohort(void *, void *);
+static int TargPropagateNode(void *, void *);
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Init --
+ * Initialize this module
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The allTargets list and the targets hash table are initialized
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_Init(void)
+{
+ allTargets = Lst_Init(FALSE);
+ Hash_InitTable(&targets, HTSIZE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_End --
+ * Finalize this module
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * All lists and gnodes are cleared
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_End(void)
+{
+#ifdef CLEANUP
+ Lst_Destroy(allTargets, NULL);
+ if (allGNs)
+ Lst_Destroy(allGNs, TargFreeGN);
+ Hash_DeleteTable(&targets);
+#endif
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_List --
+ * Return the list of all targets
+ *
+ * Results:
+ * The list of all targets.
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+Lst
+Targ_List(void)
+{
+ return allTargets;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_NewGN --
+ * Create and initialize a new graph node
+ *
+ * Input:
+ * name the name to stick in the new node
+ *
+ * Results:
+ * An initialized graph node with the name field filled with a copy
+ * of the passed name
+ *
+ * Side Effects:
+ * The gnode is added to the list of all gnodes.
+ *-----------------------------------------------------------------------
+ */
+GNode *
+Targ_NewGN(const char *name)
+{
+ GNode *gn;
+
+ gn = bmake_malloc(sizeof(GNode));
+ gn->name = bmake_strdup(name);
+ gn->uname = NULL;
+ gn->path = NULL;
+ if (name[0] == '-' && name[1] == 'l') {
+ gn->type = OP_LIB;
+ } else {
+ gn->type = 0;
+ }
+ gn->unmade = 0;
+ gn->unmade_cohorts = 0;
+ gn->cohort_num[0] = 0;
+ gn->centurion = NULL;
+ gn->made = UNMADE;
+ gn->flags = 0;
+ gn->checked = 0;
+ gn->mtime = 0;
+ gn->cmgn = NULL;
+ gn->iParents = Lst_Init(FALSE);
+ gn->cohorts = Lst_Init(FALSE);
+ gn->parents = Lst_Init(FALSE);
+ gn->children = Lst_Init(FALSE);
+ gn->order_pred = Lst_Init(FALSE);
+ gn->order_succ = Lst_Init(FALSE);
+ Hash_InitTable(&gn->context, 0);
+ gn->commands = Lst_Init(FALSE);
+ gn->suffix = NULL;
+ gn->lineno = 0;
+ gn->fname = NULL;
+
+#ifdef CLEANUP
+ if (allGNs == NULL)
+ allGNs = Lst_Init(FALSE);
+ Lst_AtEnd(allGNs, gn);
+#endif
+
+ return (gn);
+}
+
+#ifdef CLEANUP
+/*-
+ *-----------------------------------------------------------------------
+ * TargFreeGN --
+ * Destroy a GNode
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * None.
+ *-----------------------------------------------------------------------
+ */
+static void
+TargFreeGN(void *gnp)
+{
+ GNode *gn = (GNode *)gnp;
+
+
+ free(gn->name);
+ free(gn->uname);
+ free(gn->path);
+ /* gn->fname points to name allocated when file was opened, don't free */
+
+ Lst_Destroy(gn->iParents, NULL);
+ Lst_Destroy(gn->cohorts, NULL);
+ Lst_Destroy(gn->parents, NULL);
+ Lst_Destroy(gn->children, NULL);
+ Lst_Destroy(gn->order_succ, NULL);
+ Lst_Destroy(gn->order_pred, NULL);
+ Hash_DeleteTable(&gn->context);
+ Lst_Destroy(gn->commands, NULL);
+ free(gn);
+}
+#endif
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_FindNode --
+ * Find a node in the list using the given name for matching
+ *
+ * Input:
+ * name the name to find
+ * flags flags governing events when target not
+ * found
+ *
+ * Results:
+ * The node in the list if it was. If it wasn't, return NULL of
+ * flags was TARG_NOCREATE or the newly created and initialized node
+ * if it was TARG_CREATE
+ *
+ * Side Effects:
+ * Sometimes a node is created and added to the list
+ *-----------------------------------------------------------------------
+ */
+GNode *
+Targ_FindNode(const char *name, int flags)
+{
+ GNode *gn; /* node in that element */
+ Hash_Entry *he = NULL; /* New or used hash entry for node */
+ Boolean isNew; /* Set TRUE if Hash_CreateEntry had to create */
+ /* an entry for the node */
+
+ if (!(flags & (TARG_CREATE | TARG_NOHASH))) {
+ he = Hash_FindEntry(&targets, name);
+ if (he == NULL)
+ return NULL;
+ return (GNode *)Hash_GetValue(he);
+ }
+
+ if (!(flags & TARG_NOHASH)) {
+ he = Hash_CreateEntry(&targets, name, &isNew);
+ if (!isNew)
+ return (GNode *)Hash_GetValue(he);
+ }
+
+ gn = Targ_NewGN(name);
+ if (!(flags & TARG_NOHASH))
+ Hash_SetValue(he, gn);
+ Var_Append(".ALLTARGETS", name, VAR_GLOBAL);
+ (void)Lst_AtEnd(allTargets, gn);
+ if (doing_depend)
+ gn->flags |= FROM_DEPEND;
+ return gn;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_FindList --
+ * Make a complete list of GNodes from the given list of names
+ *
+ * Input:
+ * name list of names to find
+ * flags flags used if no node is found for a given name
+ *
+ * Results:
+ * A complete list of graph nodes corresponding to all instances of all
+ * the names in names.
+ *
+ * Side Effects:
+ * If flags is TARG_CREATE, nodes will be created for all names in
+ * names which do not yet have graph nodes. If flags is TARG_NOCREATE,
+ * an error message will be printed for each name which can't be found.
+ * -----------------------------------------------------------------------
+ */
+Lst
+Targ_FindList(Lst names, int flags)
+{
+ Lst nodes; /* result list */
+ LstNode ln; /* name list element */
+ GNode *gn; /* node in tLn */
+ char *name;
+
+ nodes = Lst_Init(FALSE);
+
+ if (Lst_Open(names) == FAILURE) {
+ return (nodes);
+ }
+ while ((ln = Lst_Next(names)) != NULL) {
+ name = (char *)Lst_Datum(ln);
+ gn = Targ_FindNode(name, flags);
+ if (gn != NULL) {
+ /*
+ * Note: Lst_AtEnd must come before the Lst_Concat so the nodes
+ * are added to the list in the order in which they were
+ * encountered in the makefile.
+ */
+ (void)Lst_AtEnd(nodes, gn);
+ } else if (flags == TARG_NOCREATE) {
+ Error("\"%s\" -- target unknown.", name);
+ }
+ }
+ Lst_Close(names);
+ return (nodes);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Ignore --
+ * Return true if should ignore errors when creating gn
+ *
+ * Input:
+ * gn node to check for
+ *
+ * Results:
+ * TRUE if should ignore errors
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Targ_Ignore(GNode *gn)
+{
+ if (ignoreErrors || gn->type & OP_IGNORE) {
+ return (TRUE);
+ } else {
+ return (FALSE);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Silent --
+ * Return true if be silent when creating gn
+ *
+ * Input:
+ * gn node to check for
+ *
+ * Results:
+ * TRUE if should be silent
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Targ_Silent(GNode *gn)
+{
+ if (beSilent || gn->type & OP_SILENT) {
+ return (TRUE);
+ } else {
+ return (FALSE);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Precious --
+ * See if the given target is precious
+ *
+ * Input:
+ * gn the node to check
+ *
+ * Results:
+ * TRUE if it is precious. FALSE otherwise
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Targ_Precious(GNode *gn)
+{
+ if (allPrecious || (gn->type & (OP_PRECIOUS|OP_DOUBLEDEP))) {
+ return (TRUE);
+ } else {
+ return (FALSE);
+ }
+}
+
+/******************* DEBUG INFO PRINTING ****************/
+
+static GNode *mainTarg; /* the main target, as set by Targ_SetMain */
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_SetMain --
+ * Set our idea of the main target we'll be creating. Used for
+ * debugging output.
+ *
+ * Input:
+ * gn The main target we'll create
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * "mainTarg" is set to the main target's node.
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_SetMain(GNode *gn)
+{
+ mainTarg = gn;
+}
+
+static int
+TargPrintName(void *gnp, void *pflags MAKE_ATTR_UNUSED)
+{
+ GNode *gn = (GNode *)gnp;
+
+ fprintf(debug_file, "%s%s ", gn->name, gn->cohort_num);
+
+ return 0;
+}
+
+
+int
+Targ_PrintCmd(void *cmd, void *dummy MAKE_ATTR_UNUSED)
+{
+ fprintf(debug_file, "\t%s\n", (char *)cmd);
+ return 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_FmtTime --
+ * Format a modification time in some reasonable way and return it.
+ *
+ * Results:
+ * The time reformatted.
+ *
+ * Side Effects:
+ * The time is placed in a static area, so it is overwritten
+ * with each call.
+ *
+ *-----------------------------------------------------------------------
+ */
+char *
+Targ_FmtTime(time_t tm)
+{
+ struct tm *parts;
+ static char buf[128];
+
+ parts = localtime(&tm);
+ (void)strftime(buf, sizeof buf, "%k:%M:%S %b %d, %Y", parts);
+ return(buf);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_PrintType --
+ * Print out a type field giving only those attributes the user can
+ * set.
+ *
+ * Results:
+ *
+ * Side Effects:
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_PrintType(int type)
+{
+ int tbit;
+
+#define PRINTBIT(attr) case CONCAT(OP_,attr): fprintf(debug_file, "." #attr " "); break
+#define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG))fprintf(debug_file, "." #attr " "); break
+
+ type &= ~OP_OPMASK;
+
+ while (type) {
+ tbit = 1 << (ffs(type) - 1);
+ type &= ~tbit;
+
+ switch(tbit) {
+ PRINTBIT(OPTIONAL);
+ PRINTBIT(USE);
+ PRINTBIT(EXEC);
+ PRINTBIT(IGNORE);
+ PRINTBIT(PRECIOUS);
+ PRINTBIT(SILENT);
+ PRINTBIT(MAKE);
+ PRINTBIT(JOIN);
+ PRINTBIT(INVISIBLE);
+ PRINTBIT(NOTMAIN);
+ PRINTDBIT(LIB);
+ /*XXX: MEMBER is defined, so CONCAT(OP_,MEMBER) gives OP_"%" */
+ case OP_MEMBER: if (DEBUG(TARG))fprintf(debug_file, ".MEMBER "); break;
+ PRINTDBIT(ARCHV);
+ PRINTDBIT(MADE);
+ PRINTDBIT(PHONY);
+ }
+ }
+}
+
+static const char *
+made_name(enum enum_made made)
+{
+ switch (made) {
+ case UNMADE: return "unmade";
+ case DEFERRED: return "deferred";
+ case REQUESTED: return "requested";
+ case BEINGMADE: return "being made";
+ case MADE: return "made";
+ case UPTODATE: return "up-to-date";
+ case ERROR: return "error when made";
+ case ABORTED: return "aborted";
+ default: return "unknown enum_made value";
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPrintNode --
+ * print the contents of a node
+ *-----------------------------------------------------------------------
+ */
+int
+Targ_PrintNode(void *gnp, void *passp)
+{
+ GNode *gn = (GNode *)gnp;
+ int pass = passp ? *(int *)passp : 0;
+
+ fprintf(debug_file, "# %s%s, flags %x, type %x, made %d\n",
+ gn->name, gn->cohort_num, gn->flags, gn->type, gn->made);
+ if (gn->flags == 0)
+ return 0;
+
+ if (!OP_NOP(gn->type)) {
+ fprintf(debug_file, "#\n");
+ if (gn == mainTarg) {
+ fprintf(debug_file, "# *** MAIN TARGET ***\n");
+ }
+ if (pass >= 2) {
+ if (gn->unmade) {
+ fprintf(debug_file, "# %d unmade children\n", gn->unmade);
+ } else {
+ fprintf(debug_file, "# No unmade children\n");
+ }
+ if (! (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) {
+ if (gn->mtime != 0) {
+ fprintf(debug_file, "# last modified %s: %s\n",
+ Targ_FmtTime(gn->mtime),
+ made_name(gn->made));
+ } else if (gn->made != UNMADE) {
+ fprintf(debug_file, "# non-existent (maybe): %s\n",
+ made_name(gn->made));
+ } else {
+ fprintf(debug_file, "# unmade\n");
+ }
+ }
+ if (!Lst_IsEmpty (gn->iParents)) {
+ fprintf(debug_file, "# implicit parents: ");
+ Lst_ForEach(gn->iParents, TargPrintName, NULL);
+ fprintf(debug_file, "\n");
+ }
+ } else {
+ if (gn->unmade)
+ fprintf(debug_file, "# %d unmade children\n", gn->unmade);
+ }
+ if (!Lst_IsEmpty (gn->parents)) {
+ fprintf(debug_file, "# parents: ");
+ Lst_ForEach(gn->parents, TargPrintName, NULL);
+ fprintf(debug_file, "\n");
+ }
+ if (!Lst_IsEmpty (gn->order_pred)) {
+ fprintf(debug_file, "# order_pred: ");
+ Lst_ForEach(gn->order_pred, TargPrintName, NULL);
+ fprintf(debug_file, "\n");
+ }
+ if (!Lst_IsEmpty (gn->order_succ)) {
+ fprintf(debug_file, "# order_succ: ");
+ Lst_ForEach(gn->order_succ, TargPrintName, NULL);
+ fprintf(debug_file, "\n");
+ }
+
+ fprintf(debug_file, "%-16s", gn->name);
+ switch (gn->type & OP_OPMASK) {
+ case OP_DEPENDS:
+ fprintf(debug_file, ": "); break;
+ case OP_FORCE:
+ fprintf(debug_file, "! "); break;
+ case OP_DOUBLEDEP:
+ fprintf(debug_file, ":: "); break;
+ }
+ Targ_PrintType(gn->type);
+ Lst_ForEach(gn->children, TargPrintName, NULL);
+ fprintf(debug_file, "\n");
+ Lst_ForEach(gn->commands, Targ_PrintCmd, NULL);
+ fprintf(debug_file, "\n\n");
+ if (gn->type & OP_DOUBLEDEP) {
+ Lst_ForEach(gn->cohorts, Targ_PrintNode, &pass);
+ }
+ }
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPrintOnlySrc --
+ * Print only those targets that are just a source.
+ *
+ * Results:
+ * 0.
+ *
+ * Side Effects:
+ * The name of each file is printed preceded by #\t
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+TargPrintOnlySrc(void *gnp, void *dummy MAKE_ATTR_UNUSED)
+{
+ GNode *gn = (GNode *)gnp;
+ if (!OP_NOP(gn->type))
+ return 0;
+
+ fprintf(debug_file, "#\t%s [%s] ",
+ gn->name, gn->path ? gn->path : gn->name);
+ Targ_PrintType(gn->type);
+ fprintf(debug_file, "\n");
+
+ return 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_PrintGraph --
+ * print the entire graph. heh heh
+ *
+ * Input:
+ * pass Which pass this is. 1 => no processing
+ * 2 => processing done
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * lots o' output
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_PrintGraph(int pass)
+{
+ fprintf(debug_file, "#*** Input graph:\n");
+ Lst_ForEach(allTargets, Targ_PrintNode, &pass);
+ fprintf(debug_file, "\n\n");
+ fprintf(debug_file, "#\n# Files that are only sources:\n");
+ Lst_ForEach(allTargets, TargPrintOnlySrc, NULL);
+ fprintf(debug_file, "#*** Global Variables:\n");
+ Var_Dump(VAR_GLOBAL);
+ fprintf(debug_file, "#*** Command-line Variables:\n");
+ Var_Dump(VAR_CMD);
+ fprintf(debug_file, "\n");
+ Dir_PrintDirectories();
+ fprintf(debug_file, "\n");
+ Suff_PrintAll();
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPropagateNode --
+ * Propagate information from a single node to related nodes if
+ * appropriate.
+ *
+ * Input:
+ * gnp The node that we are processing.
+ *
+ * Results:
+ * Always returns 0, for the benefit of Lst_ForEach().
+ *
+ * Side Effects:
+ * Information is propagated from this node to cohort or child
+ * nodes.
+ *
+ * If the node was defined with "::", then TargPropagateCohort()
+ * will be called for each cohort node.
+ *
+ * If the node has recursive predecessors, then
+ * TargPropagateRecpred() will be called for each recursive
+ * predecessor.
+ *-----------------------------------------------------------------------
+ */
+static int
+TargPropagateNode(void *gnp, void *junk MAKE_ATTR_UNUSED)
+{
+ GNode *gn = (GNode *)gnp;
+
+ if (gn->type & OP_DOUBLEDEP)
+ Lst_ForEach(gn->cohorts, TargPropagateCohort, gnp);
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPropagateCohort --
+ * Propagate some bits in the type mask from a node to
+ * a related cohort node.
+ *
+ * Input:
+ * cnp The node that we are processing.
+ * gnp Another node that has cnp as a cohort.
+ *
+ * Results:
+ * Always returns 0, for the benefit of Lst_ForEach().
+ *
+ * Side Effects:
+ * cnp's type bitmask is modified to incorporate some of the
+ * bits from gnp's type bitmask. (XXX need a better explanation.)
+ *-----------------------------------------------------------------------
+ */
+static int
+TargPropagateCohort(void *cgnp, void *pgnp)
+{
+ GNode *cgn = (GNode *)cgnp;
+ GNode *pgn = (GNode *)pgnp;
+
+ cgn->type |= pgn->type & ~OP_OPMASK;
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Propagate --
+ * Propagate information between related nodes. Should be called
+ * after the makefiles are parsed but before any action is taken.
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * Information is propagated between related nodes throughout the
+ * graph.
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_Propagate(void)
+{
+ Lst_ForEach(allTargets, TargPropagateNode, NULL);
+}
diff --git a/usr.bin/make/trace.c b/usr.bin/make/trace.c
new file mode 100644
index 0000000..267177f
--- /dev/null
+++ b/usr.bin/make/trace.c
@@ -0,0 +1,116 @@
+/* $NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $ */
+
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Bill Sommerfeld
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $");
+#endif /* not lint */
+#endif
+
+/*-
+ * trace.c --
+ * handle logging of trace events generated by various parts of make.
+ *
+ * Interface:
+ * Trace_Init Initialize tracing (called once during
+ * the lifetime of the process)
+ *
+ * Trace_End Finalize tracing (called before make exits)
+ *
+ * Trace_Log Log an event about a particular make job.
+ */
+
+#include <sys/time.h>
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include "make.h"
+#include "job.h"
+#include "trace.h"
+
+static FILE *trfile;
+static pid_t trpid;
+char *trwd;
+
+static const char *evname[] = {
+ "BEG",
+ "END",
+ "ERR",
+ "JOB",
+ "DON",
+ "INT",
+};
+
+void
+Trace_Init(const char *pathname)
+{
+ char *p1;
+ if (pathname != NULL) {
+ trpid = getpid();
+ trwd = Var_Value(".CURDIR", VAR_GLOBAL, &p1);
+
+ trfile = fopen(pathname, "a");
+ }
+}
+
+void
+Trace_Log(TrEvent event, Job *job)
+{
+ struct timeval rightnow;
+
+ if (trfile == NULL)
+ return;
+
+ gettimeofday(&rightnow, NULL);
+
+ fprintf(trfile, "%lld.%06ld %d %s %d %s",
+ (long long)rightnow.tv_sec, (long)rightnow.tv_usec,
+ jobTokensRunning,
+ evname[event], trpid, trwd);
+ if (job != NULL) {
+ fprintf(trfile, " %s %d %x %x", job->node->name,
+ job->pid, job->flags, job->node->type);
+ }
+ fputc('\n', trfile);
+ fflush(trfile);
+}
+
+void
+Trace_End(void)
+{
+ if (trfile != NULL)
+ fclose(trfile);
+}
diff --git a/usr.bin/make/trace.h b/usr.bin/make/trace.h
new file mode 100644
index 0000000..dc0fc6c
--- /dev/null
+++ b/usr.bin/make/trace.h
@@ -0,0 +1,49 @@
+/* $NetBSD: trace.h,v 1.3 2008/04/28 20:24:14 martin Exp $ */
+
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Bill Sommerfeld
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * trace.h --
+ * Definitions pertaining to the tracing of jobs in parallel mode.
+ */
+
+typedef enum {
+ MAKESTART,
+ MAKEEND,
+ MAKEERROR,
+ JOBSTART,
+ JOBEND,
+ MAKEINTR
+} TrEvent;
+
+void Trace_Init(const char *);
+void Trace_Log(TrEvent, Job *);
+void Trace_End(void);
+
diff --git a/usr.bin/make/unit-tests/Makefile b/usr.bin/make/unit-tests/Makefile
new file mode 100644
index 0000000..07aaceb
--- /dev/null
+++ b/usr.bin/make/unit-tests/Makefile
@@ -0,0 +1,139 @@
+# $NetBSD: Makefile,v 1.53 2018/05/24 00:25:44 christos Exp $
+#
+# Unit tests for make(1)
+# The main targets are:
+#
+# all: run all the tests
+# test: run 'all', and compare to expected results
+# accept: move generated output to expected results
+#
+# Adding a test case.
+# Each feature should get its own set of tests in its own suitably
+# named makefile (*.mk), with its own set of expected results (*.exp),
+# and it should be added to the TESTNAMES list.
+#
+
+.MAIN: all
+
+UNIT_TESTS:= ${.PARSEDIR}
+.PATH: ${UNIT_TESTS}
+
+# Each test is in a sub-makefile.
+# Keep the list sorted.
+TESTNAMES= \
+ comment \
+ cond1 \
+ cond2 \
+ error \
+ export \
+ export-all \
+ export-env \
+ doterror \
+ dotwait \
+ forloop \
+ forsubst \
+ hash \
+ misc \
+ moderrs \
+ modmatch \
+ modmisc \
+ modorder \
+ modts \
+ modword \
+ order \
+ posix \
+ qequals \
+ sunshcmd \
+ sysv \
+ ternary \
+ unexport \
+ unexport-env \
+ varcmd \
+ varmisc \
+ varquote \
+ varshell
+
+# these tests were broken by referting POSIX chanegs
+STRICT_POSIX_TESTS = \
+ escape \
+ impsrc \
+ phony-end \
+ posix1 \
+ suffixes
+
+# Override make flags for certain tests
+flags.doterror=
+flags.order=-j1
+
+OUTFILES= ${TESTNAMES:S/$/.out/}
+
+all: ${OUTFILES}
+
+CLEANFILES += *.rawout *.out *.status *.tmp *.core *.tmp
+CLEANFILES += obj*.[och] lib*.a # posix1.mk
+CLEANFILES += issue* .[ab]* # suffixes.mk
+CLEANRECURSIVE += dir dummy # posix1.mk
+
+clean:
+ rm -f ${CLEANFILES}
+.if !empty(CLEANRECURSIVE)
+ rm -rf ${CLEANRECURSIVE}
+.endif
+
+TEST_MAKE?= ${.MAKE}
+TOOL_SED?= sed
+
+# ensure consistent results from sort(1)
+LC_ALL= C
+LANG= C
+.export LANG LC_ALL
+
+# the tests are actually done with sub-makes.
+.SUFFIXES: .mk .rawout .out
+.mk.rawout:
+ @echo ${TEST_MAKE} ${flags.${.TARGET:R}:U-k} -f ${.IMPSRC}
+ -@cd ${.OBJDIR} && \
+ { ${TEST_MAKE} ${flags.${.TARGET:R}:U-k} -f ${.IMPSRC} \
+ 2>&1 ; echo $$? >${.TARGET:R}.status ; } > ${.TARGET}.tmp
+ @mv ${.TARGET}.tmp ${.TARGET}
+
+# We always pretend .MAKE was called 'make'
+# and strip ${.CURDIR}/ from the output
+# and replace anything after 'stopped in' with unit-tests
+# so the results can be compared.
+.rawout.out:
+ @echo postprocess ${.TARGET}
+ @${TOOL_SED} -e 's,^${TEST_MAKE:T:C/\./\\\./g}[][0-9]*:,make:,' \
+ -e 's,${TEST_MAKE:C/\./\\\./g},make,' \
+ -e '/stopped/s, /.*, unit-tests,' \
+ -e 's,${.CURDIR:C/\./\\\./g}/,,g' \
+ -e 's,${UNIT_TESTS:C/\./\\\./g}/,,g' \
+ < ${.IMPSRC} > ${.TARGET}.tmp
+ @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp
+ @mv ${.TARGET}.tmp ${.TARGET}
+
+# Compare all output files
+test: ${OUTFILES} .PHONY
+ @failed= ; \
+ for test in ${TESTNAMES}; do \
+ diff -u ${UNIT_TESTS}/$${test}.exp $${test}.out \
+ || failed="$${failed}$${failed:+ }$${test}" ; \
+ done ; \
+ if [ -n "$${failed}" ]; then \
+ echo "Failed tests: $${failed}" ; false ; \
+ else \
+ echo "All tests passed" ; \
+ fi
+
+accept:
+ @for test in ${TESTNAMES}; do \
+ cmp -s ${UNIT_TESTS}/$${test}.exp $${test}.out \
+ || { echo "Replacing $${test}.exp" ; \
+ cp $${test}.out ${UNIT_TESTS}/$${test}.exp ; } \
+ done
+
+.if exists(${TEST_MAKE})
+${TESTNAMES:S/$/.rawout/}: ${TEST_MAKE}
+.endif
+
+.-include <bsd.obj.mk>
diff --git a/usr.bin/make/unit-tests/comment.exp b/usr.bin/make/unit-tests/comment.exp
new file mode 100644
index 0000000..9a97df0
--- /dev/null
+++ b/usr.bin/make/unit-tests/comment.exp
@@ -0,0 +1,5 @@
+comment testing start
+this is foo
+This is how a comment looks: # comment
+comment testing done
+exit status 0
diff --git a/usr.bin/make/unit-tests/comment.mk b/usr.bin/make/unit-tests/comment.mk
new file mode 100644
index 0000000..7dd7dbb
--- /dev/null
+++ b/usr.bin/make/unit-tests/comment.mk
@@ -0,0 +1,31 @@
+# This is a comment
+.if ${MACHINE_ARCH} == something
+FOO=bar
+.endif
+
+#\
+ Multiline comment
+
+BAR=# defined
+FOOBAR= # defined
+
+# This is an escaped comment \
+that keeps going until the end of this line
+
+# Another escaped comment \
+that \
+goes \
+on
+
+# This is NOT an escaped comment due to the double backslashes \\
+all: hi foo bar
+ @echo comment testing done
+
+hi:
+ @echo comment testing start
+
+foo:
+ @echo this is $@
+
+bar:
+ @echo This is how a comment looks: '# comment'
diff --git a/usr.bin/make/unit-tests/cond1.exp b/usr.bin/make/unit-tests/cond1.exp
new file mode 100644
index 0000000..701d504
--- /dev/null
+++ b/usr.bin/make/unit-tests/cond1.exp
@@ -0,0 +1,23 @@
+make: "cond1.mk" line 75: warning: extra else
+make: "cond1.mk" line 85: warning: extra else
+2 is prime
+A='other' B='unknown' C='clever' o='no,no'
+Passed:
+ var
+ ("var")
+ (var != var)
+ var != var
+ !((var != var) && defined(name))
+ var == quoted
+
+1 is not prime
+2 is prime
+3 is prime
+4 is not prime
+5 is prime
+
+make: warning: String comparison operator should be either == or !=
+make: Bad conditional expression `"0" > 0' in "0" > 0?OK:No
+
+OK
+exit status 0
diff --git a/usr.bin/make/unit-tests/cond1.mk b/usr.bin/make/unit-tests/cond1.mk
new file mode 100644
index 0000000..e361832
--- /dev/null
+++ b/usr.bin/make/unit-tests/cond1.mk
@@ -0,0 +1,109 @@
+# $Id: cond1.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+# hard code these!
+TEST_UNAME_S= NetBSD
+TEST_UNAME_M= sparc
+TEST_MACHINE= i386
+
+.if ${TEST_UNAME_S}
+Ok=var,
+.endif
+.if ("${TEST_UNAME_S}")
+Ok+=(\"var\"),
+.endif
+.if (${TEST_UNAME_M} != ${TEST_MACHINE})
+Ok+=(var != var),
+.endif
+.if ${TEST_UNAME_M} != ${TEST_MACHINE}
+Ok+= var != var,
+.endif
+.if !((${TEST_UNAME_M} != ${TEST_MACHINE}) && defined(X))
+Ok+= !((var != var) && defined(name)),
+.endif
+# from bsd.obj.mk
+MKOBJ?=no
+.if ${MKOBJ} == "no"
+o= no
+Ok+= var == "quoted",
+.else
+.if defined(notMAKEOBJDIRPREFIX) || defined(norMAKEOBJDIR)
+.if defined(notMAKEOBJDIRPREFIX)
+o=${MAKEOBJDIRPREFIX}${__curdir}
+.else
+o= ${MAKEOBJDIR}
+.endif
+.endif
+o= o
+.endif
+
+# repeat the above to check we get the same result
+.if ${MKOBJ} == "no"
+o2= no
+.else
+.if defined(notMAKEOBJDIRPREFIX) || defined(norMAKEOBJDIR)
+.if defined(notMAKEOBJDIRPREFIX)
+o2=${MAKEOBJDIRPREFIX}${__curdir}
+.else
+o2= ${MAKEOBJDIR}
+.endif
+.endif
+o2= o
+.endif
+
+PRIMES=2 3 5 7 11
+NUMBERS=1 2 3 4 5
+
+n=2
+.if ${PRIMES:M$n} == ""
+X=not
+.else
+X=
+.endif
+
+.if ${MACHINE_ARCH} == no-such
+A=one
+.else
+.if ${MACHINE_ARCH} == not-this
+.if ${MACHINE_ARCH} == something-else
+A=unlikely
+.else
+A=no
+.endif
+.endif
+A=other
+# We expect an extra else warning - we're not skipping here
+.else
+A=this should be an error
+.endif
+
+.if $X != ""
+.if $X == not
+B=one
+.else
+B=other
+# We expect an extra else warning - we are skipping here
+.else
+B=this should be an error
+.endif
+.else
+B=unknown
+.endif
+
+.if "quoted" == quoted
+C=clever
+.else
+C=dim
+.endif
+
+.if defined(nosuch) && ${nosuch:Mx} != ""
+# this should not happen
+.info nosuch is x
+.endif
+
+all:
+ @echo "$n is $X prime"
+ @echo "A='$A' B='$B' C='$C' o='$o,${o2}'"
+ @echo "Passed:${.newline} ${Ok:S/,/${.newline}/}"
+ @echo "${NUMBERS:@n@$n is ${("${PRIMES:M$n}" == ""):?not:} prime${.newline}@}"
+ @echo "${"${DoNotQuoteHere:U0}" > 0:?OK:No}"
+ @echo "${${NoSuchNumber:U42} > 0:?OK:No}"
diff --git a/usr.bin/make/unit-tests/cond2.exp b/usr.bin/make/unit-tests/cond2.exp
new file mode 100644
index 0000000..22e76a5
--- /dev/null
+++ b/usr.bin/make/unit-tests/cond2.exp
@@ -0,0 +1,7 @@
+make: Bad conditional expression ` == "empty"' in == "empty"?oops:ok
+make: "cond2.mk" line 13: Malformed conditional ({TEST_TYPO} == "Ok")
+TEST_NOT_SET is empty or not defined
+make: "cond2.mk" line 20: Malformed conditional (${TEST_NOT_SET} == "empty")
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/usr.bin/make/unit-tests/cond2.mk b/usr.bin/make/unit-tests/cond2.mk
new file mode 100644
index 0000000..aeb5a8c
--- /dev/null
+++ b/usr.bin/make/unit-tests/cond2.mk
@@ -0,0 +1,29 @@
+# $Id: cond2.mk,v 1.2 2015/12/02 00:28:24 sjg Exp $
+
+TEST_UNAME_S= NetBSD
+
+# this should be ok
+X:= ${${TEST_UNAME_S} == "NetBSD":?Ok:fail}
+.if $X == "Ok"
+Y= good
+.endif
+# expect: Bad conditional expression ` == "empty"' in == "empty"?oops:ok
+X:= ${${TEST_NOT_SET} == "empty":?oops:ok}
+# expect: Malformed conditional ({TEST_TYPO} == "Ok")
+.if {TEST_TYPO} == "Ok"
+Y= oops
+.endif
+.if empty(TEST_NOT_SET)
+Y!= echo TEST_NOT_SET is empty or not defined >&2; echo
+.endif
+# expect: Malformed conditional (${TEST_NOT_SET} == "empty")
+.if ${TEST_NOT_SET} == "empty"
+Y= oops
+.endif
+
+.if defined(.NDEF) && ${.NDEF} > 0
+Z= yes
+.endif
+
+all:
+ @echo $@
diff --git a/usr.bin/make/unit-tests/doterror.exp b/usr.bin/make/unit-tests/doterror.exp
new file mode 100644
index 0000000..5655644
--- /dev/null
+++ b/usr.bin/make/unit-tests/doterror.exp
@@ -0,0 +1,9 @@
+At first, I am
+happy
+and now: sad
+*** Error code 1
+
+Stop.
+make: stopped in unit-tests
+.ERROR: Looks like 'sad' is upset.
+exit status 1
diff --git a/usr.bin/make/unit-tests/doterror.mk b/usr.bin/make/unit-tests/doterror.mk
new file mode 100644
index 0000000..7f1c78f
--- /dev/null
+++ b/usr.bin/make/unit-tests/doterror.mk
@@ -0,0 +1,20 @@
+# $Id: doterror.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+
+.BEGIN:
+ @echo At first, I am
+
+.END:
+ @echo not reached
+
+.ERROR:
+ @echo "$@: Looks like '${.ERROR_TARGET}' is upset."
+
+all: happy sad
+
+happy:
+ @echo $@
+
+sad:
+ @echo and now: $@; exit 1
+
diff --git a/usr.bin/make/unit-tests/dotwait.exp b/usr.bin/make/unit-tests/dotwait.exp
new file mode 100644
index 0000000..bdc0a0e
--- /dev/null
+++ b/usr.bin/make/unit-tests/dotwait.exp
@@ -0,0 +1,30 @@
+simple.1
+simple.1
+simple.2
+simple.2
+recursive.1.1.*
+recursive.1.1.*
+recursive.1.1.*
+recursive.1.1.*
+recursive.1.99
+recursive.1.99
+recursive.2.1.*
+recursive.2.1.*
+recursive.2.1.*
+recursive.2.1.*
+recursive.2.99
+recursive.2.99
+shared.0
+shared.0
+shared.1.99
+shared.1.99
+shared.2.1
+shared.2.1
+shared.2.99
+shared.2.99
+cycle.1.99
+cycle.1.99
+make: Graph cycles through `cycle.2.99'
+make: Graph cycles through `cycle.2.98'
+make: Graph cycles through `cycle.2.97'
+exit status 0
diff --git a/usr.bin/make/unit-tests/dotwait.mk b/usr.bin/make/unit-tests/dotwait.mk
new file mode 100644
index 0000000..bab5993
--- /dev/null
+++ b/usr.bin/make/unit-tests/dotwait.mk
@@ -0,0 +1,61 @@
+# $NetBSD: dotwait.mk,v 1.2 2017/10/08 20:44:19 sjg Exp $
+
+THISMAKEFILE:= ${.PARSEDIR}/${.PARSEFILE}
+
+TESTS= simple recursive shared cycle
+PAUSE= sleep 1
+
+# Use a .for loop rather than dependencies here, to ensure
+# that the tests are run one by one, with parallelism
+# only within tests.
+# Ignore "--- target ---" lines printed by parallel make.
+all:
+.for t in ${TESTS}
+ @${.MAKE} -f ${THISMAKEFILE} -j4 $t 2>&1 | grep -v "^--- "
+.endfor
+
+#
+# Within each test, the names of the sub-targets follow these
+# conventions:
+# * If it's expected that two or more targets may be made in parallel,
+# then the target names will differ only in an alphabetic component
+# such as ".a" or ".b".
+# * If it's expected that two or more targets should be made in sequence
+# then the target names will differ in numeric components, such that
+# lexical ordering of the target names matches the expected order
+# in which the targets should be made.
+#
+# Targets may echo ${PARALLEL_TARG} to print a modified version
+# of their own name, in which alphabetic components like ".a" or ".b"
+# are converted to ".*". Two targets that are expected to
+# be made in parallel will thus print the same strings, so that the
+# output is independent of the order in which these targets are made.
+#
+PARALLEL_TARG= ${.TARGET:C/\.[a-z]/.*/g:Q}
+.DEFAULT:
+ @echo ${PARALLEL_TARG}; ${PAUSE}; echo ${PARALLEL_TARG}
+_ECHOUSE: .USE
+ @echo ${PARALLEL_TARG}; ${PAUSE}; echo ${PARALLEL_TARG}
+
+# simple: no recursion, no cycles
+simple: simple.1 .WAIT simple.2
+
+# recursive: all children of the left hand side of the .WAIT
+# must be made before any child of the right hand side.
+recursive: recursive.1.99 .WAIT recursive.2.99
+recursive.1.99: recursive.1.1.a recursive.1.1.b _ECHOUSE
+recursive.2.99: recursive.2.1.a recursive.2.1.b _ECHOUSE
+
+# shared: both shared.1.99 and shared.2.99 depend on shared.0.
+# shared.0 must be made first, even though it is a child of
+# the right hand side of the .WAIT.
+shared: shared.1.99 .WAIT shared.2.99
+shared.1.99: shared.0 _ECHOUSE
+shared.2.99: shared.2.1 shared.0 _ECHOUSE
+
+# cycle: the cyclic dependency must not cause infinite recursion
+# leading to stack overflow and a crash.
+cycle: cycle.1.99 .WAIT cycle.2.99
+cycle.2.99: cycle.2.98 _ECHOUSE
+cycle.2.98: cycle.2.97 _ECHOUSE
+cycle.2.97: cycle.2.99 _ECHOUSE
diff --git a/usr.bin/make/unit-tests/error.exp b/usr.bin/make/unit-tests/error.exp
new file mode 100644
index 0000000..a2bf71b
--- /dev/null
+++ b/usr.bin/make/unit-tests/error.exp
@@ -0,0 +1,4 @@
+make: "error.mk" line 3: just FYI
+make: "error.mk" line 4: warning: this could be serious
+make: "error.mk" line 5: this is fatal
+exit status 1
diff --git a/usr.bin/make/unit-tests/error.mk b/usr.bin/make/unit-tests/error.mk
new file mode 100644
index 0000000..721ed50
--- /dev/null
+++ b/usr.bin/make/unit-tests/error.mk
@@ -0,0 +1,10 @@
+# $Id: error.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+.info just FYI
+.warning this could be serious
+.error this is fatal
+
+all:
+
+.info.html:
+ @echo this should be ignored
diff --git a/usr.bin/make/unit-tests/escape.exp b/usr.bin/make/unit-tests/escape.exp
new file mode 100644
index 0000000..6238e27
--- /dev/null
+++ b/usr.bin/make/unit-tests/escape.exp
@@ -0,0 +1,104 @@
+var-1bs
+printf "%s=:%s:\n" VAR1BS 111\\111; printf "%s=:%s:\n" VAR1BSa 111\\aaa; printf "%s=:%s:\n" VAR1BSA 111\\aaa; printf "%s=:%s:\n" VAR1BSda 111\\\$\{a\}; printf "%s=:%s:\n" VAR1BSdA 111\\\$\{A\}; printf "%s=:%s:\n" VAR1BSc 111\#\ backslash\ escapes\ comment\ char,\ so\ this\ is\ part\ of\ the\ value; printf "%s=:%s:\n" VAR1BSsc 111\\\ ;
+VAR1BS=:111\111:
+VAR1BSa=:111\aaa:
+VAR1BSA=:111\aaa:
+VAR1BSda=:111\${a}:
+VAR1BSdA=:111\${A}:
+VAR1BSc=:111# backslash escapes comment char, so this is part of the value:
+VAR1BSsc=:111\ :
+var-2bs
+printf "%s=:%s:\n" VAR2BS 222\\\\222; printf "%s=:%s:\n" VAR2BSa 222\\\\aaa; printf "%s=:%s:\n" VAR2BSA 222\\\\aaa; printf "%s=:%s:\n" VAR2BSda 222\\\\\$\{a\}; printf "%s=:%s:\n" VAR2BSdA 222\\\\\$\{A\}; printf "%s=:%s:\n" VAR2BSc 222\\\\; printf "%s=:%s:\n" VAR2BSsc 222\\\\;
+VAR2BS=:222\\222:
+VAR2BSa=:222\\aaa:
+VAR2BSA=:222\\aaa:
+VAR2BSda=:222\\${a}:
+VAR2BSdA=:222\\${A}:
+VAR2BSc=:222\\:
+VAR2BSsc=:222\\:
+var-1bsnl
+printf "%s=:%s:\n" VAR1BSNL 111\ 111; printf "%s=:%s:\n" VAR1BSNLa 111\ aaa; printf "%s=:%s:\n" VAR1BSNLA 111\ aaa; printf "%s=:%s:\n" VAR1BSNLda 111\ \$\{a\}; printf "%s=:%s:\n" VAR1BSNLdA 111\ \$\{A\}; printf "%s=:%s:\n" VAR1BSNLc 111; printf "%s=:%s:\n" VAR1BSNLsc 111;
+VAR1BSNL=:111 111:
+VAR1BSNLa=:111 aaa:
+VAR1BSNLA=:111 aaa:
+VAR1BSNLda=:111 ${a}:
+VAR1BSNLdA=:111 ${A}:
+VAR1BSNLc=:111:
+VAR1BSNLsc=:111:
+var-2bsnl
+printf "%s=:%s:\n" VAR2BSNL 222\\\\; printf "%s=:%s:\n" VAR2BSNLa 222\\\\; printf "%s=:%s:\n" VAR2BSNLA 222\\\\; printf "%s=:%s:\n" VAR2BSNLda 222\\\\; printf "%s=:%s:\n" VAR2BSNLdA 222\\\\; printf "%s=:%s:\n" VAR2BSNLc 222\\\\; printf "%s=:%s:\n" VAR2BSNLsc 222\\\\;
+VAR2BSNL=:222\\:
+VAR2BSNLa=:222\\:
+VAR2BSNLA=:222\\:
+VAR2BSNLda=:222\\:
+VAR2BSNLdA=:222\\:
+VAR2BSNLc=:222\\:
+VAR2BSNLsc=:222\\:
+var-3bsnl
+printf "%s=:%s:\n" VAR3BSNL 333\\\\\ 333=; printf "%s=:%s:\n" VAR3BSNLa 333\\\\\ aaa=; printf "%s=:%s:\n" VAR3BSNLA 333\\\\\ aaa=; printf "%s=:%s:\n" VAR3BSNLda 333\\\\\ \$\{a\}=; printf "%s=:%s:\n" VAR3BSNLdA 333\\\\\ \$\{A\}=; printf "%s=:%s:\n" VAR3BSNLc 333\\\\; printf "%s=:%s:\n" VAR3BSNLsc 333\\\\;
+VAR3BSNL=:333\\ 333=:
+VAR3BSNLa=:333\\ aaa=:
+VAR3BSNLA=:333\\ aaa=:
+VAR3BSNLda=:333\\ ${a}=:
+VAR3BSNLdA=:333\\ ${A}=:
+VAR3BSNLc=:333\\:
+VAR3BSNLsc=:333\\:
+var-1bsnl-space
+printf "%s=:%s:\n" VAR1BSNL00 first\ line; printf "%s=:%s:\n" VAR1BSNL0 first\ line\ no\ space\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLs first\ line\ one\ space\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLss first\ line\ two\ spaces\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLt first\ line\ one\ tab\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLtt first\ line\ two\ tabs\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLxx first\ line\ many\ spaces\ and\ tabs\ \[\ \ \ \ \]\ on\ second\ line;
+VAR1BSNL00=:first line:
+VAR1BSNL0=:first line no space on second line:
+VAR1BSNLs=:first line one space on second line:
+VAR1BSNLss=:first line two spaces on second line:
+VAR1BSNLt=:first line one tab on second line:
+VAR1BSNLtt=:first line two tabs on second line:
+VAR1BSNLxx=:first line many spaces and tabs [ ] on second line:
+cmd-1bsnl
+echo :'first line\
+#second line without space\
+third line':
+:first line\
+#second line without space\
+third line:
+echo :'first line\
+ second line spaces should be retained':
+:first line\
+ second line spaces should be retained:
+echo :'first line\
+second line tab should be elided':
+:first line\
+second line tab should be elided:
+echo :'first line\
+ only one tab should be elided, second tab remains'
+:first line\
+ only one tab should be elided, second tab remains
+cmd-1bsnl-eof
+echo :'command ending with backslash-newline'; \
+
+:command ending with backslash-newline
+cmd-2bsnl
+echo take one\\
+take one\
+echo take two\\
+take two\
+echo take three\\
+take three\
+cmd-3bsnl
+echo :'first line\\\
+#second line without space\\\
+third line':
+:first line\\\
+#second line without space\\\
+third line:
+echo :'first line\\\
+ second line spaces should be retained':
+:first line\\\
+ second line spaces should be retained:
+echo :'first line\\\
+second line tab should be elided':
+:first line\\\
+second line tab should be elided:
+echo :'first line\\\
+ only one tab should be elided, second tab remains'
+:first line\\\
+ only one tab should be elided, second tab remains
+exit status 0
diff --git a/usr.bin/make/unit-tests/escape.mk b/usr.bin/make/unit-tests/escape.mk
new file mode 100644
index 0000000..829403d
--- /dev/null
+++ b/usr.bin/make/unit-tests/escape.mk
@@ -0,0 +1,246 @@
+# $Id: escape.mk,v 1.10 2014/09/09 10:22:27 apb Exp $
+#
+# Test backslash escaping.
+
+# Extracts from the POSIX 2008 specification
+# <http://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html>:
+#
+# Comments start with a <number-sign> ( '#' ) and continue until an
+# unescaped <newline> is reached.
+#
+# When an escaped <newline> (one preceded by a <backslash>) is found
+# anywhere in the makefile except in a command line, an include
+# line, or a line immediately preceding an include line, it shall
+# be replaced, along with any leading white space on the following
+# line, with a single <space>.
+#
+# When an escaped <newline> is found in a command line in a
+# makefile, the command line shall contain the <backslash>, the
+# <newline>, and the next line, except that the first character of
+# the next line shall not be included if it is a <tab>.
+#
+# When an escaped <newline> is found in an include line or in a
+# line immediately preceding an include line, the behavior is
+# unspecified.
+#
+# Notice that the behaviour of <backslash><backslash> or
+# <backslash><anything other than newline> is not mentioned. I think
+# this implies that <backslash> should be taken literally everywhere
+# except before <newline>.
+#
+# Our practice, despite what POSIX might say, is that "\#"
+# in a variable assignment stores "#" as part of the value.
+# The "\" is not taken literally, and the "#" does not begin a comment.
+#
+# Also, our practice is that an even number of backslashes before a
+# newline in a variable assignment simply stores the backslashes as part
+# of the value, and treats the newline as though it was not escaped.
+# Similarly, ann even number of backslashes before a newline in a
+# command simply uses the backslashes as part of the command test, but
+# does not escape the newline. This is compatible with GNU make.
+
+all: .PHONY
+# We will add dependencies like "all: yet-another-test" later.
+
+# Some variables to be expanded in tests
+#
+a = aaa
+A = ${a}
+
+# Backslash at end of line in a comment\
+should continue the comment. \
+# This is also tested in comment.mk.
+
+__printvars: .USE .MADE
+ @echo ${.TARGET}
+ ${.ALLSRC:@v@ printf "%s=:%s:\n" ${v:Q} ${${v}:Q}; @}
+
+# Embedded backslash in variable should be taken literally.
+#
+VAR1BS = 111\111
+VAR1BSa = 111\${a}
+VAR1BSA = 111\${A}
+VAR1BSda = 111\$${a}
+VAR1BSdA = 111\$${A}
+VAR1BSc = 111\# backslash escapes comment char, so this is part of the value
+VAR1BSsc = 111\ # This is a comment. Value ends with <backslash><space>
+
+all: var-1bs
+var-1bs: .PHONY __printvars VAR1BS VAR1BSa VAR1BSA VAR1BSda VAR1BSdA \
+ VAR1BSc VAR1BSsc
+
+# Double backslash in variable should be taken as two literal backslashes.
+#
+VAR2BS = 222\\222
+VAR2BSa = 222\\${a}
+VAR2BSA = 222\\${A}
+VAR2BSda = 222\\$${a}
+VAR2BSdA = 222\\$${A}
+VAR2BSc = 222\\# backslash does not escape comment char, so this is a comment
+VAR2BSsc = 222\\ # This is a comment. Value ends with <backslash><backslash>
+
+all: var-2bs
+var-2bs: .PHONY __printvars VAR2BS VAR2BSa VAR2BSA VAR2BSda VAR2BSdA \
+ VAR2BSc VAR2BSsc
+
+# Backslash-newline in a variable setting is replaced by a single space.
+#
+VAR1BSNL = 111\
+111
+VAR1BSNLa = 111\
+${a}
+VAR1BSNLA = 111\
+${A}
+VAR1BSNLda = 111\
+$${a}
+VAR1BSNLdA = 111\
+$${A}
+VAR1BSNLc = 111\
+# this should be processed as a comment
+VAR1BSNLsc = 111\
+ # this should be processed as a comment
+
+all: var-1bsnl
+var-1bsnl: .PHONY
+var-1bsnl: .PHONY __printvars \
+ VAR1BSNL VAR1BSNLa VAR1BSNLA VAR1BSNLda VAR1BSNLdA \
+ VAR1BSNLc VAR1BSNLsc
+
+# Double-backslash-newline in a variable setting.
+# Both backslashes should be taken literally, and the newline is NOT escaped.
+#
+# The second lines below each end with '=' so that they will not
+# generate syntax errors regardless of whether or not they are
+# treated as part of the value.
+#
+VAR2BSNL = 222\\
+222=
+VAR2BSNLa = 222\\
+${a}=
+VAR2BSNLA = 222\\
+${A}=
+VAR2BSNLda = 222\\
+$${a}=
+VAR2BSNLdA = 222\\
+$${A}=
+VAR2BSNLc = 222\\
+# this should be processed as a comment
+VAR2BSNLsc = 222\\
+ # this should be processed as a comment
+
+all: var-2bsnl
+var-2bsnl: .PHONY __printvars \
+ VAR2BSNL VAR2BSNLa VAR2BSNLA VAR2BSNLda VAR2BSNLdA \
+ VAR2BSNLc VAR2BSNLsc
+
+# Triple-backslash-newline in a variable setting.
+# First two should be taken literally, and last should escape the newline.
+#
+# The second lines below each end with '=' so that they will not
+# generate syntax errors regardless of whether or not they are
+# treated as part of the value.
+#
+VAR3BSNL = 333\\\
+333=
+VAR3BSNLa = 333\\\
+${a}=
+VAR3BSNLA = 333\\\
+${A}=
+VAR3BSNLda = 333\\\
+$${a}=
+VAR3BSNLdA = 333\\\
+$${A}=
+VAR3BSNLc = 333\\\
+# this should be processed as a comment
+VAR3BSNLsc = 333\\\
+ # this should be processed as a comment
+
+all: var-3bsnl
+var-3bsnl: .PHONY __printvars \
+ VAR3BSNL VAR3BSNLa VAR3BSNLA VAR3BSNLda VAR3BSNLdA \
+ VAR3BSNLc VAR3BSNLsc
+
+# Backslash-newline in a variable setting, plus any amount of white space
+# on the next line, is replaced by a single space.
+#
+VAR1BSNL00= first line\
+
+# above line is entirely empty, and this is a comment
+VAR1BSNL0= first line\
+no space on second line
+VAR1BSNLs= first line\
+ one space on second line
+VAR1BSNLss= first line\
+ two spaces on second line
+VAR1BSNLt= first line\
+ one tab on second line
+VAR1BSNLtt= first line\
+ two tabs on second line
+VAR1BSNLxx= first line\
+ many spaces and tabs [ ] on second line
+
+all: var-1bsnl-space
+var-1bsnl-space: .PHONY __printvars \
+ VAR1BSNL00 VAR1BSNL0 VAR1BSNLs VAR1BSNLss VAR1BSNLt VAR1BSNLtt \
+ VAR1BSNLxx
+
+# Backslash-newline in a command is retained.
+#
+# The "#" in "# second line without space" makes it a comment instead
+# of a syntax error if the preceding line is parsed incorretly.
+# The ":" in "third line':" makes it look like the start of a
+# target instead of a syntax error if the first line is parsed incorrectly.
+#
+all: cmd-1bsnl
+cmd-1bsnl: .PHONY
+ @echo ${.TARGET}
+ echo :'first line\
+#second line without space\
+third line':
+ echo :'first line\
+ second line spaces should be retained':
+ echo :'first line\
+ second line tab should be elided':
+ echo :'first line\
+ only one tab should be elided, second tab remains'
+
+# When backslash-newline appears at the end of a command script,
+# both the backslash and the newline should be passed to the shell.
+# The shell should elide the backslash-newline.
+#
+all: cmd-1bsnl-eof
+cmd-1bsnl-eof:
+ @echo ${.TARGET}
+ echo :'command ending with backslash-newline'; \
+
+# above line must be blank
+
+# Double-backslash-newline in a command.
+# Both backslashes are retained, but the newline is not escaped.
+# XXX: This may differ from POSIX, but matches gmake.
+#
+# When make passes two backslashes to the shell, the shell will pass one
+# backslash to the echo commant.
+#
+all: cmd-2bsnl
+cmd-2bsnl: .PHONY
+ @echo ${.TARGET}
+ echo take one\\
+# this should be a comment
+ echo take two\\
+ echo take three\\
+
+# Triple-backslash-newline in a command is retained.
+#
+all: cmd-3bsnl
+cmd-3bsnl: .PHONY
+ @echo ${.TARGET}
+ echo :'first line\\\
+#second line without space\\\
+third line':
+ echo :'first line\\\
+ second line spaces should be retained':
+ echo :'first line\\\
+ second line tab should be elided':
+ echo :'first line\\\
+ only one tab should be elided, second tab remains'
diff --git a/usr.bin/make/unit-tests/export-all.exp b/usr.bin/make/unit-tests/export-all.exp
new file mode 100644
index 0000000..e3aefd4
--- /dev/null
+++ b/usr.bin/make/unit-tests/export-all.exp
@@ -0,0 +1,12 @@
+UT_ALL=even this gets exported
+UT_BADDIR=unit-tests
+UT_DOLLAR=This is $UT_FU
+UT_F=fine
+UT_FOO=foobar is fubar
+UT_FU=fubar
+UT_NO=all
+UT_OK=good
+UT_OKDIR=unit-tests
+UT_TEST=export-all
+UT_ZOO=hoopie
+exit status 0
diff --git a/usr.bin/make/unit-tests/export-all.mk b/usr.bin/make/unit-tests/export-all.mk
new file mode 100644
index 0000000..576487b
--- /dev/null
+++ b/usr.bin/make/unit-tests/export-all.mk
@@ -0,0 +1,23 @@
+# $Id: export-all.mk,v 1.2 2015/04/10 20:41:59 sjg Exp $
+
+UT_OK=good
+UT_F=fine
+
+# the old way to do :tA
+M_tAbad = C,.*,cd & \&\& 'pwd',:sh
+# the new
+M_tA = tA
+
+here := ${.PARSEDIR}
+
+# this will cause trouble (recursing if we let it)
+UT_BADDIR = ${${here}/../${here:T}:L:${M_tAbad}:T}
+# this will be ok
+UT_OKDIR = ${${here}/../${here:T}:L:${M_tA}:T}
+
+.export
+
+.include "export.mk"
+
+UT_TEST=export-all
+UT_ALL=even this gets exported
diff --git a/usr.bin/make/unit-tests/export-env.exp b/usr.bin/make/unit-tests/export-env.exp
new file mode 100644
index 0000000..8a779e6
--- /dev/null
+++ b/usr.bin/make/unit-tests/export-env.exp
@@ -0,0 +1,11 @@
+make:
+UT_TEST=export-env.mk
+UT_ENV=not-exported
+UT_EXP=not-exported
+UT_LIT=literal export-env.mk
+env:
+UT_TEST=export-env.mk
+UT_ENV=exported
+UT_EXP=exported
+UT_LIT=literal ${UT_TEST}
+exit status 0
diff --git a/usr.bin/make/unit-tests/export-env.mk b/usr.bin/make/unit-tests/export-env.mk
new file mode 100644
index 0000000..c4d3e75
--- /dev/null
+++ b/usr.bin/make/unit-tests/export-env.mk
@@ -0,0 +1,27 @@
+# $Id: export-env.mk,v 1.2 2016/02/18 20:25:08 sjg Exp $
+
+# our normal .export, subsequent changes affect the environment
+UT_TEST=this
+.export UT_TEST
+UT_TEST:= ${.PARSEFILE}
+
+# not so with .export-env
+UT_ENV=exported
+.export-env UT_ENV
+UT_ENV=not-exported
+
+# gmake style export goes further; affects nothing but the environment
+UT_EXP=before-export
+export UT_EXP=exported
+UT_EXP=not-exported
+
+UT_LIT= literal ${UT_TEST}
+.export-literal UT_LIT
+
+all:
+ @echo make:; ${UT_TEST UT_ENV UT_EXP UT_LIT:L:@v@echo $v=${$v};@}
+ @echo env:; ${UT_TEST UT_ENV UT_EXP UT_LIT:L:@v@echo $v=$${$v};@}
+
+
+
+
diff --git a/usr.bin/make/unit-tests/export.exp b/usr.bin/make/unit-tests/export.exp
new file mode 100644
index 0000000..143771c
--- /dev/null
+++ b/usr.bin/make/unit-tests/export.exp
@@ -0,0 +1,6 @@
+UT_DOLLAR=This is $UT_FU
+UT_FOO=foobar is fubar
+UT_FU=fubar
+UT_TEST=export
+UT_ZOO=hoopie
+exit status 0
diff --git a/usr.bin/make/unit-tests/export.mk b/usr.bin/make/unit-tests/export.mk
new file mode 100644
index 0000000..1b4ee72
--- /dev/null
+++ b/usr.bin/make/unit-tests/export.mk
@@ -0,0 +1,22 @@
+# $Id: export.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+UT_TEST=export
+UT_FOO=foo${BAR}
+UT_FU=fubar
+UT_ZOO=hoopie
+UT_NO=all
+# belive it or not, we expect this one to come out with $UT_FU unexpanded.
+UT_DOLLAR= This is $$UT_FU
+
+.export UT_FU UT_FOO
+.export UT_DOLLAR
+# this one will be ignored
+.export .MAKE.PID
+
+BAR=bar is ${UT_FU}
+
+.MAKE.EXPORTED+= UT_ZOO UT_TEST
+
+all:
+ @env | grep '^UT_' | sort
+
diff --git a/usr.bin/make/unit-tests/forloop.exp b/usr.bin/make/unit-tests/forloop.exp
new file mode 100644
index 0000000..df14b75
--- /dev/null
+++ b/usr.bin/make/unit-tests/forloop.exp
@@ -0,0 +1,19 @@
+x=one
+x="two and three"
+x=four
+x="five"
+x=-I/this
+x=-I"This or that"
+x=-Ithat
+x="-DTHIS=\"this and that\""
+cfl=-I/this -I"This or that" -Ithat "-DTHIS=\"this and that\""
+a=one b="two and three"
+a=four b="five"
+a=ONE b="TWO AND THREE"
+a=FOUR b="FIVE"
+We expect an error next:
+make: "forloop.mk" line 38: Wrong number of words (9) in .for substitution list with 2 vars
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+OK
+exit status 0
diff --git a/usr.bin/make/unit-tests/forloop.mk b/usr.bin/make/unit-tests/forloop.mk
new file mode 100644
index 0000000..e0399f3
--- /dev/null
+++ b/usr.bin/make/unit-tests/forloop.mk
@@ -0,0 +1,45 @@
+# $Id: forloop.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+all: for-loop
+
+LIST = one "two and three" four "five"
+
+.if make(for-fail)
+for-fail:
+
+XTRA_LIST = xtra
+.else
+
+.for x in ${LIST}
+X!= echo 'x=$x' >&2; echo
+.endfor
+
+CFL = -I/this -I"This or that" -Ithat "-DTHIS=\"this and that\""
+cfl=
+.for x in ${CFL}
+X!= echo 'x=$x' >&2; echo
+.if empty(cfl)
+cfl= $x
+.else
+cfl+= $x
+.endif
+.endfor
+X!= echo 'cfl=${cfl}' >&2; echo
+
+.if ${cfl} != ${CFL}
+.error ${.newline}'${cfl}' != ${.newline}'${CFL}'
+.endif
+
+.for a b in ${EMPTY}
+X!= echo 'a=$a b=$b' >&2; echo
+.endfor
+.endif
+
+.for a b in ${LIST} ${LIST:tu} ${XTRA_LIST}
+X!= echo 'a=$a b=$b' >&2; echo
+.endfor
+
+for-loop:
+ @echo We expect an error next:
+ @(cd ${.CURDIR} && ${.MAKE} -f ${MAKEFILE} for-fail) && \
+ { echo "Oops that should have failed!"; exit 1; } || echo OK
diff --git a/usr.bin/make/unit-tests/forsubst.exp b/usr.bin/make/unit-tests/forsubst.exp
new file mode 100644
index 0000000..0a98c00
--- /dev/null
+++ b/usr.bin/make/unit-tests/forsubst.exp
@@ -0,0 +1,2 @@
+.for with :S;... OK
+exit status 0
diff --git a/usr.bin/make/unit-tests/forsubst.mk b/usr.bin/make/unit-tests/forsubst.mk
new file mode 100644
index 0000000..00cd9b6
--- /dev/null
+++ b/usr.bin/make/unit-tests/forsubst.mk
@@ -0,0 +1,10 @@
+# $Id: forsubst.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+all: for-subst
+
+here := ${.PARSEDIR}
+# this should not run foul of the parser
+.for file in ${.PARSEFILE}
+for-subst: ${file:S;^;${here}/;g}
+ @echo ".for with :S;... OK"
+.endfor
diff --git a/usr.bin/make/unit-tests/hash.exp b/usr.bin/make/unit-tests/hash.exp
new file mode 100644
index 0000000..0a24234
--- /dev/null
+++ b/usr.bin/make/unit-tests/hash.exp
@@ -0,0 +1,9 @@
+b2af338b
+3360ac65
+7747f046
+9ca87054
+880fe816
+208fcbd3
+d5d376eb
+de41416c
+exit status 0
diff --git a/usr.bin/make/unit-tests/hash.mk b/usr.bin/make/unit-tests/hash.mk
new file mode 100644
index 0000000..1ed84e7
--- /dev/null
+++ b/usr.bin/make/unit-tests/hash.mk
@@ -0,0 +1,18 @@
+STR1=
+STR2= a
+STR3= ab
+STR4= abc
+STR5= abcd
+STR6= abcde
+STR7= abcdef
+STR8= abcdefghijklmnopqrstuvwxyz
+
+all:
+ @echo ${STR1:hash}
+ @echo ${STR2:hash}
+ @echo ${STR3:hash}
+ @echo ${STR4:hash}
+ @echo ${STR5:hash}
+ @echo ${STR6:hash}
+ @echo ${STR7:hash}
+ @echo ${STR8:hash}
diff --git a/usr.bin/make/unit-tests/impsrc.exp b/usr.bin/make/unit-tests/impsrc.exp
new file mode 100644
index 0000000..23e8347
--- /dev/null
+++ b/usr.bin/make/unit-tests/impsrc.exp
@@ -0,0 +1,13 @@
+expected: source4
+actual: source4
+expected: target1.x
+actual: target1.x
+expected: target1.y
+actual: target1.y
+expected: source1
+actual: source1
+expected: source2
+actual: source2
+expected: source1
+actual: source1
+exit status 0
diff --git a/usr.bin/make/unit-tests/impsrc.mk b/usr.bin/make/unit-tests/impsrc.mk
new file mode 100644
index 0000000..95ae0c3
--- /dev/null
+++ b/usr.bin/make/unit-tests/impsrc.mk
@@ -0,0 +1,43 @@
+# $NetBSD: impsrc.mk,v 1.2 2014/08/30 22:21:07 sjg Exp $
+
+# Does ${.IMPSRC} work properly?
+# It should be set, in order of precedence, to ${.TARGET} of:
+# 1) the implied source of a transformation rule,
+# 2) the first prerequisite from the dependency line of an explicit rule, or
+# 3) the first prerequisite of an explicit rule.
+#
+
+all: target1.z target2 target3 target4
+
+.SUFFIXES: .x .y .z
+
+.x.y: source1
+ @echo 'expected: target1.x'
+ @echo 'actual: $<'
+
+.y.z: source2
+ @echo 'expected: target1.y'
+ @echo 'actual: $<'
+
+target1.y: source3
+
+target1.x: source4
+ @echo 'expected: source4'
+ @echo 'actual: $<'
+
+target2: source1 source2
+ @echo 'expected: source1'
+ @echo 'actual: $<'
+
+target3: source1
+target3: source2 source3
+ @echo 'expected: source2'
+ @echo 'actual: $<'
+
+target4: source1
+target4:
+ @echo 'expected: source1'
+ @echo 'actual: $<'
+
+source1 source2 source3 source4:
+
diff --git a/usr.bin/make/unit-tests/misc.exp b/usr.bin/make/unit-tests/misc.exp
new file mode 100644
index 0000000..39a9383
--- /dev/null
+++ b/usr.bin/make/unit-tests/misc.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/usr.bin/make/unit-tests/misc.mk b/usr.bin/make/unit-tests/misc.mk
new file mode 100644
index 0000000..2773e30
--- /dev/null
+++ b/usr.bin/make/unit-tests/misc.mk
@@ -0,0 +1,16 @@
+# $Id: misc.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+.if !exists(${.CURDIR}/)
+.warning ${.CURDIR}/ doesn't exist ?
+.endif
+
+.if !exists(${.CURDIR}/.)
+.warning ${.CURDIR}/. doesn't exist ?
+.endif
+
+.if !exists(${.CURDIR}/..)
+.warning ${.CURDIR}/.. doesn't exist ?
+.endif
+
+all:
+ @: all is well
diff --git a/usr.bin/make/unit-tests/moderrs.exp b/usr.bin/make/unit-tests/moderrs.exp
new file mode 100644
index 0000000..cb51aa0
--- /dev/null
+++ b/usr.bin/make/unit-tests/moderrs.exp
@@ -0,0 +1,16 @@
+Expect: Unknown modifier 'Z'
+make: Unknown modifier 'Z'
+VAR:Z=
+Expect: Unknown modifier 'Z'
+make: Unknown modifier 'Z'
+VAR:Z=
+Expect: Unclosed variable specification for VAR
+make: Unclosed variable specification (expecting '}') for "VAR" (value "Thevariable") modifier S
+VAR:S,V,v,=Thevariable
+Expect: Unclosed variable specification for VAR
+make: Unclosed variable specification after complex modifier (expecting '}') for VAR
+VAR:S,V,v,=Thevariable
+Expect: Unclosed substitution for VAR (, missing)
+make: Unclosed substitution for VAR (, missing)
+VAR:S,V,v=
+exit status 0
diff --git a/usr.bin/make/unit-tests/moderrs.mk b/usr.bin/make/unit-tests/moderrs.mk
new file mode 100644
index 0000000..825e6c7
--- /dev/null
+++ b/usr.bin/make/unit-tests/moderrs.mk
@@ -0,0 +1,31 @@
+# $Id: moderrs.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+#
+# various modifier error tests
+
+VAR=TheVariable
+# incase we have to change it ;-)
+MOD_UNKN=Z
+MOD_TERM=S,V,v
+MOD_S:= ${MOD_TERM},
+
+all: modunkn modunknV varterm vartermV modtermV
+
+modunkn:
+ @echo "Expect: Unknown modifier 'Z'"
+ @echo "VAR:Z=${VAR:Z}"
+
+modunknV:
+ @echo "Expect: Unknown modifier 'Z'"
+ @echo "VAR:${MOD_UNKN}=${VAR:${MOD_UNKN}}"
+
+varterm:
+ @echo "Expect: Unclosed variable specification for VAR"
+ @echo VAR:S,V,v,=${VAR:S,V,v,
+
+vartermV:
+ @echo "Expect: Unclosed variable specification for VAR"
+ @echo VAR:${MOD_TERM},=${VAR:${MOD_S}
+
+modtermV:
+ @echo "Expect: Unclosed substitution for VAR (, missing)"
+ -@echo "VAR:${MOD_TERM}=${VAR:${MOD_TERM}}"
diff --git a/usr.bin/make/unit-tests/modmatch.exp b/usr.bin/make/unit-tests/modmatch.exp
new file mode 100644
index 0000000..a7bf8b7
--- /dev/null
+++ b/usr.bin/make/unit-tests/modmatch.exp
@@ -0,0 +1,20 @@
+LIB=a X_LIBS:M${LIB${LIB:tu}} is "/tmp/liba.a"
+LIB=a X_LIBS:M*/lib${LIB}.a is "/tmp/liba.a"
+LIB=a X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBA.A"
+LIB=b X_LIBS:M${LIB${LIB:tu}} is ""
+LIB=b X_LIBS:M*/lib${LIB}.a is ""
+LIB=b X_LIBS:M*/lib${LIB}.a:tu is ""
+LIB=c X_LIBS:M${LIB${LIB:tu}} is ""
+LIB=c X_LIBS:M*/lib${LIB}.a is ""
+LIB=c X_LIBS:M*/lib${LIB}.a:tu is ""
+LIB=d X_LIBS:M${LIB${LIB:tu}} is "/tmp/libd.a"
+LIB=d X_LIBS:M*/lib${LIB}.a is "/tmp/libd.a"
+LIB=d X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBD.A"
+LIB=e X_LIBS:M${LIB${LIB:tu}} is "/tmp/libe.a"
+LIB=e X_LIBS:M*/lib${LIB}.a is "/tmp/libe.a"
+LIB=e X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBE.A"
+Mscanner=OK
+Upper=One Two Three Four
+Lower=five six seven
+nose=One Three five
+exit status 0
diff --git a/usr.bin/make/unit-tests/modmatch.mk b/usr.bin/make/unit-tests/modmatch.mk
new file mode 100644
index 0000000..4519928
--- /dev/null
+++ b/usr.bin/make/unit-tests/modmatch.mk
@@ -0,0 +1,34 @@
+
+X=a b c d e
+
+.for x in $X
+LIB${x:tu}=/tmp/lib$x.a
+.endfor
+
+X_LIBS= ${LIBA} ${LIBD} ${LIBE}
+
+LIB?=a
+
+var = head
+res = no
+.if !empty(var:M${:Uhead\:tail:C/:.*//})
+res = OK
+.endif
+
+all: show-libs check-cclass
+
+show-libs:
+ @for x in $X; do ${.MAKE} -f ${MAKEFILE} show LIB=$$x; done
+ @echo "Mscanner=${res}"
+
+show:
+ @echo 'LIB=${LIB} X_LIBS:M$${LIB$${LIB:tu}} is "${X_LIBS:M${LIB${LIB:tu}}}"'
+ @echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a is "${X_LIBS:M*/lib${LIB}.a}"'
+ @echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a:tu is "${X_LIBS:M*/lib${LIB}.a:tu}"'
+
+LIST= One Two Three Four five six seven
+
+check-cclass:
+ @echo Upper=${LIST:M[A-Z]*}
+ @echo Lower=${LIST:M[^A-Z]*}
+ @echo nose=${LIST:M[^s]*[ex]}
diff --git a/usr.bin/make/unit-tests/modmisc.exp b/usr.bin/make/unit-tests/modmisc.exp
new file mode 100644
index 0000000..e406647
--- /dev/null
+++ b/usr.bin/make/unit-tests/modmisc.exp
@@ -0,0 +1,10 @@
+path=':/bin:/tmp::/:.:/no/such/dir:.'
+path='/bin:/tmp:/:/no/such/dir'
+path='/bin:/tmp:/:/no/such/dir'
+path='/bin':'/tmp':'/':'/no/such/dir'
+path='/bin':'/tmp':'/':'/no/such/dir'
+path_/usr/xbin=/opt/xbin/
+paths=/bin /tmp / /no/such/dir /opt/xbin
+PATHS=/BIN /TMP / /NO/SUCH/DIR /OPT/XBIN
+The answer is 42
+exit status 0
diff --git a/usr.bin/make/unit-tests/modmisc.mk b/usr.bin/make/unit-tests/modmisc.mk
new file mode 100644
index 0000000..a292b96
--- /dev/null
+++ b/usr.bin/make/unit-tests/modmisc.mk
@@ -0,0 +1,38 @@
+# $Id: modmisc.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+#
+# miscellaneous modifier tests
+
+# do not put any dirs in this list which exist on some
+# but not all target systems - an exists() check is below.
+path=:/bin:/tmp::/:.:/no/such/dir:.
+# strip cwd from path.
+MOD_NODOT=S/:/ /g:N.:ts:
+# and decorate, note that $'s need to be doubled. Also note that
+# the modifier_variable can be used with other modifiers.
+MOD_NODOTX=S/:/ /g:N.:@d@'$$d'@
+# another mod - pretend it is more interesting
+MOD_HOMES=S,/home/,/homes/,
+MOD_OPT=@d@$${exists($$d):?$$d:$${d:S,/usr,/opt,}}@
+MOD_SEP=S,:, ,g
+
+all: modvar modvarloop modsysv
+
+modsysv:
+ @echo "The answer is ${libfoo.a:L:libfoo.a=42}"
+
+modvar:
+ @echo "path='${path}'"
+ @echo "path='${path:${MOD_NODOT}}'"
+ @echo "path='${path:S,home,homes,:${MOD_NODOT}}'"
+ @echo "path=${path:${MOD_NODOTX}:ts:}"
+ @echo "path=${path:${MOD_HOMES}:${MOD_NODOTX}:ts:}"
+
+.for d in ${path:${MOD_SEP}:N.} /usr/xbin
+path_$d?= ${d:${MOD_OPT}:${MOD_HOMES}}/
+paths+= ${d:${MOD_OPT}:${MOD_HOMES}}
+.endfor
+
+modvarloop:
+ @echo "path_/usr/xbin=${path_/usr/xbin}"
+ @echo "paths=${paths}"
+ @echo "PATHS=${paths:tu}"
diff --git a/usr.bin/make/unit-tests/modorder.exp b/usr.bin/make/unit-tests/modorder.exp
new file mode 100644
index 0000000..4117427
--- /dev/null
+++ b/usr.bin/make/unit-tests/modorder.exp
@@ -0,0 +1,11 @@
+LIST = one two three four five six seven eight nine ten
+LIST:O = eight five four nine one seven six ten three two
+LIST:Ox = Ok
+LIST:O:Ox = Ok
+LISTX = Ok
+LISTSX = Ok
+make: Bad modifier `:OX' for LIST
+BADMOD 1 = }
+make: Bad modifier `:OxXX' for LIST
+BADMOD 2 = XX}
+exit status 0
diff --git a/usr.bin/make/unit-tests/modorder.mk b/usr.bin/make/unit-tests/modorder.mk
new file mode 100644
index 0000000..bc24d33
--- /dev/null
+++ b/usr.bin/make/unit-tests/modorder.mk
@@ -0,0 +1,22 @@
+# $NetBSD: modorder.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+LIST= one two three four five six seven eight nine ten
+LISTX= ${LIST:Ox}
+LISTSX:= ${LIST:Ox}
+TEST_RESULT= && echo Ok || echo Failed
+
+# unit-tests have to produce the same results on each run
+# so we cannot actually include :Ox output.
+all:
+ @echo "LIST = ${LIST}"
+ @echo "LIST:O = ${LIST:O}"
+ # Note that 1 in every 10! trials two independently generated
+ # randomized orderings will be the same. The test framework doesn't
+ # support checking probabilistic output, so we accept that the test
+ # will incorrectly fail with probability 2.8E-7.
+ @echo "LIST:Ox = `test '${LIST:Ox}' != '${LIST:Ox}' ${TEST_RESULT}`"
+ @echo "LIST:O:Ox = `test '${LIST:O:Ox}' != '${LIST:O:Ox}' ${TEST_RESULT}`"
+ @echo "LISTX = `test '${LISTX}' != '${LISTX}' ${TEST_RESULT}`"
+ @echo "LISTSX = `test '${LISTSX}' = '${LISTSX}' ${TEST_RESULT}`"
+ @echo "BADMOD 1 = ${LIST:OX}"
+ @echo "BADMOD 2 = ${LIST:OxXX}"
diff --git a/usr.bin/make/unit-tests/modts.exp b/usr.bin/make/unit-tests/modts.exp
new file mode 100644
index 0000000..3389649
--- /dev/null
+++ b/usr.bin/make/unit-tests/modts.exp
@@ -0,0 +1,39 @@
+LIST="one two three four five six"
+LIST:ts,="one,two,three,four,five,six"
+LIST:ts/:tu="ONE/TWO/THREE/FOUR/FIVE/SIX"
+LIST:ts::tu="ONE:TWO:THREE:FOUR:FIVE:SIX"
+LIST:ts:tu="ONETWOTHREEFOURFIVESIX"
+LIST:tu:ts/="ONE/TWO/THREE/FOUR/FIVE/SIX"
+LIST:ts:="one:two:three:four:five:six"
+LIST:ts="onetwothreefourfivesix"
+LIST:ts:S/two/2/="one2threefourfivesix"
+LIST:S/two/2/:ts="one2threefourfivesix"
+LIST:ts/:S/two/2/="one/2/three/four/five/six"
+Pretend the '/' in '/n' etc. below are back-slashes.
+LIST:ts/n="one
+two
+three
+four
+five
+six"
+LIST:ts/t="one two three four five six"
+LIST:ts/012:tu="ONE
+TWO
+THREE
+FOUR
+FIVE
+SIX"
+LIST:ts/xa:tu="ONE
+TWO
+THREE
+FOUR
+FIVE
+SIX"
+make: Bad modifier `:tx' for LIST
+LIST:tx="}"
+make: Bad modifier `:ts\X' for LIST
+LIST:ts/x:tu="\X:tu}"
+FU_mod-ts="a/b/cool"
+FU_mod-ts:ts:T="cool" == cool?
+B.${AAA:ts}="Baaa" == Baaa?
+exit status 0
diff --git a/usr.bin/make/unit-tests/modts.mk b/usr.bin/make/unit-tests/modts.mk
new file mode 100644
index 0000000..254aa42
--- /dev/null
+++ b/usr.bin/make/unit-tests/modts.mk
@@ -0,0 +1,44 @@
+
+LIST= one two three
+LIST+= four five six
+
+FU_mod-ts = a / b / cool
+
+AAA= a a a
+B.aaa= Baaa
+
+all: mod-ts
+
+# Use print or printf iff they are builtin.
+# XXX note that this causes problems, when make decides
+# there is no need to use a shell, so avoid where possible.
+.if ${type print 2> /dev/null || echo:L:sh:Mbuiltin} != ""
+PRINT= print -r --
+.elif ${type printf 2> /dev/null || echo:L:sh:Mbuiltin} != ""
+PRINT= printf '%s\n'
+.else
+PRINT= echo
+.endif
+
+mod-ts:
+ @echo 'LIST="${LIST}"'
+ @echo 'LIST:ts,="${LIST:ts,}"'
+ @echo 'LIST:ts/:tu="${LIST:ts/:tu}"'
+ @echo 'LIST:ts::tu="${LIST:ts::tu}"'
+ @echo 'LIST:ts:tu="${LIST:ts:tu}"'
+ @echo 'LIST:tu:ts/="${LIST:tu:ts/}"'
+ @echo 'LIST:ts:="${LIST:ts:}"'
+ @echo 'LIST:ts="${LIST:ts}"'
+ @echo 'LIST:ts:S/two/2/="${LIST:ts:S/two/2/}"'
+ @echo 'LIST:S/two/2/:ts="${LIST:S/two/2/:ts}"'
+ @echo 'LIST:ts/:S/two/2/="${LIST:ts/:S/two/2/}"'
+ @echo "Pretend the '/' in '/n' etc. below are back-slashes."
+ @${PRINT} 'LIST:ts/n="${LIST:ts\n}"'
+ @${PRINT} 'LIST:ts/t="${LIST:ts\t}"'
+ @${PRINT} 'LIST:ts/012:tu="${LIST:ts\012:tu}"'
+ @${PRINT} 'LIST:ts/xa:tu="${LIST:ts\xa:tu}"'
+ @${PRINT} 'LIST:tx="${LIST:tx}"'
+ @${PRINT} 'LIST:ts/x:tu="${LIST:ts\X:tu}"'
+ @${PRINT} 'FU_$@="${FU_${@:ts}:ts}"'
+ @${PRINT} 'FU_$@:ts:T="${FU_${@:ts}:ts:T}" == cool?'
+ @${PRINT} 'B.$${AAA:ts}="${B.${AAA:ts}}" == Baaa?'
diff --git a/usr.bin/make/unit-tests/modword.exp b/usr.bin/make/unit-tests/modword.exp
new file mode 100644
index 0000000..258d7ea
--- /dev/null
+++ b/usr.bin/make/unit-tests/modword.exp
@@ -0,0 +1,122 @@
+make: Bad modifier `:[]' for LIST
+LIST:[]="" is an error
+LIST:[0]="one two three four five six"
+LIST:[0x0]="one two three four five six"
+LIST:[000]="one two three four five six"
+LIST:[*]="one two three four five six"
+LIST:[@]="one two three four five six"
+LIST:[0]:C/ /,/="one,two three four five six"
+LIST:[0]:C/ /,/g="one,two,three,four,five,six"
+LIST:[0]:C/ /,/1g="one,two,three,four,five,six"
+LIST:[*]:C/ /,/="one,two three four five six"
+LIST:[*]:C/ /,/g="one,two,three,four,five,six"
+LIST:[*]:C/ /,/1g="one,two,three,four,five,six"
+LIST:[@]:C/ /,/="one two three four five six"
+LIST:[@]:C/ /,/g="one two three four five six"
+LIST:[@]:C/ /,/1g="one two three four five six"
+LIST:[@]:[0]:C/ /,/="one,two three four five six"
+LIST:[0]:[@]:C/ /,/="one two three four five six"
+LIST:[@]:[*]:C/ /,/="one,two three four five six"
+LIST:[*]:[@]:C/ /,/="one two three four five six"
+EMPTY=""
+EMPTY:[#]="1" == 1 ?
+ESCAPEDSPACE="\ "
+ESCAPEDSPACE:[#]="1" == 1 ?
+REALLYSPACE=" "
+REALLYSPACE:[#]="1" == 1 ?
+LIST:[#]="6"
+LIST:[0]:[#]="1" == 1 ?
+LIST:[*]:[#]="1" == 1 ?
+LIST:[@]:[#]="6"
+LIST:[1]:[#]="1"
+LIST:[1..3]:[#]="3"
+EMPTY:[1]=""
+ESCAPEDSPACE="\ "
+ESCAPEDSPACE:[1]="\ "
+REALLYSPACE=" "
+REALLYSPACE:[1]="" == "" ?
+REALLYSPACE:[*]:[1]=" " == " " ?
+LIST:[1]="one"
+make: Bad modifier `:[1.]' for LIST
+LIST:[1.]="" is an error
+make: Bad modifier `:[1].' for LIST
+LIST:[1].="}" is an error
+LIST:[2]="two"
+LIST:[6]="six"
+LIST:[7]=""
+LIST:[999]=""
+make: Bad modifier `:[-]' for LIST
+LIST:[-]="" is an error
+make: Bad modifier `:[--]' for LIST
+LIST:[--]="" is an error
+LIST:[-1]="six"
+LIST:[-2]="five"
+LIST:[-6]="one"
+LIST:[-7]=""
+LIST:[-999]=""
+LONGLIST:[17]="17"
+LONGLIST:[0x11]="17"
+LONGLIST:[021]="17"
+LIST:[0]:[1]="one two three four five six"
+LIST:[*]:[1]="one two three four five six"
+LIST:[@]:[1]="one"
+LIST:[0]:[2]=""
+LIST:[*]:[2]=""
+LIST:[@]:[2]="two"
+LIST:[*]:C/ /,/:[2]=""
+LIST:[*]:C/ /,/:[*]:[2]=""
+LIST:[*]:C/ /,/:[@]:[2]="three"
+make: Bad modifier `:[1.]' for LIST
+LIST:[1.]="" is an error
+make: Bad modifier `:[1..]' for LIST
+LIST:[1..]="" is an error
+LIST:[1..1]="one"
+make: Bad modifier `:[1..1.]' for LIST
+LIST:[1..1.]="" is an error
+LIST:[1..2]="one two"
+LIST:[2..1]="two one"
+LIST:[3..-2]="three four five"
+LIST:[-4..4]="three four"
+make: Bad modifier `:[0..1]' for LIST
+LIST:[0..1]="" is an error
+make: Bad modifier `:[-1..0]' for LIST
+LIST:[-1..0]="" is an error
+LIST:[-1..1]="six five four three two one"
+LIST:[0..0]="one two three four five six"
+LIST:[3..99]="three four five six"
+LIST:[-3..-99]="four three two one"
+LIST:[-99..-3]="one two three four"
+HASH="#" == "#" ?
+LIST:[${HASH}]="6"
+LIST:[${ZERO}]="one two three four five six"
+LIST:[${ZERO}x${ONE}]="one"
+LIST:[${ONE}]="one"
+LIST:[${MINUSONE}]="six"
+LIST:[${STAR}]="one two three four five six"
+LIST:[${AT}]="one two three four five six"
+make: Bad modifier `:[${EMPTY' for LIST
+LIST:[${EMPTY}]="" is an error
+LIST:[${LONGLIST:[21]:S/2//}]="one"
+LIST:[${LIST:[#]}]="six"
+LIST:[${LIST:[${HASH}]}]="six"
+LIST:S/ /,/="one two three four five six"
+LIST:S/ /,/W="one,two three four five six"
+LIST:S/ /,/gW="one,two,three,four,five,six"
+EMPTY:S/^/,/=","
+EMPTY:S/^/,/W=","
+LIST:C/ /,/="one two three four five six"
+LIST:C/ /,/W="one,two three four five six"
+LIST:C/ /,/gW="one,two,three,four,five,six"
+EMPTY:C/^/,/=","
+EMPTY:C/^/,/W=","
+LIST:tW="one two three four five six"
+LIST:tw="one two three four five six"
+LIST:tW:C/ /,/="one,two three four five six"
+LIST:tW:C/ /,/g="one,two,three,four,five,six"
+LIST:tW:C/ /,/1g="one,two,three,four,five,six"
+LIST:tw:C/ /,/="one two three four five six"
+LIST:tw:C/ /,/g="one two three four five six"
+LIST:tw:C/ /,/1g="one two three four five six"
+LIST:tw:tW:C/ /,/="one,two three four five six"
+LIST:tW:tw:C/ /,/="one two three four five six"
+exit status 0
diff --git a/usr.bin/make/unit-tests/modword.mk b/usr.bin/make/unit-tests/modword.mk
new file mode 100644
index 0000000..00a56de
--- /dev/null
+++ b/usr.bin/make/unit-tests/modword.mk
@@ -0,0 +1,151 @@
+# $Id: modword.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+#
+# Test behaviour of new :[] modifier
+
+all: mod-squarebrackets mod-S-W mod-C-W mod-tW-tw
+
+LIST= one two three
+LIST+= four five six
+LONGLIST= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
+
+EMPTY= # the space should be ignored
+ESCAPEDSPACE=\ # escaped space before the '#'
+REALLYSPACE:=${EMPTY:C/^/ /W}
+HASH= \#
+AT= @
+STAR= *
+ZERO= 0
+ONE= 1
+MINUSONE= -1
+
+mod-squarebrackets: mod-squarebrackets-0-star-at \
+ mod-squarebrackets-hash \
+ mod-squarebrackets-n \
+ mod-squarebrackets-start-end \
+ mod-squarebrackets-nested
+
+mod-squarebrackets-0-star-at:
+ @echo 'LIST:[]="${LIST:[]}" is an error'
+ @echo 'LIST:[0]="${LIST:[0]}"'
+ @echo 'LIST:[0x0]="${LIST:[0x0]}"'
+ @echo 'LIST:[000]="${LIST:[000]}"'
+ @echo 'LIST:[*]="${LIST:[*]}"'
+ @echo 'LIST:[@]="${LIST:[@]}"'
+ @echo 'LIST:[0]:C/ /,/="${LIST:[0]:C/ /,/}"'
+ @echo 'LIST:[0]:C/ /,/g="${LIST:[0]:C/ /,/g}"'
+ @echo 'LIST:[0]:C/ /,/1g="${LIST:[0]:C/ /,/1g}"'
+ @echo 'LIST:[*]:C/ /,/="${LIST:[*]:C/ /,/}"'
+ @echo 'LIST:[*]:C/ /,/g="${LIST:[*]:C/ /,/g}"'
+ @echo 'LIST:[*]:C/ /,/1g="${LIST:[*]:C/ /,/1g}"'
+ @echo 'LIST:[@]:C/ /,/="${LIST:[@]:C/ /,/}"'
+ @echo 'LIST:[@]:C/ /,/g="${LIST:[@]:C/ /,/g}"'
+ @echo 'LIST:[@]:C/ /,/1g="${LIST:[@]:C/ /,/1g}"'
+ @echo 'LIST:[@]:[0]:C/ /,/="${LIST:[@]:[0]:C/ /,/}"'
+ @echo 'LIST:[0]:[@]:C/ /,/="${LIST:[0]:[@]:C/ /,/}"'
+ @echo 'LIST:[@]:[*]:C/ /,/="${LIST:[@]:[*]:C/ /,/}"'
+ @echo 'LIST:[*]:[@]:C/ /,/="${LIST:[*]:[@]:C/ /,/}"'
+
+mod-squarebrackets-hash:
+ @echo 'EMPTY="${EMPTY}"'
+ @echo 'EMPTY:[#]="${EMPTY:[#]}" == 1 ?'
+ @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"'
+ @echo 'ESCAPEDSPACE:[#]="${ESCAPEDSPACE:[#]}" == 1 ?'
+ @echo 'REALLYSPACE="${REALLYSPACE}"'
+ @echo 'REALLYSPACE:[#]="${REALLYSPACE:[#]}" == 1 ?'
+ @echo 'LIST:[#]="${LIST:[#]}"'
+ @echo 'LIST:[0]:[#]="${LIST:[0]:[#]}" == 1 ?'
+ @echo 'LIST:[*]:[#]="${LIST:[*]:[#]}" == 1 ?'
+ @echo 'LIST:[@]:[#]="${LIST:[@]:[#]}"'
+ @echo 'LIST:[1]:[#]="${LIST:[1]:[#]}"'
+ @echo 'LIST:[1..3]:[#]="${LIST:[1..3]:[#]}"'
+
+mod-squarebrackets-n:
+ @echo 'EMPTY:[1]="${EMPTY:[1]}"'
+ @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"'
+ @echo 'ESCAPEDSPACE:[1]="${ESCAPEDSPACE:[1]}"'
+ @echo 'REALLYSPACE="${REALLYSPACE}"'
+ @echo 'REALLYSPACE:[1]="${REALLYSPACE:[1]}" == "" ?'
+ @echo 'REALLYSPACE:[*]:[1]="${REALLYSPACE:[*]:[1]}" == " " ?'
+ @echo 'LIST:[1]="${LIST:[1]}"'
+ @echo 'LIST:[1.]="${LIST:[1.]}" is an error'
+ @echo 'LIST:[1].="${LIST:[1].}" is an error'
+ @echo 'LIST:[2]="${LIST:[2]}"'
+ @echo 'LIST:[6]="${LIST:[6]}"'
+ @echo 'LIST:[7]="${LIST:[7]}"'
+ @echo 'LIST:[999]="${LIST:[999]}"'
+ @echo 'LIST:[-]="${LIST:[-]}" is an error'
+ @echo 'LIST:[--]="${LIST:[--]}" is an error'
+ @echo 'LIST:[-1]="${LIST:[-1]}"'
+ @echo 'LIST:[-2]="${LIST:[-2]}"'
+ @echo 'LIST:[-6]="${LIST:[-6]}"'
+ @echo 'LIST:[-7]="${LIST:[-7]}"'
+ @echo 'LIST:[-999]="${LIST:[-999]}"'
+ @echo 'LONGLIST:[17]="${LONGLIST:[17]}"'
+ @echo 'LONGLIST:[0x11]="${LONGLIST:[0x11]}"'
+ @echo 'LONGLIST:[021]="${LONGLIST:[021]}"'
+ @echo 'LIST:[0]:[1]="${LIST:[0]:[1]}"'
+ @echo 'LIST:[*]:[1]="${LIST:[*]:[1]}"'
+ @echo 'LIST:[@]:[1]="${LIST:[@]:[1]}"'
+ @echo 'LIST:[0]:[2]="${LIST:[0]:[2]}"'
+ @echo 'LIST:[*]:[2]="${LIST:[*]:[2]}"'
+ @echo 'LIST:[@]:[2]="${LIST:[@]:[2]}"'
+ @echo 'LIST:[*]:C/ /,/:[2]="${LIST:[*]:C/ /,/:[2]}"'
+ @echo 'LIST:[*]:C/ /,/:[*]:[2]="${LIST:[*]:C/ /,/:[*]:[2]}"'
+ @echo 'LIST:[*]:C/ /,/:[@]:[2]="${LIST:[*]:C/ /,/:[@]:[2]}"'
+
+mod-squarebrackets-start-end:
+ @echo 'LIST:[1.]="${LIST:[1.]}" is an error'
+ @echo 'LIST:[1..]="${LIST:[1..]}" is an error'
+ @echo 'LIST:[1..1]="${LIST:[1..1]}"'
+ @echo 'LIST:[1..1.]="${LIST:[1..1.]}" is an error'
+ @echo 'LIST:[1..2]="${LIST:[1..2]}"'
+ @echo 'LIST:[2..1]="${LIST:[2..1]}"'
+ @echo 'LIST:[3..-2]="${LIST:[3..-2]}"'
+ @echo 'LIST:[-4..4]="${LIST:[-4..4]}"'
+ @echo 'LIST:[0..1]="${LIST:[0..1]}" is an error'
+ @echo 'LIST:[-1..0]="${LIST:[-1..0]}" is an error'
+ @echo 'LIST:[-1..1]="${LIST:[-1..1]}"'
+ @echo 'LIST:[0..0]="${LIST:[0..0]}"'
+ @echo 'LIST:[3..99]="${LIST:[3..99]}"'
+ @echo 'LIST:[-3..-99]="${LIST:[-3..-99]}"'
+ @echo 'LIST:[-99..-3]="${LIST:[-99..-3]}"'
+
+mod-squarebrackets-nested:
+ @echo 'HASH="${HASH}" == "#" ?'
+ @echo 'LIST:[$${HASH}]="${LIST:[${HASH}]}"'
+ @echo 'LIST:[$${ZERO}]="${LIST:[${ZERO}]}"'
+ @echo 'LIST:[$${ZERO}x$${ONE}]="${LIST:[${ZERO}x${ONE}]}"'
+ @echo 'LIST:[$${ONE}]="${LIST:[${ONE}]}"'
+ @echo 'LIST:[$${MINUSONE}]="${LIST:[${MINUSONE}]}"'
+ @echo 'LIST:[$${STAR}]="${LIST:[${STAR}]}"'
+ @echo 'LIST:[$${AT}]="${LIST:[${AT}]}"'
+ @echo 'LIST:[$${EMPTY}]="${LIST:[${EMPTY}]}" is an error'
+ @echo 'LIST:[$${LONGLIST:[21]:S/2//}]="${LIST:[${LONGLIST:[21]:S/2//}]}"'
+ @echo 'LIST:[$${LIST:[#]}]="${LIST:[${LIST:[#]}]}"'
+ @echo 'LIST:[$${LIST:[$${HASH}]}]="${LIST:[${LIST:[${HASH}]}]}"'
+
+mod-C-W:
+ @echo 'LIST:C/ /,/="${LIST:C/ /,/}"'
+ @echo 'LIST:C/ /,/W="${LIST:C/ /,/W}"'
+ @echo 'LIST:C/ /,/gW="${LIST:C/ /,/gW}"'
+ @echo 'EMPTY:C/^/,/="${EMPTY:C/^/,/}"'
+ @echo 'EMPTY:C/^/,/W="${EMPTY:C/^/,/W}"'
+
+mod-S-W:
+ @echo 'LIST:S/ /,/="${LIST:S/ /,/}"'
+ @echo 'LIST:S/ /,/W="${LIST:S/ /,/W}"'
+ @echo 'LIST:S/ /,/gW="${LIST:S/ /,/gW}"'
+ @echo 'EMPTY:S/^/,/="${EMPTY:S/^/,/}"'
+ @echo 'EMPTY:S/^/,/W="${EMPTY:S/^/,/W}"'
+
+mod-tW-tw:
+ @echo 'LIST:tW="${LIST:tW}"'
+ @echo 'LIST:tw="${LIST:tw}"'
+ @echo 'LIST:tW:C/ /,/="${LIST:tW:C/ /,/}"'
+ @echo 'LIST:tW:C/ /,/g="${LIST:tW:C/ /,/g}"'
+ @echo 'LIST:tW:C/ /,/1g="${LIST:tW:C/ /,/1g}"'
+ @echo 'LIST:tw:C/ /,/="${LIST:tw:C/ /,/}"'
+ @echo 'LIST:tw:C/ /,/g="${LIST:tw:C/ /,/g}"'
+ @echo 'LIST:tw:C/ /,/1g="${LIST:tw:C/ /,/1g}"'
+ @echo 'LIST:tw:tW:C/ /,/="${LIST:tw:tW:C/ /,/}"'
+ @echo 'LIST:tW:tw:C/ /,/="${LIST:tW:tw:C/ /,/}"'
diff --git a/usr.bin/make/unit-tests/order.exp b/usr.bin/make/unit-tests/order.exp
new file mode 100644
index 0000000..d876914
--- /dev/null
+++ b/usr.bin/make/unit-tests/order.exp
@@ -0,0 +1,4 @@
+Making the.c
+Making the.h
+Making the.o from the.h the.c
+exit status 0
diff --git a/usr.bin/make/unit-tests/order.mk b/usr.bin/make/unit-tests/order.mk
new file mode 100644
index 0000000..f90b627
--- /dev/null
+++ b/usr.bin/make/unit-tests/order.mk
@@ -0,0 +1,20 @@
+# $NetBSD: order.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+# Test that .ORDER is handled correctly.
+# The explicit dependency the.o: the.h will make us examine the.h
+# the .ORDER will prevent us building it immediately,
+# we should then examine the.c rather than stop.
+
+all: the.o
+
+.ORDER: the.c the.h
+
+the.c the.h:
+ @echo Making $@
+
+.SUFFIXES: .o .c
+
+.c.o:
+ @echo Making $@ from $?
+
+the.o: the.h
diff --git a/usr.bin/make/unit-tests/phony-end.exp b/usr.bin/make/unit-tests/phony-end.exp
new file mode 100644
index 0000000..c3c517c
--- /dev/null
+++ b/usr.bin/make/unit-tests/phony-end.exp
@@ -0,0 +1,6 @@
+.TARGET="phony" .PREFIX="phony" .IMPSRC=""
+.TARGET="all" .PREFIX="all" .IMPSRC="phony"
+.TARGET="ok" .PREFIX="ok" .IMPSRC=""
+.TARGET="also.ok" .PREFIX="also.ok" .IMPSRC=""
+.TARGET="bug" .PREFIX="bug" .IMPSRC=""
+exit status 0
diff --git a/usr.bin/make/unit-tests/phony-end.mk b/usr.bin/make/unit-tests/phony-end.mk
new file mode 100644
index 0000000..92cc0e6
--- /dev/null
+++ b/usr.bin/make/unit-tests/phony-end.mk
@@ -0,0 +1,9 @@
+# $Id: phony-end.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+all ok also.ok bug phony:
+ @echo '${.TARGET .PREFIX .IMPSRC:L:@v@$v="${$v}"@}'
+
+.END: ok also.ok bug
+
+phony bug: .PHONY
+all: phony
diff --git a/usr.bin/make/unit-tests/posix.exp b/usr.bin/make/unit-tests/posix.exp
new file mode 100644
index 0000000..7e74cab
--- /dev/null
+++ b/usr.bin/make/unit-tests/posix.exp
@@ -0,0 +1,23 @@
+Posix says we should execute the command as if run by system(3)
+Expect 'Hello,' and 'World!'
+Hello,
+World!
+a command
+a command prefixed by '+' executes even with -n
+another command
+make -n
+echo a command
+echo "a command prefixed by '+' executes even with -n"
+a command prefixed by '+' executes even with -n
+echo another command
+make -n -j1
+{ echo a command
+} || exit $?
+echo "a command prefixed by '+' executes even with -n"
+a command prefixed by '+' executes even with -n
+{ echo another command
+} || exit $?
+Now we expect an error...
+*** Error code 1 (continuing)
+`all' not remade because of errors.
+exit status 0
diff --git a/usr.bin/make/unit-tests/posix.mk b/usr.bin/make/unit-tests/posix.mk
new file mode 100644
index 0000000..a73e2e5
--- /dev/null
+++ b/usr.bin/make/unit-tests/posix.mk
@@ -0,0 +1,24 @@
+# $Id: posix.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+all: x plus subs err
+
+x:
+ @echo "Posix says we should execute the command as if run by system(3)"
+ @echo "Expect 'Hello,' and 'World!'"
+ @echo Hello,; false; echo "World!"
+
+plus:
+ @echo a command
+ +@echo "a command prefixed by '+' executes even with -n"
+ @echo another command
+
+subs:
+ @echo make -n
+ @${.MAKE} -f ${MAKEFILE} -n plus
+ @echo make -n -j1
+ @${.MAKE} -f ${MAKEFILE} -n -j1 plus
+
+err:
+ @(echo Now we expect an error...; exit 1)
+ @echo "Oops! you shouldn't see this!"
+
diff --git a/usr.bin/make/unit-tests/posix1.exp b/usr.bin/make/unit-tests/posix1.exp
new file mode 100644
index 0000000..fa1f15d
--- /dev/null
+++ b/usr.bin/make/unit-tests/posix1.exp
@@ -0,0 +1,186 @@
+${VAR} = "foo bar baz"
+a
+b
+c
+foo baR baz, bar baz, foo bar baz, fooadd baradd bazadd
+mkdir -p 'dir'
+touch 'dir/obj_1.h'
+mkdir -p 'dir'
+printf '#include "obj_1.h"\nconst char* obj_1 = "dir/obj_1.c";\n' \
+ >'dir/obj_1.c'
+Local variables
+ ${@}="dir/obj_1.o" ${<}="dir/obj_1.c"
+ ${*}="dir/obj_1" ${?}="dir/obj_1.h dir/obj_1.c"
+ ${%}=""
+
+Directory and filename parts of local variables
+ ${@D}="dir" ${@F}="obj_1.o"
+ ${<D}="dir" ${<F}="obj_1.c"
+ ${*D}="dir" ${*F}="obj_1"
+ ${?D}="dir dir" ${?F}="obj_1.h obj_1.c"
+ ${%D}="" ${%F}=""
+
+Local variable substitutions
+ ${@:.o=}="dir/obj_1" ${<:.c=.C}="dir/obj_1.C"
+ ${*:=.h}="dir/obj_1.h" ${?:.h=.H}="dir/obj_1.H dir/obj_1.c"
+ ${%:=}=""
+
+Target with suffix transformations
+ ${@D:=append}="dirappend"
+ ${@F:.o=.O}="obj_1.O"
+
+ Implied source with suffix transformations
+ ${<D:r=rr}="dirr"
+ ${<F:.c=.C}="obj_1.C"
+
+ Suffixless target with suffix transformations
+ ${*D:.=dot}="dir"
+ ${*F:.a=}="obj_1"
+
+ Out-of-date dependencies with suffix transformations
+ ${?D:ir=}="d d"
+ ${?F:.h=.H}="obj_1.H obj_1.c"
+
+ Member with suffix transformations
+ ${%D:.=}=""
+ ${%F:${VAR2}=${VAR}}=""
+
+cc -c -o 'dir/obj_1.o' 'dir/obj_1.c'
+mkdir -p '.'
+touch 'dummy'
+Local variables
+ ${@}="lib.a" ${<}="dir/obj_1.o"
+ ${*}="obj1" ${?}="dir/obj_1.o dummy"
+ ${%}="obj1.o"
+
+Directory and filename parts of local variables
+ ${@D}="." ${@F}="lib.a"
+ ${<D}="dir" ${<F}="obj_1.o"
+ ${*D}="." ${*F}="obj1"
+ ${?D}="dir ." ${?F}="obj_1.o dummy"
+ ${%D}="." ${%F}="obj1.o"
+
+Local variable substitutions
+ ${@:.o=}="lib.a" ${<:.c=.C}="dir/obj_1.o"
+ ${*:=.h}="obj1.h" ${?:.h=.H}="dir/obj_1.o dummy"
+ ${%:=}="obj1.o"
+
+Target with suffix transformations
+ ${@D:=append}=".append"
+ ${@F:.o=.O}="lib.a"
+
+ Implied source with suffix transformations
+ ${<D:r=rr}="dirr"
+ ${<F:.c=.C}="obj_1.o"
+
+ Suffixless target with suffix transformations
+ ${*D:.=dot}="dot"
+ ${*F:.a=}="obj1"
+
+ Out-of-date dependencies with suffix transformations
+ ${?D:ir=}="d ."
+ ${?F:.h=.H}="obj_1.o dummy"
+
+ Member with suffix transformations
+ ${%D:.=}=""
+ ${%F:${VAR2}=${VAR}}="obj1foo bar baz"
+
+cp 'dir/obj_1.o' 'obj1.o'
+ar -rcv 'lib.a' 'obj1.o'
+a - obj1.o
+rm -f 'obj1.o'
+mkdir -p '.'
+printf '#include "obj_2.h"\nconst char* obj_2 = "obj_2.c";\n' \
+ >'obj_2.c'
+mkdir -p '.'
+touch 'obj_2.h'
+Local variables
+ ${@}="obj2.o" ${<}="obj_2.c"
+ ${*}="obj2" ${?}="obj_2.c obj_2.h dir/obj_1.h"
+ ${%}=""
+
+Directory and filename parts of local variables
+ ${@D}="." ${@F}="obj2.o"
+ ${<D}="." ${<F}="obj_2.c"
+ ${*D}="." ${*F}="obj2"
+ ${?D}=". . dir" ${?F}="obj_2.c obj_2.h obj_1.h"
+ ${%D}="" ${%F}=""
+
+Local variable substitutions
+ ${@:.o=}="obj2" ${<:.c=.C}="obj_2.C"
+ ${*:=.h}="obj2.h" ${?:.h=.H}="obj_2.c obj_2.H dir/obj_1.H"
+ ${%:=}=""
+
+Target with suffix transformations
+ ${@D:=append}=".append"
+ ${@F:.o=.O}="obj2.O"
+
+ Implied source with suffix transformations
+ ${<D:r=rr}="."
+ ${<F:.c=.C}="obj_2.C"
+
+ Suffixless target with suffix transformations
+ ${*D:.=dot}="dot"
+ ${*F:.a=}="obj2"
+
+ Out-of-date dependencies with suffix transformations
+ ${?D:ir=}=". . d"
+ ${?F:.h=.H}="obj_2.c obj_2.H obj_1.H"
+
+ Member with suffix transformations
+ ${%D:.=}=""
+ ${%F:${VAR2}=${VAR}}=""
+
+cc -c -o 'obj2.o' 'obj_2.c'
+ar -rcv 'lib.a' 'obj2.o'
+a - obj2.o
+mkdir -p '.'
+touch 'obj3.h'
+mkdir -p 'dir'
+touch 'dir/dummy'
+mkdir -p '.'
+printf '#include "obj3.h"\nconst char* obj3 = "obj3.c";\n' \
+ >'obj3.c'
+Local variables
+ ${@}="lib.a" ${<}="obj3.c"
+ ${*}="obj3" ${?}="obj3.h dir/dummy obj3.c"
+ ${%}="obj3.o"
+
+Directory and filename parts of local variables
+ ${@D}="." ${@F}="lib.a"
+ ${<D}="." ${<F}="obj3.c"
+ ${*D}="." ${*F}="obj3"
+ ${?D}=". dir ." ${?F}="obj3.h dummy obj3.c"
+ ${%D}="." ${%F}="obj3.o"
+
+Local variable substitutions
+ ${@:.o=}="lib.a" ${<:.c=.C}="obj3.C"
+ ${*:=.h}="obj3.h" ${?:.h=.H}="obj3.H dir/dummy obj3.c"
+ ${%:=}="obj3.o"
+
+Target with suffix transformations
+ ${@D:=append}=".append"
+ ${@F:.o=.O}="lib.a"
+
+ Implied source with suffix transformations
+ ${<D:r=rr}="."
+ ${<F:.c=.C}="obj3.C"
+
+ Suffixless target with suffix transformations
+ ${*D:.=dot}="dot"
+ ${*F:.a=}="obj3"
+
+ Out-of-date dependencies with suffix transformations
+ ${?D:ir=}=". d ."
+ ${?F:.h=.H}="obj3.H dummy obj3.c"
+
+ Member with suffix transformations
+ ${%D:.=}=""
+ ${%F:${VAR2}=${VAR}}="obj3foo bar baz"
+
+cc -c -o 'obj3.o' 'obj3.c'
+ar -rcv 'lib.a' 'obj3.o'
+a - obj3.o
+rm -f 'obj3.o'
+ar -s 'lib.a'
+exit status 0
diff --git a/usr.bin/make/unit-tests/posix1.mk b/usr.bin/make/unit-tests/posix1.mk
new file mode 100644
index 0000000..1bf6a56
--- /dev/null
+++ b/usr.bin/make/unit-tests/posix1.mk
@@ -0,0 +1,184 @@
+# $NetBSD: posix1.mk,v 1.3 2014/08/30 22:21:08 sjg Exp $
+
+# Keep the default suffixes from interfering, just in case.
+.SUFFIXES:
+
+all: line-continuations suffix-substitution localvars
+
+# we need to clean for repeatable results
+.BEGIN: clean
+clean:
+ @rm -f lib.a dir/* dummy obj*
+
+#
+# Line continuations
+#
+
+# Escaped newlines and leading whitespace from the next line are replaced
+# with single space, except in commands, where the escape and the newline
+# are retained, but a single leading tab (if any) from the next line is
+# removed. (PR 49085)
+# Expect:
+# ${VAR} = "foo bar baz"
+# a
+# b
+# c
+VAR = foo\
+\
+ bar\
+ baz
+
+line-continuations:
+ @echo '$${VAR} = "${VAR}"'
+ @echo 'aXbXc' | sed -e 's/X/\
+ /g'
+
+
+#
+# Suffix substitution
+#
+
+# The only variable modifier accepted by POSIX.
+# ${VAR:s1=s2}: replace s1, if found, with s2 at end of each word in
+# ${VAR}. s1 and s2 may contain macro expansions.
+# Expect: foo baR baz, bar baz, foo bar baz, fooadd baradd bazadd
+suffix-substitution:
+ @echo '${VAR:r=R}, ${VAR:foo=}, ${VAR:not_there=wrong}, ${VAR:=add}'
+
+
+#
+# Local variables: regular forms, D/F forms and suffix substitution.
+#
+
+# In the past substitutions did not work with the D/F forms and those
+# forms were not available for $?. (PR 49085)
+
+ARFLAGS = -rcv
+
+localvars: lib.a
+
+# $@ = target or archive name $< = implied source
+# $* = target without suffix $? = sources newer than target
+# $% = archive member name
+LOCALS = \
+ "Local variables\n\
+ \$${@}=\"${@}\" \$${<}=\"${<}\"\n\
+ \$${*}=\"${*}\" \$${?}=\"${?}\"\n\
+ \$${%%}=\"${%}\"\n\n"
+
+# $XD = directory part of X $XF = file part of X
+# X is one of the local variables.
+LOCAL_ALTERNATIVES = \
+ "Directory and filename parts of local variables\n\
+ \$${@D}=\"${@D}\" \$${@F}=\"${@F}\"\n\
+ \$${<D}=\"${<D}\" \$${<F}=\"${<F}\"\n\
+ \$${*D}=\"${*D}\" \$${*F}=\"${*F}\"\n\
+ \$${?D}=\"${?D}\" \$${?F}=\"${?F}\"\n\
+ \$${%%D}=\"${%D}\" \$${%%F}=\"${%F}\"\n\n"
+
+# Do all kinds of meaningless substitutions on local variables to see
+# if they work. Add, remove and replace things.
+VAR2 = .o
+VAR3 = foo
+LOCAL_SUBSTITUTIONS = \
+ "Local variable substitutions\n\
+ \$${@:.o=}=\"${@:.o=}\" \$${<:.c=.C}=\"${<:.c=.C}\"\n\
+ \$${*:=.h}=\"${*:=.h}\" \$${?:.h=.H}=\"${?:.h=.H}\"\n\
+ \$${%%:=}=\"${%:=}\"\n\n"
+
+LOCAL_ALTERNATIVE_SUBSTITUTIONS = \
+ "Target with suffix transformations\n\
+ \$${@D:=append}=\"${@D:=append}\"\n\
+ \$${@F:.o=.O}=\"${@F:.o=.O}\"\n\
+ \n\
+ Implied source with suffix transformations\n\
+ \$${<D:r=rr}=\"${<D:r=rr}\"\n\
+ \$${<F:.c=.C}=\"${<F:.c=.C}\"\n\
+ \n\
+ Suffixless target with suffix transformations\n\
+ \$${*D:.=dot}=\"${*D:.=dot}\"\n\
+ \$${*F:.a=}=\"${*F:.a=}\"\n\
+ \n\
+ Out-of-date dependencies with suffix transformations\n\
+ \$${?D:ir=}=\"${?D:ir=}\"\n\
+ \$${?F:.h=.H}=\"${?F:.h=.H}\"\n\
+ \n\
+ Member with suffix transformations\n\
+ \$${%%D:.=}=\"${%D:.=}\"\n\
+ \$${%%F:\$${VAR2}=\$${VAR}}=\"${%F:${VAR2}=${VAR}}\"\n\n"
+
+.SUFFIXES: .c .o .a
+
+# The system makefiles make the .c.a rule .PRECIOUS with a special source,
+# but such a thing is not POSIX compatible. It's also somewhat useless
+# in a test makefile.
+.c.a:
+ @printf ${LOCALS}
+ @printf ${LOCAL_ALTERNATIVES}
+ @printf ${LOCAL_SUBSTITUTIONS}
+ @printf ${LOCAL_ALTERNATIVE_SUBSTITUTIONS}
+ cc -c -o '${%}' '${<}'
+ ar ${ARFLAGS} '${@}' '${%}'
+ rm -f '${%}'
+
+.c.o:
+ @printf ${LOCALS}
+ @printf ${LOCAL_ALTERNATIVES}
+ @printf ${LOCAL_SUBSTITUTIONS}
+ @printf ${LOCAL_ALTERNATIVE_SUBSTITUTIONS}
+ cc -c -o '${@}' '${<}'
+
+# Some of these rules are padded with useless extra dependencies just so
+# that ${?} has more than one file.
+
+lib.a: lib.a(obj1.o) lib.a(obj2.o) lib.a(obj3.o)
+ ar -s '${@}'
+
+# Explicit rule where the dependency is an inferred file. The dependency
+# object's name differs from the member's because there was a bug which
+# forced a dependency on member even when no such dependency was specified
+# (PR 49086).
+lib.a(obj1.o): dir/obj_1.o dummy
+ @printf ${LOCALS}
+ @printf ${LOCAL_ALTERNATIVES}
+ @printf ${LOCAL_SUBSTITUTIONS}
+ @printf ${LOCAL_ALTERNATIVE_SUBSTITUTIONS}
+ cp 'dir/obj_1.o' '$%'
+ ar ${ARFLAGS} '${@}' '$%'
+ rm -f '$%'
+
+# Excplicit rule where the dependency also has an explicit rule.
+lib.a(obj2.o): obj2.o
+ ar ${ARFLAGS} '${@}' '${%}'
+
+# Use .c.a inference with an extra dependency.
+lib.a(obj3.o): obj3.h dir/dummy
+
+# Use .c.o inference with an extra dependency.
+dir/obj_1.o: dir/obj_1.h
+
+# According to POSIX, $* is only required for inference rules and $<'s
+# value is unspecified outside of inference rules. Strictly speaking
+# we shouldn't be expanding them here but who cares. At least we get
+# to check that the program does nothing stupid (like crash) with them.
+# The C file is named differently from the object file because there
+# was a bug which forced dependencies based on inference rules on all
+# applicable targets (PR 49086).
+obj2.o: obj_2.c obj_2.h dir/obj_1.h
+ @printf ${LOCALS}
+ @printf ${LOCAL_ALTERNATIVES}
+ @printf ${LOCAL_SUBSTITUTIONS}
+ @printf ${LOCAL_ALTERNATIVE_SUBSTITUTIONS}
+ cc -c -o '${@}' 'obj_2.c'
+
+# Hey, this is make, we can make our own test data setup! obj1.c
+# and obj2.c are not used, so they should not get created. They're here
+# as a bait for a regression into the forced dependencies discussed earlier.
+obj1.c dir/obj_1.c obj2.c obj_2.c obj3.c:
+ mkdir -p '${@D}'
+ printf '#include "${@F:.c=.h}"\nconst char* ${@F:.c=} = "${@}";\n' \
+ >'${@}'
+
+dir/obj_1.h obj_2.h obj3.h dummy dir/dummy:
+ mkdir -p '${@D}'
+ touch '${@}'
diff --git a/usr.bin/make/unit-tests/qequals.exp b/usr.bin/make/unit-tests/qequals.exp
new file mode 100644
index 0000000..6b2f4dc
--- /dev/null
+++ b/usr.bin/make/unit-tests/qequals.exp
@@ -0,0 +1,2 @@
+V.i386 ?= OK
+exit status 0
diff --git a/usr.bin/make/unit-tests/qequals.mk b/usr.bin/make/unit-tests/qequals.mk
new file mode 100644
index 0000000..db6d9c3
--- /dev/null
+++ b/usr.bin/make/unit-tests/qequals.mk
@@ -0,0 +1,8 @@
+# $Id: qequals.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+
+M= i386
+V.i386= OK
+V.$M ?= bug
+
+all:
+ @echo 'V.$M ?= ${V.$M}'
diff --git a/usr.bin/make/unit-tests/suffixes.exp b/usr.bin/make/unit-tests/suffixes.exp
new file mode 100644
index 0000000..2a46e1c
--- /dev/null
+++ b/usr.bin/make/unit-tests/suffixes.exp
@@ -0,0 +1,35 @@
+make: don't know how to make issue3 (continuing)
+There should be no text after the colon:
+touch .a
+There should be no text after the colon:
+touch .a.b
+There should be no text after the colon:
+touch .b.a
+touch issue5a.c
+first set
+cp issue5a.c issue5a.d
+touch issue5b.d
+first set
+cp issue5b.d issue5b.c
+touch issue5c.d
+first set
+cp issue5c.d issue5c
+touch issue5d.d
+first set
+cp issue5d.d issue5d.e
+touch issue5e.e
+first set
+cp issue5e.e issue5e.d
+make: don't know how to make issue6.f (continuing)
+touch issue10.d
+first set
+cp issue10.d issue10.e
+touch issue11.h
+touch issue11.first
+.ALLSRC: issue11.h issue11.first
+cp issue11.h issue11.i
+touch issue11.second
+.ALLSRC: issue11.i issue11.second
+cp issue11.i issue11.j
+`all' not remade because of errors.
+exit status 0
diff --git a/usr.bin/make/unit-tests/suffixes.mk b/usr.bin/make/unit-tests/suffixes.mk
new file mode 100644
index 0000000..113484a
--- /dev/null
+++ b/usr.bin/make/unit-tests/suffixes.mk
@@ -0,0 +1,89 @@
+# $NetBSD: suffixes.mk,v 1.3 2014/08/30 22:21:08 sjg Exp $
+
+# Issues from PR 49086
+
+# Issue 3: single suffix rules remain active after .SUFFIXES is cleared
+#
+# There's a rule for issue3.a, but .a is no longer a known suffix when
+# targets are being made, so issue3 should not get made.
+all: issue3
+
+# Issue 4: suffix rules do not become regular rules when .SUFFIXES is cleared
+#
+# When the rules were encountered, .a and .b were known suffices, but later
+# on they were forgotten. These should get created as regular targets.
+all: .a .a.b .b.a
+
+# Issue 5: adding more suffixes does not make existing rules into suffix rules
+#
+# When the targets .c.d, .d.c, .d, .d.e, and .e.d were encountered, only .a,
+# .b and .c were known suffixes, so all of them were regular rules. Later
+# rest of the suffixes were made known, so they should all be suffix
+# transformation rules.
+all: issue5a.d issue5b.c issue5c issue5d.e issue5e.d
+
+# Issue 6: transformation search can end up in an infinite loop
+#
+# There is no file or target from which issue6.f could be made from so
+# this should fail. The bug was that because rules .e.f, .d.e and .e.d
+# exist, make would try to make .f from .e and then infinitely try
+# to do .e from .d and vice versa.
+all: issue6.f
+
+# Issue 10: explicit dependencies affect transformation rule selection
+#
+# If issue10.e is wanted and both issue10.d and issue10.f are available,
+# make should choose the .d.e rule, because .d is before .f in .SUFFIXES.
+# The bug was that if issue10.d had an explicit dependency on issue10.f,
+# it would choose .f.e instead.
+all: issue10.e
+
+# Issue 11: sources from transformation rules are expanded incorrectly
+#
+# issue11.j should depend on issue11.i and issue11.second and issue11.i
+# should depend on issue11.h and issue11.first. The bug was that
+# the dynamic sources were expanded before ${.PREFIX} and ${.TARGET} were
+# available, so they would have expanded to a null string.
+all: issue11.j
+
+# we need to clean for repeatable results
+.BEGIN: clean
+clean:
+ @rm -f issue* .[ab]*
+
+.SUFFIXES: .a .b .c
+
+.a .a.b .b.a:
+ @echo 'There should be no text after the colon: ${.IMPSRC}'
+ touch ${.TARGET}
+
+.c.d .d.c .d .d.e .e.d:
+ @echo 'first set'
+ cp ${.IMPSRC} ${.TARGET}
+
+.SUFFIXES:
+.SUFFIXES: .c .d .e .f .g
+
+.e .e.f .f.e:
+ @echo 'second set'
+ cp ${.IMPSRC} ${.TARGET}
+
+issue3.a:
+ @echo 'There is a bug if you see this.'
+ touch ${.TARGET}
+
+issue5a.c issue5b.d issue5c.d issue5d.d issue5e.e issue10.d issue10.f:
+ touch ${.TARGET}
+
+.SUFFIXES: .h .i .j
+
+.h.i: ${.PREFIX}.first
+ @echo '.ALLSRC: ${.ALLSRC}'
+ cp ${.IMPSRC} ${.TARGET}
+
+.i.j: ${.PREFIX}.second
+ @echo '.ALLSRC: ${.ALLSRC}'
+ cp ${.IMPSRC} ${.TARGET}
+
+issue11.h issue11.first issue11.second:
+ touch ${.TARGET}
diff --git a/usr.bin/make/unit-tests/sunshcmd.exp b/usr.bin/make/unit-tests/sunshcmd.exp
new file mode 100644
index 0000000..b14f6b6
--- /dev/null
+++ b/usr.bin/make/unit-tests/sunshcmd.exp
@@ -0,0 +1,4 @@
+TEST1=hello
+TEST2=bye
+TEST3=later
+exit status 0
diff --git a/usr.bin/make/unit-tests/sunshcmd.mk b/usr.bin/make/unit-tests/sunshcmd.mk
new file mode 100644
index 0000000..e3baf90
--- /dev/null
+++ b/usr.bin/make/unit-tests/sunshcmd.mk
@@ -0,0 +1,10 @@
+BYECMD = echo bye
+LATERCMD = echo later
+TEST1 :sh = echo hello
+TEST2 :sh = ${BYECMD}
+TEST3 = ${LATERCMD:sh}
+
+all:
+ @echo "TEST1=${TEST1}"
+ @echo "TEST2=${TEST2}"
+ @echo "TEST3=${TEST3}"
diff --git a/usr.bin/make/unit-tests/sysv.exp b/usr.bin/make/unit-tests/sysv.exp
new file mode 100644
index 0000000..4cce2de
--- /dev/null
+++ b/usr.bin/make/unit-tests/sysv.exp
@@ -0,0 +1,7 @@
+FOOBAR =
+FOOBAR = foobar fubar
+fun
+fun
+fun
+In the Sun
+exit status 0
diff --git a/usr.bin/make/unit-tests/sysv.mk b/usr.bin/make/unit-tests/sysv.mk
new file mode 100644
index 0000000..3651eda
--- /dev/null
+++ b/usr.bin/make/unit-tests/sysv.mk
@@ -0,0 +1,26 @@
+# $Id: sysv.mk,v 1.2 2014/08/30 22:21:08 sjg Exp $
+
+FOO ?=
+FOOBAR = ${FOO:=bar}
+
+_this := ${.PARSEDIR}/${.PARSEFILE}
+
+B = /b
+S = /
+FUN = ${B}${S}fun
+SUN = the Sun
+
+# we expect nothing when FOO is empty
+all: foo fun
+
+foo:
+ @echo FOOBAR = ${FOOBAR}
+.if empty(FOO)
+ @FOO="foo fu" ${.MAKE} -f ${_this} foo
+.endif
+
+fun:
+ @echo ${FUN:T}
+ @echo ${FUN:${B}${S}fun=fun}
+ @echo ${FUN:${B}${S}%=%}
+ @echo ${In:L:%=% ${SUN}}
diff --git a/usr.bin/make/unit-tests/ternary.exp b/usr.bin/make/unit-tests/ternary.exp
new file mode 100644
index 0000000..ed9c1bd
--- /dev/null
+++ b/usr.bin/make/unit-tests/ternary.exp
@@ -0,0 +1,10 @@
+The answer is unknown
+The answer is unknown
+The answer is empty
+The answer is known
+The answer is
+The answer is empty
+The answer is known
+The answer is 42
+The answer is 42
+exit status 0
diff --git a/usr.bin/make/unit-tests/ternary.mk b/usr.bin/make/unit-tests/ternary.mk
new file mode 100644
index 0000000..77f8349
--- /dev/null
+++ b/usr.bin/make/unit-tests/ternary.mk
@@ -0,0 +1,8 @@
+
+all:
+ @for x in "" A= A=42; do ${.MAKE} -f ${MAKEFILE} show $$x; done
+
+show:
+ @echo "The answer is ${A:?known:unknown}"
+ @echo "The answer is ${A:?$A:unknown}"
+ @echo "The answer is ${empty(A):?empty:$A}"
diff --git a/usr.bin/make/unit-tests/unexport-env.exp b/usr.bin/make/unit-tests/unexport-env.exp
new file mode 100644
index 0000000..6d43cab
--- /dev/null
+++ b/usr.bin/make/unit-tests/unexport-env.exp
@@ -0,0 +1,2 @@
+UT_TEST=unexport-env
+exit status 0
diff --git a/usr.bin/make/unit-tests/unexport-env.mk b/usr.bin/make/unit-tests/unexport-env.mk
new file mode 100644
index 0000000..b8192f1
--- /dev/null
+++ b/usr.bin/make/unit-tests/unexport-env.mk
@@ -0,0 +1,14 @@
+# $Id: unexport-env.mk,v 1.1 2014/08/21 13:44:52 apb Exp $
+
+# pick up a bunch of exported vars
+.include "export.mk"
+
+# an example of setting up a minimal environment.
+PATH = /bin:/usr/bin:/sbin:/usr/sbin
+
+# now clobber the environment to just PATH and UT_TEST
+UT_TEST = unexport-env
+
+# this removes everything
+.unexport-env
+.export PATH UT_TEST
diff --git a/usr.bin/make/unit-tests/unexport.exp b/usr.bin/make/unit-tests/unexport.exp
new file mode 100644
index 0000000..7b16ea3
--- /dev/null
+++ b/usr.bin/make/unit-tests/unexport.exp
@@ -0,0 +1,4 @@
+UT_DOLLAR=This is $UT_FU
+UT_FU=fubar
+UT_TEST=unexport
+exit status 0
diff --git a/usr.bin/make/unit-tests/unexport.mk b/usr.bin/make/unit-tests/unexport.mk
new file mode 100644
index 0000000..b3d7d34
--- /dev/null
+++ b/usr.bin/make/unit-tests/unexport.mk
@@ -0,0 +1,8 @@
+# $Id: unexport.mk,v 1.1 2014/08/21 13:44:52 apb Exp $
+
+# pick up a bunch of exported vars
+.include "export.mk"
+
+.unexport UT_ZOO UT_FOO
+
+UT_TEST = unexport
diff --git a/usr.bin/make/unit-tests/varcmd.exp b/usr.bin/make/unit-tests/varcmd.exp
new file mode 100644
index 0000000..7803c2b
--- /dev/null
+++ b/usr.bin/make/unit-tests/varcmd.exp
@@ -0,0 +1,11 @@
+default FU=<v>fu</v> FOO=<v>foo</v> VAR=<v></v>
+two FU=<v>bar</v> FOO=<v>goo</v> VAR=<v></v>
+immutable FU='bar'
+immutable FOO='goo'
+three FU=<v>bar</v> FOO=<v>goo</v> VAR=<v></v>
+four FU=<v>bar</v> FOO=<v>goo</v> VAR=<v>Internal</v>
+five FU=<v>bar</v> FOO=<v>goo</v> VAR=<v>Internal</v>
+five v=is x k=is x
+six v=is y k=is y
+show-v v=override k=override
+exit status 0
diff --git a/usr.bin/make/unit-tests/varcmd.mk b/usr.bin/make/unit-tests/varcmd.mk
new file mode 100644
index 0000000..005ed47
--- /dev/null
+++ b/usr.bin/make/unit-tests/varcmd.mk
@@ -0,0 +1,60 @@
+# $Id: varcmd.mk,v 1.3 2017/12/08 03:36:42 sjg Exp $
+#
+# Test behaviour of recursive make and vars set on command line.
+
+FU=fu
+FOO?=foo
+.if !empty(.TARGETS)
+TAG=${.TARGETS}
+.endif
+TAG?=default
+
+all: one
+
+show:
+ @echo "${TAG} FU=<v>${FU}</v> FOO=<v>${FOO}</v> VAR=<v>${VAR}</v>"
+
+one: show
+ @${.MAKE} -f ${MAKEFILE} FU=bar FOO+=goo two
+
+two: show
+ @${.MAKE} -f ${MAKEFILE} three
+
+three: show
+ @${.MAKE} -f ${MAKEFILE} four
+
+
+.ifmake two
+# this should not work
+FU+= oops
+FOO+= oops
+_FU:= ${FU}
+_FOO:= ${FOO}
+two: immutable
+immutable:
+ @echo "$@ FU='${_FU}'"
+ @echo "$@ FOO='${_FOO}'"
+.endif
+.ifmake four
+VAR=Internal
+.MAKEOVERRIDES+= VAR
+.endif
+
+four: show
+ @${.MAKE} -f ${MAKEFILE} five
+
+M = x
+V.y = is y
+V.x = is x
+V := ${V.$M}
+K := ${V}
+
+show-v:
+ @echo '${TAG} v=${V} k=${K}'
+
+five: show show-v
+ @${.MAKE} -f ${MAKEFILE} M=y six
+
+six: show-v
+ @${.MAKE} -f ${MAKEFILE} V=override show-v
+
diff --git a/usr.bin/make/unit-tests/varmisc.exp b/usr.bin/make/unit-tests/varmisc.exp
new file mode 100644
index 0000000..ffe8f8b
--- /dev/null
+++ b/usr.bin/make/unit-tests/varmisc.exp
@@ -0,0 +1,25 @@
+
+:D expanded when var set
+true
+TRUE
+:U expanded when var undef
+true
+TRUE
+:D skipped if var undef
+
+:U skipped when var set
+is set
+:? only lhs when value true
+true
+TRUE
+:? only rhs when value false
+false
+FALSE
+do not evaluate or expand :? if discarding
+is set
+year=2016 month=04 day=01
+date=20160401
+Version=123.456.789 == 123456789
+Literal=3.4.5 == 3004005
+We have target specific vars
+exit status 0
diff --git a/usr.bin/make/unit-tests/varmisc.mk b/usr.bin/make/unit-tests/varmisc.mk
new file mode 100644
index 0000000..34d32cc
--- /dev/null
+++ b/usr.bin/make/unit-tests/varmisc.mk
@@ -0,0 +1,62 @@
+# $Id: varmisc.mk,v 1.8 2017/01/31 18:56:35 sjg Exp $
+#
+# Miscellaneous variable tests.
+
+all: unmatched_var_paren D_true U_true D_false U_false Q_lhs Q_rhs NQ_none \
+ strftime cmpv
+
+unmatched_var_paren:
+ @echo ${foo::=foo-text}
+
+True = ${echo true >&2:L:sh}TRUE
+False= ${echo false >&2:L:sh}FALSE
+
+VSET= is set
+.undef UNDEF
+
+U_false:
+ @echo :U skipped when var set
+ @echo ${VSET:U${False}}
+
+D_false:
+ @echo :D skipped if var undef
+ @echo ${UNDEF:D${False}}
+
+U_true:
+ @echo :U expanded when var undef
+ @echo ${UNDEF:U${True}}
+
+D_true:
+ @echo :D expanded when var set
+ @echo ${VSET:D${True}}
+
+Q_lhs:
+ @echo :? only lhs when value true
+ @echo ${1:L:?${True}:${False}}
+
+Q_rhs:
+ @echo :? only rhs when value false
+ @echo ${0:L:?${True}:${False}}
+
+NQ_none:
+ @echo do not evaluate or expand :? if discarding
+ @echo ${VSET:U${1:L:?${True}:${False}}}
+
+April1= 1459494000
+
+# slightly contorted syntax to use utc via variable
+strftime:
+ @echo ${year=%Y month=%m day=%d:L:gmtime=1459494000}
+ @echo date=${%Y%m%d:L:${gmtime=${April1}:L}}
+
+# big jumps to handle 3 digits per step
+M_cmpv.units = 1 1000 1000000
+M_cmpv = S,., ,g:_:range:@i@+ $${_:[-$$i]} \* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh
+
+Version = 123.456.789
+cmpv.only = target specific vars
+
+cmpv:
+ @echo Version=${Version} == ${Version:${M_cmpv}}
+ @echo Literal=3.4.5 == ${3.4.5:L:${M_cmpv}}
+ @echo We have ${${.TARGET:T}.only}
diff --git a/usr.bin/make/unit-tests/varquote.exp b/usr.bin/make/unit-tests/varquote.exp
new file mode 100644
index 0000000..63107bf
--- /dev/null
+++ b/usr.bin/make/unit-tests/varquote.exp
@@ -0,0 +1,3 @@
+-fdebug-prefix-map=$NETBSDSRCDIR=/usr/src -fdebug-regex-map=/usr/src/(.*)/obj$=/usr/obj/\1
+-fdebug-prefix-map=$NETBSDSRCDIR=/usr/src -fdebug-regex-map=/usr/src/(.*)/obj$=/usr/obj/\1
+exit status 0
diff --git a/usr.bin/make/unit-tests/varquote.mk b/usr.bin/make/unit-tests/varquote.mk
new file mode 100644
index 0000000..fb8b106
--- /dev/null
+++ b/usr.bin/make/unit-tests/varquote.mk
@@ -0,0 +1,14 @@
+# $NetBSD: varquote.mk,v 1.4 2018/12/16 18:53:34 christos Exp $
+#
+# Test VAR:q modifier
+
+.if !defined(REPROFLAGS)
+REPROFLAGS+= -fdebug-prefix-map=\$$NETBSDSRCDIR=/usr/src
+REPROFLAGS+= -fdebug-regex-map='/usr/src/(.*)/obj$$=/usr/obj/\1'
+all:
+ @${MAKE} -f ${MAKEFILE} REPROFLAGS=${REPROFLAGS:S/\$/&&/g:Q}
+ @${MAKE} -f ${MAKEFILE} REPROFLAGS=${REPROFLAGS:q}
+.else
+all:
+ @printf "%s %s\n" ${REPROFLAGS}
+.endif
diff --git a/usr.bin/make/unit-tests/varshell.exp b/usr.bin/make/unit-tests/varshell.exp
new file mode 100644
index 0000000..6ac8c88
--- /dev/null
+++ b/usr.bin/make/unit-tests/varshell.exp
@@ -0,0 +1,12 @@
+sh: /bin/no/such/command: not found
+make: "varshell.mk" line 5: warning: "/bin/no/such/command" returned non-zero status
+make: "varshell.mk" line 6: warning: "kill -14 $$" exited on a signal
+make: "varshell.mk" line 7: warning: "false" returned non-zero status
+make: "varshell.mk" line 8: warning: "echo "output before the error"; false" returned non-zero status
+EXEC_FAILED=''
+TERMINATED_BY_SIGNAL=''
+ERROR_NO_OUTPUT=''
+ERROR_WITH_OUTPUT='output before the error'
+NO_ERROR_NO_OUTPUT=''
+NO_ERROR_WITH_OUTPUT='this is good'
+exit status 0
diff --git a/usr.bin/make/unit-tests/varshell.mk b/usr.bin/make/unit-tests/varshell.mk
new file mode 100644
index 0000000..a006736
--- /dev/null
+++ b/usr.bin/make/unit-tests/varshell.mk
@@ -0,0 +1,18 @@
+# $Id: varshell.mk,v 1.2 2015/04/10 20:41:59 sjg Exp $
+#
+# Test VAR != shell command
+
+EXEC_FAILED != /bin/no/such/command
+TERMINATED_BY_SIGNAL != kill -14 $$$$
+ERROR_NO_OUTPUT != false
+ERROR_WITH_OUTPUT != echo "output before the error"; false
+NO_ERROR_NO_OUTPUT != true
+NO_ERROR_WITH_OUTPUT != echo "this is good"
+
+allvars= EXEC_FAILED TERMINATED_BY_SIGNAL ERROR_NO_OUTPUT ERROR_WITH_OUTPUT \
+ NO_ERROR_NO_OUTPUT NO_ERROR_WITH_OUTPUT
+
+all:
+.for v in ${allvars}
+ @echo ${v}=\'${${v}}\'
+.endfor
diff --git a/usr.bin/make/util.c b/usr.bin/make/util.c
new file mode 100644
index 0000000..506fb4d
--- /dev/null
+++ b/usr.bin/make/util.c
@@ -0,0 +1,494 @@
+/* $NetBSD: util.c,v 1.54 2013/11/26 13:44:41 joerg Exp $ */
+
+/*
+ * Missing stuff from OS's
+ */
+#if defined(__MINT__) || defined(__linux__)
+#include <signal.h>
+#endif
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: util.c,v 1.54 2013/11/26 13:44:41 joerg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: util.c,v 1.54 2013/11/26 13:44:41 joerg Exp $");
+#endif
+#endif
+
+#include <sys/param.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <time.h>
+#include <signal.h>
+
+#include "make.h"
+
+#if !defined(MAKE_NATIVE) && !defined(HAVE_STRERROR)
+extern int errno, sys_nerr;
+extern char *sys_errlist[];
+
+char *
+strerror(int e)
+{
+ static char buf[100];
+ if (e < 0 || e >= sys_nerr) {
+ snprintf(buf, sizeof(buf), "Unknown error %d", e);
+ return buf;
+ }
+ else
+ return sys_errlist[e];
+}
+#endif
+
+#if !defined(MAKE_NATIVE) && !defined(HAVE_SETENV)
+extern char **environ;
+
+static char *
+findenv(const char *name, int *offset)
+{
+ size_t i, len;
+ char *p, *q;
+
+ len = strlen(name);
+ for (i = 0; (q = environ[i]); i++) {
+ p = strchr(q, '=');
+ if (p == NULL || p - q != len)
+ continue;
+ if (strncmp(name, q, len) == 0) {
+ *offset = i;
+ return q + len + 1;
+ }
+ }
+ *offset = i;
+ return NULL;
+}
+
+char *
+getenv(const char *name)
+{
+ int offset;
+
+ return(findenv(name, &offset));
+}
+
+int
+unsetenv(const char *name)
+{
+ char **p;
+ int offset;
+
+ if (name == NULL || *name == '\0' || strchr(name, '=') != NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ while (findenv(name, &offset)) { /* if set multiple times */
+ for (p = &environ[offset];; ++p)
+ if (!(*p = *(p + 1)))
+ break;
+ }
+ return 0;
+}
+
+int
+setenv(const char *name, const char *value, int rewrite)
+{
+ char *c, **newenv;
+ const char *cc;
+ size_t l_value, size;
+ int offset;
+
+ if (name == NULL || value == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (*value == '=') /* no `=' in value */
+ ++value;
+ l_value = strlen(value);
+
+ /* find if already exists */
+ if ((c = findenv(name, &offset))) {
+ if (!rewrite)
+ return 0;
+ if (strlen(c) >= l_value) /* old larger; copy over */
+ goto copy;
+ } else { /* create new slot */
+ size = sizeof(char *) * (offset + 2);
+ if (savedEnv == environ) { /* just increase size */
+ if ((newenv = realloc(savedEnv, size)) == NULL)
+ return -1;
+ savedEnv = newenv;
+ } else { /* get new space */
+ /*
+ * We don't free here because we don't know if
+ * the first allocation is valid on all OS's
+ */
+ if ((savedEnv = malloc(size)) == NULL)
+ return -1;
+ (void)memcpy(savedEnv, environ, size - sizeof(char *));
+ }
+ environ = savedEnv;
+ environ[offset + 1] = NULL;
+ }
+ for (cc = name; *cc && *cc != '='; ++cc) /* no `=' in name */
+ continue;
+ size = cc - name;
+ /* name + `=' + value */
+ if ((environ[offset] = malloc(size + l_value + 2)) == NULL)
+ return -1;
+ c = environ[offset];
+ (void)memcpy(c, name, size);
+ c += size;
+ *c++ = '=';
+copy:
+ (void)memcpy(c, value, l_value + 1);
+ return 0;
+}
+
+#ifdef TEST
+int
+main(int argc, char *argv[])
+{
+ setenv(argv[1], argv[2], 0);
+ printf("%s\n", getenv(argv[1]));
+ unsetenv(argv[1]);
+ printf("%s\n", getenv(argv[1]));
+ return 0;
+}
+#endif
+
+#endif
+
+#if defined(__hpux__) || defined(__hpux)
+/* strrcpy():
+ * Like strcpy, going backwards and returning the new pointer
+ */
+static char *
+strrcpy(char *ptr, char *str)
+{
+ int len = strlen(str);
+
+ while (len)
+ *--ptr = str[--len];
+
+ return (ptr);
+} /* end strrcpy */
+
+char *sys_siglist[] = {
+ "Signal 0",
+ "Hangup", /* SIGHUP */
+ "Interrupt", /* SIGINT */
+ "Quit", /* SIGQUIT */
+ "Illegal instruction", /* SIGILL */
+ "Trace/BPT trap", /* SIGTRAP */
+ "IOT trap", /* SIGIOT */
+ "EMT trap", /* SIGEMT */
+ "Floating point exception", /* SIGFPE */
+ "Killed", /* SIGKILL */
+ "Bus error", /* SIGBUS */
+ "Segmentation fault", /* SIGSEGV */
+ "Bad system call", /* SIGSYS */
+ "Broken pipe", /* SIGPIPE */
+ "Alarm clock", /* SIGALRM */
+ "Terminated", /* SIGTERM */
+ "User defined signal 1", /* SIGUSR1 */
+ "User defined signal 2", /* SIGUSR2 */
+ "Child exited", /* SIGCLD */
+ "Power-fail restart", /* SIGPWR */
+ "Virtual timer expired", /* SIGVTALRM */
+ "Profiling timer expired", /* SIGPROF */
+ "I/O possible", /* SIGIO */
+ "Window size changes", /* SIGWINDOW */
+ "Stopped (signal)", /* SIGSTOP */
+ "Stopped", /* SIGTSTP */
+ "Continued", /* SIGCONT */
+ "Stopped (tty input)", /* SIGTTIN */
+ "Stopped (tty output)", /* SIGTTOU */
+ "Urgent I/O condition", /* SIGURG */
+ "Remote lock lost (NFS)", /* SIGLOST */
+ "Signal 31", /* reserved */
+ "DIL signal" /* SIGDIL */
+};
+#endif /* __hpux__ || __hpux */
+
+#if defined(__hpux__) || defined(__hpux)
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+int
+killpg(int pid, int sig)
+{
+ return kill(-pid, sig);
+}
+
+#if !defined(__hpux__) && !defined(__hpux)
+void
+srandom(long seed)
+{
+ srand48(seed);
+}
+
+long
+random(void)
+{
+ return lrand48();
+}
+#endif
+
+#if !defined(__hpux__) && !defined(__hpux)
+int
+utimes(char *file, struct timeval tvp[2])
+{
+ struct utimbuf t;
+
+ t.actime = tvp[0].tv_sec;
+ t.modtime = tvp[1].tv_sec;
+ return(utime(file, &t));
+}
+#endif
+
+#if !defined(BSD) && !defined(d_fileno)
+# define d_fileno d_ino
+#endif
+
+#ifndef DEV_DEV_COMPARE
+# define DEV_DEV_COMPARE(a, b) ((a) == (b))
+#endif
+#define ISDOT(c) ((c)[0] == '.' && (((c)[1] == '\0') || ((c)[1] == '/')))
+#define ISDOTDOT(c) ((c)[0] == '.' && ISDOT(&((c)[1])))
+
+char *
+getwd(char *pathname)
+{
+ DIR *dp;
+ struct dirent *d;
+ extern int errno;
+
+ struct stat st_root, st_cur, st_next, st_dotdot;
+ char pathbuf[MAXPATHLEN], nextpathbuf[MAXPATHLEN * 2];
+ char *pathptr, *nextpathptr, *cur_name_add;
+
+ /* find the inode of root */
+ if (stat("/", &st_root) == -1) {
+ (void)sprintf(pathname,
+ "getwd: Cannot stat \"/\" (%s)", strerror(errno));
+ return NULL;
+ }
+ pathbuf[MAXPATHLEN - 1] = '\0';
+ pathptr = &pathbuf[MAXPATHLEN - 1];
+ nextpathbuf[MAXPATHLEN - 1] = '\0';
+ cur_name_add = nextpathptr = &nextpathbuf[MAXPATHLEN - 1];
+
+ /* find the inode of the current directory */
+ if (lstat(".", &st_cur) == -1) {
+ (void)sprintf(pathname,
+ "getwd: Cannot stat \".\" (%s)", strerror(errno));
+ return NULL;
+ }
+ nextpathptr = strrcpy(nextpathptr, "../");
+
+ /* Descend to root */
+ for (;;) {
+
+ /* look if we found root yet */
+ if (st_cur.st_ino == st_root.st_ino &&
+ DEV_DEV_COMPARE(st_cur.st_dev, st_root.st_dev)) {
+ (void)strcpy(pathname, *pathptr != '/' ? "/" : pathptr);
+ return (pathname);
+ }
+
+ /* open the parent directory */
+ if (stat(nextpathptr, &st_dotdot) == -1) {
+ (void)sprintf(pathname,
+ "getwd: Cannot stat directory \"%s\" (%s)",
+ nextpathptr, strerror(errno));
+ return NULL;
+ }
+ if ((dp = opendir(nextpathptr)) == NULL) {
+ (void)sprintf(pathname,
+ "getwd: Cannot open directory \"%s\" (%s)",
+ nextpathptr, strerror(errno));
+ return NULL;
+ }
+
+ /* look in the parent for the entry with the same inode */
+ if (DEV_DEV_COMPARE(st_dotdot.st_dev, st_cur.st_dev)) {
+ /* Parent has same device. No need to stat every member */
+ for (d = readdir(dp); d != NULL; d = readdir(dp))
+ if (d->d_fileno == st_cur.st_ino)
+ break;
+ }
+ else {
+ /*
+ * Parent has a different device. This is a mount point so we
+ * need to stat every member
+ */
+ for (d = readdir(dp); d != NULL; d = readdir(dp)) {
+ if (ISDOT(d->d_name) || ISDOTDOT(d->d_name))
+ continue;
+ (void)strcpy(cur_name_add, d->d_name);
+ if (lstat(nextpathptr, &st_next) == -1) {
+ (void)sprintf(pathname,
+ "getwd: Cannot stat \"%s\" (%s)",
+ d->d_name, strerror(errno));
+ (void)closedir(dp);
+ return NULL;
+ }
+ /* check if we found it yet */
+ if (st_next.st_ino == st_cur.st_ino &&
+ DEV_DEV_COMPARE(st_next.st_dev, st_cur.st_dev))
+ break;
+ }
+ }
+ if (d == NULL) {
+ (void)sprintf(pathname,
+ "getwd: Cannot find \".\" in \"..\"");
+ (void)closedir(dp);
+ return NULL;
+ }
+ st_cur = st_dotdot;
+ pathptr = strrcpy(pathptr, d->d_name);
+ pathptr = strrcpy(pathptr, "/");
+ nextpathptr = strrcpy(nextpathptr, "../");
+ (void)closedir(dp);
+ *cur_name_add = '\0';
+ }
+} /* end getwd */
+#endif /* __hpux */
+
+/* force posix signals */
+void (*
+bmake_signal(int s, void (*a)(int)))(int)
+{
+ struct sigaction sa, osa;
+
+ sa.sa_handler = a;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+
+ if (sigaction(s, &sa, &osa) == -1)
+ return SIG_ERR;
+ else
+ return osa.sa_handler;
+}
+
+#if !defined(MAKE_NATIVE) && !defined(HAVE_VSNPRINTF)
+#include <stdarg.h>
+
+#if !defined(__osf__)
+#ifdef _IOSTRG
+#define STRFLAG (_IOSTRG|_IOWRT) /* no _IOWRT: avoid stdio bug */
+#else
+#if 0
+#define STRFLAG (_IOREAD) /* XXX: Assume svr4 stdio */
+#endif
+#endif /* _IOSTRG */
+#endif /* __osf__ */
+
+int
+vsnprintf(char *s, size_t n, const char *fmt, va_list args)
+{
+#ifdef STRFLAG
+ FILE fakebuf;
+
+ fakebuf._flag = STRFLAG;
+ /*
+ * Some os's are char * _ptr, others are unsigned char *_ptr...
+ * We cast to void * to make everyone happy.
+ */
+ fakebuf._ptr = (void *)s;
+ fakebuf._cnt = n-1;
+ fakebuf._file = -1;
+ _doprnt(fmt, args, &fakebuf);
+ fakebuf._cnt++;
+ putc('\0', &fakebuf);
+ if (fakebuf._cnt<0)
+ fakebuf._cnt = 0;
+ return (n-fakebuf._cnt-1);
+#else
+ (void)vsprintf(s, fmt, args);
+ return strlen(s);
+#endif
+}
+
+int
+snprintf(char *s, size_t n, const char *fmt, ...)
+{
+ va_list ap;
+ int rv;
+
+ va_start(ap, fmt);
+ rv = vsnprintf(s, n, fmt, ap);
+ va_end(ap);
+ return rv;
+}
+
+#if !defined(MAKE_NATIVE) && !defined(HAVE_STRFTIME)
+size_t
+strftime(char *buf, size_t len, const char *fmt, const struct tm *tm)
+{
+ static char months[][4] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+
+ size_t s;
+ char *b = buf;
+
+ while (*fmt) {
+ if (len == 0)
+ return buf - b;
+ if (*fmt != '%') {
+ *buf++ = *fmt++;
+ len--;
+ continue;
+ }
+ switch (*fmt++) {
+ case '%':
+ *buf++ = '%';
+ len--;
+ if (len == 0) return buf - b;
+ /*FALLTHROUGH*/
+ case '\0':
+ *buf = '%';
+ s = 1;
+ break;
+ case 'k':
+ s = snprintf(buf, len, "%d", tm->tm_hour);
+ break;
+ case 'M':
+ s = snprintf(buf, len, "%02d", tm->tm_min);
+ break;
+ case 'S':
+ s = snprintf(buf, len, "%02d", tm->tm_sec);
+ break;
+ case 'b':
+ if (tm->tm_mon >= 12)
+ return buf - b;
+ s = snprintf(buf, len, "%s", months[tm->tm_mon]);
+ break;
+ case 'd':
+ s = snprintf(buf, len, "%02d", tm->tm_mday);
+ break;
+ case 'Y':
+ s = snprintf(buf, len, "%d", 1900 + tm->tm_year);
+ break;
+ default:
+ s = snprintf(buf, len, "Unsupported format %c",
+ fmt[-1]);
+ break;
+ }
+ buf += s;
+ len -= s;
+ }
+}
+#endif
+#endif
diff --git a/usr.bin/make/var.c b/usr.bin/make/var.c
new file mode 100644
index 0000000..547e7fd
--- /dev/null
+++ b/usr.bin/make/var.c
@@ -0,0 +1,4355 @@
+/* $NetBSD: var.c,v 1.221 2018/12/21 05:50:19 sjg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: var.c,v 1.221 2018/12/21 05:50:19 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: var.c,v 1.221 2018/12/21 05:50:19 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * var.c --
+ * Variable-handling functions
+ *
+ * Interface:
+ * Var_Set Set the value of a variable in the given
+ * context. The variable is created if it doesn't
+ * yet exist. The value and variable name need not
+ * be preserved.
+ *
+ * Var_Append Append more characters to an existing variable
+ * in the given context. The variable needn't
+ * exist already -- it will be created if it doesn't.
+ * A space is placed between the old value and the
+ * new one.
+ *
+ * Var_Exists See if a variable exists.
+ *
+ * Var_Value Return the value of a variable in a context or
+ * NULL if the variable is undefined.
+ *
+ * Var_Subst Substitute named variable, or all variables if
+ * NULL in a string using
+ * the given context as the top-most one. If the
+ * third argument is non-zero, Parse_Error is
+ * called if any variables are undefined.
+ *
+ * Var_Parse Parse a variable expansion from a string and
+ * return the result and the number of characters
+ * consumed.
+ *
+ * Var_Delete Delete a variable in a context.
+ *
+ * Var_Init Initialize this module.
+ *
+ * Debugging:
+ * Var_Dump Print out all variables defined in the given
+ * context.
+ *
+ * XXX: There's a lot of duplication in these functions.
+ */
+
+#include <sys/stat.h>
+#ifndef NO_REGEX
+#include <sys/types.h>
+#include <regex.h>
+#endif
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <time.h>
+
+#include "make.h"
+#include "buf.h"
+#include "dir.h"
+#include "job.h"
+#include "metachar.h"
+
+extern int makelevel;
+/*
+ * This lets us tell if we have replaced the original environ
+ * (which we cannot free).
+ */
+char **savedEnv = NULL;
+
+/*
+ * This is a harmless return value for Var_Parse that can be used by Var_Subst
+ * to determine if there was an error in parsing -- easier than returning
+ * a flag, as things outside this module don't give a hoot.
+ */
+char var_Error[] = "";
+
+/*
+ * Similar to var_Error, but returned when the 'VARF_UNDEFERR' flag for
+ * Var_Parse is not set. Why not just use a constant? Well, gcc likes
+ * to condense identical string instances...
+ */
+static char varNoError[] = "";
+
+/*
+ * Traditionally we consume $$ during := like any other expansion.
+ * Other make's do not.
+ * This knob allows controlling the behavior.
+ * FALSE for old behavior.
+ * TRUE for new compatible.
+ */
+#define SAVE_DOLLARS ".MAKE.SAVE_DOLLARS"
+static Boolean save_dollars = TRUE;
+
+/*
+ * Internally, variables are contained in four different contexts.
+ * 1) the environment. They may not be changed. If an environment
+ * variable is appended-to, the result is placed in the global
+ * context.
+ * 2) the global context. Variables set in the Makefile are located in
+ * the global context. It is the penultimate context searched when
+ * substituting.
+ * 3) the command-line context. All variables set on the command line
+ * are placed in this context. They are UNALTERABLE once placed here.
+ * 4) the local context. Each target has associated with it a context
+ * list. On this list are located the structures describing such
+ * local variables as $(@) and $(*)
+ * The four contexts are searched in the reverse order from which they are
+ * listed.
+ */
+GNode *VAR_INTERNAL; /* variables from make itself */
+GNode *VAR_GLOBAL; /* variables from the makefile */
+GNode *VAR_CMD; /* variables defined on the command-line */
+
+#define FIND_CMD 0x1 /* look in VAR_CMD when searching */
+#define FIND_GLOBAL 0x2 /* look in VAR_GLOBAL as well */
+#define FIND_ENV 0x4 /* look in the environment also */
+
+typedef struct Var {
+ char *name; /* the variable's name */
+ Buffer val; /* its value */
+ int flags; /* miscellaneous status flags */
+#define VAR_IN_USE 1 /* Variable's value currently being used.
+ * Used to avoid recursion */
+#define VAR_FROM_ENV 2 /* Variable comes from the environment */
+#define VAR_JUNK 4 /* Variable is a junk variable that
+ * should be destroyed when done with
+ * it. Used by Var_Parse for undefined,
+ * modified variables */
+#define VAR_KEEP 8 /* Variable is VAR_JUNK, but we found
+ * a use for it in some modifier and
+ * the value is therefore valid */
+#define VAR_EXPORTED 16 /* Variable is exported */
+#define VAR_REEXPORT 32 /* Indicate if var needs re-export.
+ * This would be true if it contains $'s
+ */
+#define VAR_FROM_CMD 64 /* Variable came from command line */
+} Var;
+
+/*
+ * Exporting vars is expensive so skip it if we can
+ */
+#define VAR_EXPORTED_NONE 0
+#define VAR_EXPORTED_YES 1
+#define VAR_EXPORTED_ALL 2
+static int var_exportedVars = VAR_EXPORTED_NONE;
+/*
+ * We pass this to Var_Export when doing the initial export
+ * or after updating an exported var.
+ */
+#define VAR_EXPORT_PARENT 1
+/*
+ * We pass this to Var_Export1 to tell it to leave the value alone.
+ */
+#define VAR_EXPORT_LITERAL 2
+
+/* Var*Pattern flags */
+#define VAR_SUB_GLOBAL 0x01 /* Apply substitution globally */
+#define VAR_SUB_ONE 0x02 /* Apply substitution to one word */
+#define VAR_SUB_MATCHED 0x04 /* There was a match */
+#define VAR_MATCH_START 0x08 /* Match at start of word */
+#define VAR_MATCH_END 0x10 /* Match at end of word */
+#define VAR_NOSUBST 0x20 /* don't expand vars in VarGetPattern */
+
+/* Var_Set flags */
+#define VAR_NO_EXPORT 0x01 /* do not export */
+
+typedef struct {
+ /*
+ * The following fields are set by Var_Parse() when it
+ * encounters modifiers that need to keep state for use by
+ * subsequent modifiers within the same variable expansion.
+ */
+ Byte varSpace; /* Word separator in expansions */
+ Boolean oneBigWord; /* TRUE if we will treat the variable as a
+ * single big word, even if it contains
+ * embedded spaces (as opposed to the
+ * usual behaviour of treating it as
+ * several space-separated words). */
+} Var_Parse_State;
+
+/* struct passed as 'void *' to VarSubstitute() for ":S/lhs/rhs/",
+ * to VarSYSVMatch() for ":lhs=rhs". */
+typedef struct {
+ const char *lhs; /* String to match */
+ int leftLen; /* Length of string */
+ const char *rhs; /* Replacement string (w/ &'s removed) */
+ int rightLen; /* Length of replacement */
+ int flags;
+} VarPattern;
+
+/* struct passed as 'void *' to VarLoopExpand() for ":@tvar@str@" */
+typedef struct {
+ GNode *ctxt; /* variable context */
+ char *tvar; /* name of temp var */
+ int tvarLen;
+ char *str; /* string to expand */
+ int strLen;
+ int errnum; /* errnum for not defined */
+} VarLoop_t;
+
+#ifndef NO_REGEX
+/* struct passed as 'void *' to VarRESubstitute() for ":C///" */
+typedef struct {
+ regex_t re;
+ int nsub;
+ regmatch_t *matches;
+ char *replace;
+ int flags;
+} VarREPattern;
+#endif
+
+/* struct passed to VarSelectWords() for ":[start..end]" */
+typedef struct {
+ int start; /* first word to select */
+ int end; /* last word to select */
+} VarSelectWords_t;
+
+static Var *VarFind(const char *, GNode *, int);
+static void VarAdd(const char *, const char *, GNode *);
+static Boolean VarHead(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+static Boolean VarTail(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+static Boolean VarSuffix(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+static Boolean VarRoot(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+static Boolean VarMatch(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+#ifdef SYSVVARSUB
+static Boolean VarSYSVMatch(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+#endif
+static Boolean VarNoMatch(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+#ifndef NO_REGEX
+static void VarREError(int, regex_t *, const char *);
+static Boolean VarRESubstitute(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+#endif
+static Boolean VarSubstitute(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+static Boolean VarLoopExpand(GNode *, Var_Parse_State *,
+ char *, Boolean, Buffer *, void *);
+static char *VarGetPattern(GNode *, Var_Parse_State *,
+ int, const char **, int, int *, int *,
+ VarPattern *);
+static char *VarQuote(char *, Boolean);
+static char *VarHash(char *);
+static char *VarModify(GNode *, Var_Parse_State *,
+ const char *,
+ Boolean (*)(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *),
+ void *);
+static char *VarOrder(const char *, const char);
+static char *VarUniq(const char *);
+static int VarWordCompare(const void *, const void *);
+static void VarPrintVar(void *);
+
+#define BROPEN '{'
+#define BRCLOSE '}'
+#define PROPEN '('
+#define PRCLOSE ')'
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarFind --
+ * Find the given variable in the given context and any other contexts
+ * indicated.
+ *
+ * Input:
+ * name name to find
+ * ctxt context in which to find it
+ * flags FIND_GLOBAL set means to look in the
+ * VAR_GLOBAL context as well. FIND_CMD set means
+ * to look in the VAR_CMD context also. FIND_ENV
+ * set means to look in the environment
+ *
+ * Results:
+ * A pointer to the structure describing the desired variable or
+ * NULL if the variable does not exist.
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+static Var *
+VarFind(const char *name, GNode *ctxt, int flags)
+{
+ Hash_Entry *var;
+ Var *v;
+
+ /*
+ * If the variable name begins with a '.', it could very well be one of
+ * the local ones. We check the name against all the local variables
+ * and substitute the short version in for 'name' if it matches one of
+ * them.
+ */
+ if (*name == '.' && isupper((unsigned char) name[1]))
+ switch (name[1]) {
+ case 'A':
+ if (!strcmp(name, ".ALLSRC"))
+ name = ALLSRC;
+ if (!strcmp(name, ".ARCHIVE"))
+ name = ARCHIVE;
+ break;
+ case 'I':
+ if (!strcmp(name, ".IMPSRC"))
+ name = IMPSRC;
+ break;
+ case 'M':
+ if (!strcmp(name, ".MEMBER"))
+ name = MEMBER;
+ break;
+ case 'O':
+ if (!strcmp(name, ".OODATE"))
+ name = OODATE;
+ break;
+ case 'P':
+ if (!strcmp(name, ".PREFIX"))
+ name = PREFIX;
+ break;
+ case 'T':
+ if (!strcmp(name, ".TARGET"))
+ name = TARGET;
+ break;
+ }
+#ifdef notyet
+ /* for compatibility with gmake */
+ if (name[0] == '^' && name[1] == '\0')
+ name = ALLSRC;
+#endif
+
+ /*
+ * First look for the variable in the given context. If it's not there,
+ * look for it in VAR_CMD, VAR_GLOBAL and the environment, in that order,
+ * depending on the FIND_* flags in 'flags'
+ */
+ var = Hash_FindEntry(&ctxt->context, name);
+
+ if ((var == NULL) && (flags & FIND_CMD) && (ctxt != VAR_CMD)) {
+ var = Hash_FindEntry(&VAR_CMD->context, name);
+ }
+ if (!checkEnvFirst && (var == NULL) && (flags & FIND_GLOBAL) &&
+ (ctxt != VAR_GLOBAL))
+ {
+ var = Hash_FindEntry(&VAR_GLOBAL->context, name);
+ if ((var == NULL) && (ctxt != VAR_INTERNAL)) {
+ /* VAR_INTERNAL is subordinate to VAR_GLOBAL */
+ var = Hash_FindEntry(&VAR_INTERNAL->context, name);
+ }
+ }
+ if ((var == NULL) && (flags & FIND_ENV)) {
+ char *env;
+
+ if ((env = getenv(name)) != NULL) {
+ int len;
+
+ v = bmake_malloc(sizeof(Var));
+ v->name = bmake_strdup(name);
+
+ len = strlen(env);
+
+ Buf_Init(&v->val, len + 1);
+ Buf_AddBytes(&v->val, len, env);
+
+ v->flags = VAR_FROM_ENV;
+ return (v);
+ } else if (checkEnvFirst && (flags & FIND_GLOBAL) &&
+ (ctxt != VAR_GLOBAL))
+ {
+ var = Hash_FindEntry(&VAR_GLOBAL->context, name);
+ if ((var == NULL) && (ctxt != VAR_INTERNAL)) {
+ var = Hash_FindEntry(&VAR_INTERNAL->context, name);
+ }
+ if (var == NULL) {
+ return NULL;
+ } else {
+ return ((Var *)Hash_GetValue(var));
+ }
+ } else {
+ return NULL;
+ }
+ } else if (var == NULL) {
+ return NULL;
+ } else {
+ return ((Var *)Hash_GetValue(var));
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarFreeEnv --
+ * If the variable is an environment variable, free it
+ *
+ * Input:
+ * v the variable
+ * destroy true if the value buffer should be destroyed.
+ *
+ * Results:
+ * 1 if it is an environment variable 0 ow.
+ *
+ * Side Effects:
+ * The variable is free'ed if it is an environent variable.
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarFreeEnv(Var *v, Boolean destroy)
+{
+ if ((v->flags & VAR_FROM_ENV) == 0)
+ return FALSE;
+ free(v->name);
+ Buf_Destroy(&v->val, destroy);
+ free(v);
+ return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarAdd --
+ * Add a new variable of name name and value val to the given context
+ *
+ * Input:
+ * name name of variable to add
+ * val value to set it to
+ * ctxt context in which to set it
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The new variable is placed at the front of the given context
+ * The name and val arguments are duplicated so they may
+ * safely be freed.
+ *-----------------------------------------------------------------------
+ */
+static void
+VarAdd(const char *name, const char *val, GNode *ctxt)
+{
+ Var *v;
+ int len;
+ Hash_Entry *h;
+
+ v = bmake_malloc(sizeof(Var));
+
+ len = val ? strlen(val) : 0;
+ Buf_Init(&v->val, len+1);
+ Buf_AddBytes(&v->val, len, val);
+
+ v->flags = 0;
+
+ h = Hash_CreateEntry(&ctxt->context, name, NULL);
+ Hash_SetValue(h, v);
+ v->name = h->name;
+ if (DEBUG(VAR) && (ctxt->flags & INTERNAL) == 0) {
+ fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name, val);
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Delete --
+ * Remove a variable from a context.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * The Var structure is removed and freed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Delete(const char *name, GNode *ctxt)
+{
+ Hash_Entry *ln;
+ char *cp;
+
+ if (strchr(name, '$')) {
+ cp = Var_Subst(NULL, name, VAR_GLOBAL, VARF_WANTRES);
+ } else {
+ cp = (char *)name;
+ }
+ ln = Hash_FindEntry(&ctxt->context, cp);
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "%s:delete %s%s\n",
+ ctxt->name, cp, ln ? "" : " (not found)");
+ }
+ if (cp != name) {
+ free(cp);
+ }
+ if (ln != NULL) {
+ Var *v;
+
+ v = (Var *)Hash_GetValue(ln);
+ if ((v->flags & VAR_EXPORTED)) {
+ unsetenv(v->name);
+ }
+ if (strcmp(MAKE_EXPORTED, v->name) == 0) {
+ var_exportedVars = VAR_EXPORTED_NONE;
+ }
+ if (v->name != ln->name)
+ free(v->name);
+ Hash_DeleteEntry(&ctxt->context, ln);
+ Buf_Destroy(&v->val, TRUE);
+ free(v);
+ }
+}
+
+
+/*
+ * Export a var.
+ * We ignore make internal variables (those which start with '.')
+ * Also we jump through some hoops to avoid calling setenv
+ * more than necessary since it can leak.
+ * We only manipulate flags of vars if 'parent' is set.
+ */
+static int
+Var_Export1(const char *name, int flags)
+{
+ char tmp[BUFSIZ];
+ Var *v;
+ char *val = NULL;
+ int n;
+ int parent = (flags & VAR_EXPORT_PARENT);
+
+ if (*name == '.')
+ return 0; /* skip internals */
+ if (!name[1]) {
+ /*
+ * A single char.
+ * If it is one of the vars that should only appear in
+ * local context, skip it, else we can get Var_Subst
+ * into a loop.
+ */
+ switch (name[0]) {
+ case '@':
+ case '%':
+ case '*':
+ case '!':
+ return 0;
+ }
+ }
+ v = VarFind(name, VAR_GLOBAL, 0);
+ if (v == NULL) {
+ return 0;
+ }
+ if (!parent &&
+ (v->flags & (VAR_EXPORTED|VAR_REEXPORT)) == VAR_EXPORTED) {
+ return 0; /* nothing to do */
+ }
+ val = Buf_GetAll(&v->val, NULL);
+ if ((flags & VAR_EXPORT_LITERAL) == 0 && strchr(val, '$')) {
+ if (parent) {
+ /*
+ * Flag this as something we need to re-export.
+ * No point actually exporting it now though,
+ * the child can do it at the last minute.
+ */
+ v->flags |= (VAR_EXPORTED|VAR_REEXPORT);
+ return 1;
+ }
+ if (v->flags & VAR_IN_USE) {
+ /*
+ * We recursed while exporting in a child.
+ * This isn't going to end well, just skip it.
+ */
+ return 0;
+ }
+ n = snprintf(tmp, sizeof(tmp), "${%s}", name);
+ if (n < (int)sizeof(tmp)) {
+ val = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES);
+ setenv(name, val, 1);
+ free(val);
+ }
+ } else {
+ if (parent) {
+ v->flags &= ~VAR_REEXPORT; /* once will do */
+ }
+ if (parent || !(v->flags & VAR_EXPORTED)) {
+ setenv(name, val, 1);
+ }
+ }
+ /*
+ * This is so Var_Set knows to call Var_Export again...
+ */
+ if (parent) {
+ v->flags |= VAR_EXPORTED;
+ }
+ return 1;
+}
+
+/*
+ * This gets called from our children.
+ */
+void
+Var_ExportVars(void)
+{
+ char tmp[BUFSIZ];
+ Hash_Entry *var;
+ Hash_Search state;
+ Var *v;
+ char *val;
+ int n;
+
+ /*
+ * Several make's support this sort of mechanism for tracking
+ * recursion - but each uses a different name.
+ * We allow the makefiles to update MAKELEVEL and ensure
+ * children see a correctly incremented value.
+ */
+ snprintf(tmp, sizeof(tmp), "%d", makelevel + 1);
+ setenv(MAKE_LEVEL_ENV, tmp, 1);
+
+ if (VAR_EXPORTED_NONE == var_exportedVars)
+ return;
+
+ if (VAR_EXPORTED_ALL == var_exportedVars) {
+ /*
+ * Ouch! This is crazy...
+ */
+ for (var = Hash_EnumFirst(&VAR_GLOBAL->context, &state);
+ var != NULL;
+ var = Hash_EnumNext(&state)) {
+ v = (Var *)Hash_GetValue(var);
+ Var_Export1(v->name, 0);
+ }
+ return;
+ }
+ /*
+ * We have a number of exported vars,
+ */
+ n = snprintf(tmp, sizeof(tmp), "${" MAKE_EXPORTED ":O:u}");
+ if (n < (int)sizeof(tmp)) {
+ char **av;
+ char *as;
+ int ac;
+ int i;
+
+ val = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES);
+ if (*val) {
+ av = brk_string(val, &ac, FALSE, &as);
+ for (i = 0; i < ac; i++) {
+ Var_Export1(av[i], 0);
+ }
+ free(as);
+ free(av);
+ }
+ free(val);
+ }
+}
+
+/*
+ * This is called when .export is seen or
+ * .MAKE.EXPORTED is modified.
+ * It is also called when any exported var is modified.
+ */
+void
+Var_Export(char *str, int isExport)
+{
+ char *name;
+ char *val;
+ char **av;
+ char *as;
+ int flags;
+ int ac;
+ int i;
+
+ if (isExport && (!str || !str[0])) {
+ var_exportedVars = VAR_EXPORTED_ALL; /* use with caution! */
+ return;
+ }
+
+ flags = 0;
+ if (strncmp(str, "-env", 4) == 0) {
+ str += 4;
+ } else if (strncmp(str, "-literal", 8) == 0) {
+ str += 8;
+ flags |= VAR_EXPORT_LITERAL;
+ } else {
+ flags |= VAR_EXPORT_PARENT;
+ }
+ val = Var_Subst(NULL, str, VAR_GLOBAL, VARF_WANTRES);
+ if (*val) {
+ av = brk_string(val, &ac, FALSE, &as);
+ for (i = 0; i < ac; i++) {
+ name = av[i];
+ if (!name[1]) {
+ /*
+ * A single char.
+ * If it is one of the vars that should only appear in
+ * local context, skip it, else we can get Var_Subst
+ * into a loop.
+ */
+ switch (name[0]) {
+ case '@':
+ case '%':
+ case '*':
+ case '!':
+ continue;
+ }
+ }
+ if (Var_Export1(name, flags)) {
+ if (VAR_EXPORTED_ALL != var_exportedVars)
+ var_exportedVars = VAR_EXPORTED_YES;
+ if (isExport && (flags & VAR_EXPORT_PARENT)) {
+ Var_Append(MAKE_EXPORTED, name, VAR_GLOBAL);
+ }
+ }
+ }
+ free(as);
+ free(av);
+ }
+ free(val);
+}
+
+
+/*
+ * This is called when .unexport[-env] is seen.
+ */
+extern char **environ;
+
+void
+Var_UnExport(char *str)
+{
+ char tmp[BUFSIZ];
+ char *vlist;
+ char *cp;
+ Boolean unexport_env;
+ int n;
+
+ if (!str || !str[0]) {
+ return; /* assert? */
+ }
+
+ vlist = NULL;
+
+ str += 8;
+ unexport_env = (strncmp(str, "-env", 4) == 0);
+ if (unexport_env) {
+ char **newenv;
+
+ cp = getenv(MAKE_LEVEL_ENV); /* we should preserve this */
+ if (environ == savedEnv) {
+ /* we have been here before! */
+ newenv = bmake_realloc(environ, 2 * sizeof(char *));
+ } else {
+ if (savedEnv) {
+ free(savedEnv);
+ savedEnv = NULL;
+ }
+ newenv = bmake_malloc(2 * sizeof(char *));
+ }
+ if (!newenv)
+ return;
+ /* Note: we cannot safely free() the original environ. */
+ environ = savedEnv = newenv;
+ newenv[0] = NULL;
+ newenv[1] = NULL;
+ if (cp && *cp)
+ setenv(MAKE_LEVEL_ENV, cp, 1);
+ } else {
+ for (; *str != '\n' && isspace((unsigned char) *str); str++)
+ continue;
+ if (str[0] && str[0] != '\n') {
+ vlist = str;
+ }
+ }
+
+ if (!vlist) {
+ /* Using .MAKE.EXPORTED */
+ n = snprintf(tmp, sizeof(tmp), "${" MAKE_EXPORTED ":O:u}");
+ if (n < (int)sizeof(tmp)) {
+ vlist = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES);
+ }
+ }
+ if (vlist) {
+ Var *v;
+ char **av;
+ char *as;
+ int ac;
+ int i;
+
+ av = brk_string(vlist, &ac, FALSE, &as);
+ for (i = 0; i < ac; i++) {
+ v = VarFind(av[i], VAR_GLOBAL, 0);
+ if (!v)
+ continue;
+ if (!unexport_env &&
+ (v->flags & (VAR_EXPORTED|VAR_REEXPORT)) == VAR_EXPORTED) {
+ unsetenv(v->name);
+ }
+ v->flags &= ~(VAR_EXPORTED|VAR_REEXPORT);
+ /*
+ * If we are unexporting a list,
+ * remove each one from .MAKE.EXPORTED.
+ * If we are removing them all,
+ * just delete .MAKE.EXPORTED below.
+ */
+ if (vlist == str) {
+ n = snprintf(tmp, sizeof(tmp),
+ "${" MAKE_EXPORTED ":N%s}", v->name);
+ if (n < (int)sizeof(tmp)) {
+ cp = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES);
+ Var_Set(MAKE_EXPORTED, cp, VAR_GLOBAL, 0);
+ free(cp);
+ }
+ }
+ }
+ free(as);
+ free(av);
+ if (vlist != str) {
+ Var_Delete(MAKE_EXPORTED, VAR_GLOBAL);
+ free(vlist);
+ }
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Set --
+ * Set the variable name to the value val in the given context.
+ *
+ * Input:
+ * name name of variable to set
+ * val value to give to the variable
+ * ctxt context in which to set it
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * If the variable doesn't yet exist, a new record is created for it.
+ * Else the old value is freed and the new one stuck in its place
+ *
+ * Notes:
+ * The variable is searched for only in its context before being
+ * created in that context. I.e. if the context is VAR_GLOBAL,
+ * only VAR_GLOBAL->context is searched. Likewise if it is VAR_CMD, only
+ * VAR_CMD->context is searched. This is done to avoid the literally
+ * thousands of unnecessary strcmp's that used to be done to
+ * set, say, $(@) or $(<).
+ * If the context is VAR_GLOBAL though, we check if the variable
+ * was set in VAR_CMD from the command line and skip it if so.
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Set(const char *name, const char *val, GNode *ctxt, int flags)
+{
+ Var *v;
+ char *expanded_name = NULL;
+
+ /*
+ * We only look for a variable in the given context since anything set
+ * here will override anything in a lower context, so there's not much
+ * point in searching them all just to save a bit of memory...
+ */
+ if (strchr(name, '$') != NULL) {
+ expanded_name = Var_Subst(NULL, name, ctxt, VARF_WANTRES);
+ if (expanded_name[0] == 0) {
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "Var_Set(\"%s\", \"%s\", ...) "
+ "name expands to empty string - ignored\n",
+ name, val);
+ }
+ free(expanded_name);
+ return;
+ }
+ name = expanded_name;
+ }
+ if (ctxt == VAR_GLOBAL) {
+ v = VarFind(name, VAR_CMD, 0);
+ if (v != NULL) {
+ if ((v->flags & VAR_FROM_CMD)) {
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "%s:%s = %s ignored!\n", ctxt->name, name, val);
+ }
+ goto out;
+ }
+ VarFreeEnv(v, TRUE);
+ }
+ }
+ v = VarFind(name, ctxt, 0);
+ if (v == NULL) {
+ if (ctxt == VAR_CMD && (flags & VAR_NO_EXPORT) == 0) {
+ /*
+ * This var would normally prevent the same name being added
+ * to VAR_GLOBAL, so delete it from there if needed.
+ * Otherwise -V name may show the wrong value.
+ */
+ Var_Delete(name, VAR_GLOBAL);
+ }
+ VarAdd(name, val, ctxt);
+ } else {
+ Buf_Empty(&v->val);
+ if (val)
+ Buf_AddBytes(&v->val, strlen(val), val);
+
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name, val);
+ }
+ if ((v->flags & VAR_EXPORTED)) {
+ Var_Export1(name, VAR_EXPORT_PARENT);
+ }
+ }
+ /*
+ * Any variables given on the command line are automatically exported
+ * to the environment (as per POSIX standard)
+ */
+ if (ctxt == VAR_CMD && (flags & VAR_NO_EXPORT) == 0) {
+ if (v == NULL) {
+ /* we just added it */
+ v = VarFind(name, ctxt, 0);
+ }
+ if (v != NULL)
+ v->flags |= VAR_FROM_CMD;
+ /*
+ * If requested, don't export these in the environment
+ * individually. We still put them in MAKEOVERRIDES so
+ * that the command-line settings continue to override
+ * Makefile settings.
+ */
+ if (varNoExportEnv != TRUE)
+ setenv(name, val ? val : "", 1);
+
+ Var_Append(MAKEOVERRIDES, name, VAR_GLOBAL);
+ }
+ if (*name == '.') {
+ if (strcmp(name, SAVE_DOLLARS) == 0)
+ save_dollars = s2Boolean(val, save_dollars);
+ }
+
+ out:
+ free(expanded_name);
+ if (v != NULL)
+ VarFreeEnv(v, TRUE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Append --
+ * The variable of the given name has the given value appended to it in
+ * the given context.
+ *
+ * Input:
+ * name name of variable to modify
+ * val String to append to it
+ * ctxt Context in which this should occur
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * If the variable doesn't exist, it is created. Else the strings
+ * are concatenated (with a space in between).
+ *
+ * Notes:
+ * Only if the variable is being sought in the global context is the
+ * environment searched.
+ * XXX: Knows its calling circumstances in that if called with ctxt
+ * an actual target, it will only search that context since only
+ * a local variable could be being appended to. This is actually
+ * a big win and must be tolerated.
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Append(const char *name, const char *val, GNode *ctxt)
+{
+ Var *v;
+ Hash_Entry *h;
+ char *expanded_name = NULL;
+
+ if (strchr(name, '$') != NULL) {
+ expanded_name = Var_Subst(NULL, name, ctxt, VARF_WANTRES);
+ if (expanded_name[0] == 0) {
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "Var_Append(\"%s\", \"%s\", ...) "
+ "name expands to empty string - ignored\n",
+ name, val);
+ }
+ free(expanded_name);
+ return;
+ }
+ name = expanded_name;
+ }
+
+ v = VarFind(name, ctxt, (ctxt == VAR_GLOBAL) ? (FIND_CMD|FIND_ENV) : 0);
+
+ if (v == NULL) {
+ Var_Set(name, val, ctxt, 0);
+ } else if (ctxt == VAR_CMD || !(v->flags & VAR_FROM_CMD)) {
+ Buf_AddByte(&v->val, ' ');
+ Buf_AddBytes(&v->val, strlen(val), val);
+
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name,
+ Buf_GetAll(&v->val, NULL));
+ }
+
+ if (v->flags & VAR_FROM_ENV) {
+ /*
+ * If the original variable came from the environment, we
+ * have to install it in the global context (we could place
+ * it in the environment, but then we should provide a way to
+ * export other variables...)
+ */
+ v->flags &= ~VAR_FROM_ENV;
+ h = Hash_CreateEntry(&ctxt->context, name, NULL);
+ Hash_SetValue(h, v);
+ }
+ }
+ free(expanded_name);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Exists --
+ * See if the given variable exists.
+ *
+ * Input:
+ * name Variable to find
+ * ctxt Context in which to start search
+ *
+ * Results:
+ * TRUE if it does, FALSE if it doesn't
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Var_Exists(const char *name, GNode *ctxt)
+{
+ Var *v;
+ char *cp;
+
+ if ((cp = strchr(name, '$')) != NULL) {
+ cp = Var_Subst(NULL, name, ctxt, VARF_WANTRES);
+ }
+ v = VarFind(cp ? cp : name, ctxt, FIND_CMD|FIND_GLOBAL|FIND_ENV);
+ free(cp);
+ if (v == NULL) {
+ return(FALSE);
+ } else {
+ (void)VarFreeEnv(v, TRUE);
+ }
+ return(TRUE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Value --
+ * Return the value of the named variable in the given context
+ *
+ * Input:
+ * name name to find
+ * ctxt context in which to search for it
+ *
+ * Results:
+ * The value if the variable exists, NULL if it doesn't
+ *
+ * Side Effects:
+ * None
+ *-----------------------------------------------------------------------
+ */
+char *
+Var_Value(const char *name, GNode *ctxt, char **frp)
+{
+ Var *v;
+
+ v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
+ *frp = NULL;
+ if (v != NULL) {
+ char *p = (Buf_GetAll(&v->val, NULL));
+ if (VarFreeEnv(v, FALSE))
+ *frp = p;
+ return p;
+ } else {
+ return NULL;
+ }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarHead --
+ * Remove the tail of the given word and place the result in the given
+ * buffer.
+ *
+ * Input:
+ * word Word to trim
+ * addSpace True if need to add a space to the buffer
+ * before sticking in the head
+ * buf Buffer in which to store it
+ *
+ * Results:
+ * TRUE if characters were added to the buffer (a space needs to be
+ * added to the buffer before the next word).
+ *
+ * Side Effects:
+ * The trimmed word is added to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarHead(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *dummy MAKE_ATTR_UNUSED)
+{
+ char *slash;
+
+ slash = strrchr(word, '/');
+ if (slash != NULL) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ *slash = '\0';
+ Buf_AddBytes(buf, strlen(word), word);
+ *slash = '/';
+ return (TRUE);
+ } else {
+ /*
+ * If no directory part, give . (q.v. the POSIX standard)
+ */
+ if (addSpace && vpstate->varSpace)
+ Buf_AddByte(buf, vpstate->varSpace);
+ Buf_AddByte(buf, '.');
+ }
+ return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarTail --
+ * Remove the head of the given word and place the result in the given
+ * buffer.
+ *
+ * Input:
+ * word Word to trim
+ * addSpace True if need to add a space to the buffer
+ * before adding the tail
+ * buf Buffer in which to store it
+ *
+ * Results:
+ * TRUE if characters were added to the buffer (a space needs to be
+ * added to the buffer before the next word).
+ *
+ * Side Effects:
+ * The trimmed word is added to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarTail(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *dummy MAKE_ATTR_UNUSED)
+{
+ char *slash;
+
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+
+ slash = strrchr(word, '/');
+ if (slash != NULL) {
+ *slash++ = '\0';
+ Buf_AddBytes(buf, strlen(slash), slash);
+ slash[-1] = '/';
+ } else {
+ Buf_AddBytes(buf, strlen(word), word);
+ }
+ return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarSuffix --
+ * Place the suffix of the given word in the given buffer.
+ *
+ * Input:
+ * word Word to trim
+ * addSpace TRUE if need to add a space before placing the
+ * suffix in the buffer
+ * buf Buffer in which to store it
+ *
+ * Results:
+ * TRUE if characters were added to the buffer (a space needs to be
+ * added to the buffer before the next word).
+ *
+ * Side Effects:
+ * The suffix from the word is placed in the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarSuffix(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *dummy MAKE_ATTR_UNUSED)
+{
+ char *dot;
+
+ dot = strrchr(word, '.');
+ if (dot != NULL) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ *dot++ = '\0';
+ Buf_AddBytes(buf, strlen(dot), dot);
+ dot[-1] = '.';
+ addSpace = TRUE;
+ }
+ return addSpace;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarRoot --
+ * Remove the suffix of the given word and place the result in the
+ * buffer.
+ *
+ * Input:
+ * word Word to trim
+ * addSpace TRUE if need to add a space to the buffer
+ * before placing the root in it
+ * buf Buffer in which to store it
+ *
+ * Results:
+ * TRUE if characters were added to the buffer (a space needs to be
+ * added to the buffer before the next word).
+ *
+ * Side Effects:
+ * The trimmed word is added to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarRoot(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *dummy MAKE_ATTR_UNUSED)
+{
+ char *dot;
+
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+
+ dot = strrchr(word, '.');
+ if (dot != NULL) {
+ *dot = '\0';
+ Buf_AddBytes(buf, strlen(word), word);
+ *dot = '.';
+ } else {
+ Buf_AddBytes(buf, strlen(word), word);
+ }
+ return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarMatch --
+ * Place the word in the buffer if it matches the given pattern.
+ * Callback function for VarModify to implement the :M modifier.
+ *
+ * Input:
+ * word Word to examine
+ * addSpace TRUE if need to add a space to the buffer
+ * before adding the word, if it matches
+ * buf Buffer in which to store it
+ * pattern Pattern the word must match
+ *
+ * Results:
+ * TRUE if a space should be placed in the buffer before the next
+ * word.
+ *
+ * Side Effects:
+ * The word may be copied to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarMatch(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *pattern)
+{
+ if (DEBUG(VAR))
+ fprintf(debug_file, "VarMatch [%s] [%s]\n", word, (char *)pattern);
+ if (Str_Match(word, (char *)pattern)) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ addSpace = TRUE;
+ Buf_AddBytes(buf, strlen(word), word);
+ }
+ return(addSpace);
+}
+
+#ifdef SYSVVARSUB
+/*-
+ *-----------------------------------------------------------------------
+ * VarSYSVMatch --
+ * Place the word in the buffer if it matches the given pattern.
+ * Callback function for VarModify to implement the System V %
+ * modifiers.
+ *
+ * Input:
+ * word Word to examine
+ * addSpace TRUE if need to add a space to the buffer
+ * before adding the word, if it matches
+ * buf Buffer in which to store it
+ * patp Pattern the word must match
+ *
+ * Results:
+ * TRUE if a space should be placed in the buffer before the next
+ * word.
+ *
+ * Side Effects:
+ * The word may be copied to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarSYSVMatch(GNode *ctx, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *patp)
+{
+ int len;
+ char *ptr;
+ VarPattern *pat = (VarPattern *)patp;
+ char *varexp;
+
+ if (addSpace && vpstate->varSpace)
+ Buf_AddByte(buf, vpstate->varSpace);
+
+ addSpace = TRUE;
+
+ if ((ptr = Str_SYSVMatch(word, pat->lhs, &len)) != NULL) {
+ varexp = Var_Subst(NULL, pat->rhs, ctx, VARF_WANTRES);
+ Str_SYSVSubst(buf, varexp, ptr, len);
+ free(varexp);
+ } else {
+ Buf_AddBytes(buf, strlen(word), word);
+ }
+
+ return(addSpace);
+}
+#endif
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarNoMatch --
+ * Place the word in the buffer if it doesn't match the given pattern.
+ * Callback function for VarModify to implement the :N modifier.
+ *
+ * Input:
+ * word Word to examine
+ * addSpace TRUE if need to add a space to the buffer
+ * before adding the word, if it matches
+ * buf Buffer in which to store it
+ * pattern Pattern the word must match
+ *
+ * Results:
+ * TRUE if a space should be placed in the buffer before the next
+ * word.
+ *
+ * Side Effects:
+ * The word may be copied to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarNoMatch(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *pattern)
+{
+ if (!Str_Match(word, (char *)pattern)) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ addSpace = TRUE;
+ Buf_AddBytes(buf, strlen(word), word);
+ }
+ return(addSpace);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarSubstitute --
+ * Perform a string-substitution on the given word, placing the
+ * result in the passed buffer.
+ *
+ * Input:
+ * word Word to modify
+ * addSpace True if space should be added before
+ * other characters
+ * buf Buffer for result
+ * patternp Pattern for substitution
+ *
+ * Results:
+ * TRUE if a space is needed before more characters are added.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarSubstitute(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *patternp)
+{
+ int wordLen; /* Length of word */
+ char *cp; /* General pointer */
+ VarPattern *pattern = (VarPattern *)patternp;
+
+ wordLen = strlen(word);
+ if ((pattern->flags & (VAR_SUB_ONE|VAR_SUB_MATCHED)) !=
+ (VAR_SUB_ONE|VAR_SUB_MATCHED)) {
+ /*
+ * Still substituting -- break it down into simple anchored cases
+ * and if none of them fits, perform the general substitution case.
+ */
+ if ((pattern->flags & VAR_MATCH_START) &&
+ (strncmp(word, pattern->lhs, pattern->leftLen) == 0)) {
+ /*
+ * Anchored at start and beginning of word matches pattern
+ */
+ if ((pattern->flags & VAR_MATCH_END) &&
+ (wordLen == pattern->leftLen)) {
+ /*
+ * Also anchored at end and matches to the end (word
+ * is same length as pattern) add space and rhs only
+ * if rhs is non-null.
+ */
+ if (pattern->rightLen != 0) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ addSpace = TRUE;
+ Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
+ }
+ pattern->flags |= VAR_SUB_MATCHED;
+ } else if (pattern->flags & VAR_MATCH_END) {
+ /*
+ * Doesn't match to end -- copy word wholesale
+ */
+ goto nosub;
+ } else {
+ /*
+ * Matches at start but need to copy in trailing characters
+ */
+ if ((pattern->rightLen + wordLen - pattern->leftLen) != 0){
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ addSpace = TRUE;
+ }
+ Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
+ Buf_AddBytes(buf, wordLen - pattern->leftLen,
+ (word + pattern->leftLen));
+ pattern->flags |= VAR_SUB_MATCHED;
+ }
+ } else if (pattern->flags & VAR_MATCH_START) {
+ /*
+ * Had to match at start of word and didn't -- copy whole word.
+ */
+ goto nosub;
+ } else if (pattern->flags & VAR_MATCH_END) {
+ /*
+ * Anchored at end, Find only place match could occur (leftLen
+ * characters from the end of the word) and see if it does. Note
+ * that because the $ will be left at the end of the lhs, we have
+ * to use strncmp.
+ */
+ cp = word + (wordLen - pattern->leftLen);
+ if ((cp >= word) &&
+ (strncmp(cp, pattern->lhs, pattern->leftLen) == 0)) {
+ /*
+ * Match found. If we will place characters in the buffer,
+ * add a space before hand as indicated by addSpace, then
+ * stuff in the initial, unmatched part of the word followed
+ * by the right-hand-side.
+ */
+ if (((cp - word) + pattern->rightLen) != 0) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ addSpace = TRUE;
+ }
+ Buf_AddBytes(buf, cp - word, word);
+ Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
+ pattern->flags |= VAR_SUB_MATCHED;
+ } else {
+ /*
+ * Had to match at end and didn't. Copy entire word.
+ */
+ goto nosub;
+ }
+ } else {
+ /*
+ * Pattern is unanchored: search for the pattern in the word using
+ * String_FindSubstring, copying unmatched portions and the
+ * right-hand-side for each match found, handling non-global
+ * substitutions correctly, etc. When the loop is done, any
+ * remaining part of the word (word and wordLen are adjusted
+ * accordingly through the loop) is copied straight into the
+ * buffer.
+ * addSpace is set FALSE as soon as a space is added to the
+ * buffer.
+ */
+ Boolean done;
+ int origSize;
+
+ done = FALSE;
+ origSize = Buf_Size(buf);
+ while (!done) {
+ cp = Str_FindSubstring(word, pattern->lhs);
+ if (cp != NULL) {
+ if (addSpace && (((cp - word) + pattern->rightLen) != 0)){
+ Buf_AddByte(buf, vpstate->varSpace);
+ addSpace = FALSE;
+ }
+ Buf_AddBytes(buf, cp-word, word);
+ Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
+ wordLen -= (cp - word) + pattern->leftLen;
+ word = cp + pattern->leftLen;
+ if (wordLen == 0) {
+ done = TRUE;
+ }
+ if ((pattern->flags & VAR_SUB_GLOBAL) == 0) {
+ done = TRUE;
+ }
+ pattern->flags |= VAR_SUB_MATCHED;
+ } else {
+ done = TRUE;
+ }
+ }
+ if (wordLen != 0) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ Buf_AddBytes(buf, wordLen, word);
+ }
+ /*
+ * If added characters to the buffer, need to add a space
+ * before we add any more. If we didn't add any, just return
+ * the previous value of addSpace.
+ */
+ return ((Buf_Size(buf) != origSize) || addSpace);
+ }
+ return (addSpace);
+ }
+ nosub:
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ Buf_AddBytes(buf, wordLen, word);
+ return(TRUE);
+}
+
+#ifndef NO_REGEX
+/*-
+ *-----------------------------------------------------------------------
+ * VarREError --
+ * Print the error caused by a regcomp or regexec call.
+ *
+ * Results:
+ * None.
+ *
+ * Side Effects:
+ * An error gets printed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+VarREError(int reerr, regex_t *pat, const char *str)
+{
+ char *errbuf;
+ int errlen;
+
+ errlen = regerror(reerr, pat, 0, 0);
+ errbuf = bmake_malloc(errlen);
+ regerror(reerr, pat, errbuf, errlen);
+ Error("%s: %s", str, errbuf);
+ free(errbuf);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarRESubstitute --
+ * Perform a regex substitution on the given word, placing the
+ * result in the passed buffer.
+ *
+ * Results:
+ * TRUE if a space is needed before more characters are added.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarRESubstitute(GNode *ctx MAKE_ATTR_UNUSED,
+ Var_Parse_State *vpstate MAKE_ATTR_UNUSED,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *patternp)
+{
+ VarREPattern *pat;
+ int xrv;
+ char *wp;
+ char *rp;
+ int added;
+ int flags = 0;
+
+#define MAYBE_ADD_SPACE() \
+ if (addSpace && !added) \
+ Buf_AddByte(buf, ' '); \
+ added = 1
+
+ added = 0;
+ wp = word;
+ pat = patternp;
+
+ if ((pat->flags & (VAR_SUB_ONE|VAR_SUB_MATCHED)) ==
+ (VAR_SUB_ONE|VAR_SUB_MATCHED))
+ xrv = REG_NOMATCH;
+ else {
+ tryagain:
+ xrv = regexec(&pat->re, wp, pat->nsub, pat->matches, flags);
+ }
+
+ switch (xrv) {
+ case 0:
+ pat->flags |= VAR_SUB_MATCHED;
+ if (pat->matches[0].rm_so > 0) {
+ MAYBE_ADD_SPACE();
+ Buf_AddBytes(buf, pat->matches[0].rm_so, wp);
+ }
+
+ for (rp = pat->replace; *rp; rp++) {
+ if ((*rp == '\\') && ((rp[1] == '&') || (rp[1] == '\\'))) {
+ MAYBE_ADD_SPACE();
+ Buf_AddByte(buf,rp[1]);
+ rp++;
+ }
+ else if ((*rp == '&') ||
+ ((*rp == '\\') && isdigit((unsigned char)rp[1]))) {
+ int n;
+ const char *subbuf;
+ int sublen;
+ char errstr[3];
+
+ if (*rp == '&') {
+ n = 0;
+ errstr[0] = '&';
+ errstr[1] = '\0';
+ } else {
+ n = rp[1] - '0';
+ errstr[0] = '\\';
+ errstr[1] = rp[1];
+ errstr[2] = '\0';
+ rp++;
+ }
+
+ if (n > pat->nsub) {
+ Error("No subexpression %s", &errstr[0]);
+ subbuf = "";
+ sublen = 0;
+ } else if ((pat->matches[n].rm_so == -1) &&
+ (pat->matches[n].rm_eo == -1)) {
+ Error("No match for subexpression %s", &errstr[0]);
+ subbuf = "";
+ sublen = 0;
+ } else {
+ subbuf = wp + pat->matches[n].rm_so;
+ sublen = pat->matches[n].rm_eo - pat->matches[n].rm_so;
+ }
+
+ if (sublen > 0) {
+ MAYBE_ADD_SPACE();
+ Buf_AddBytes(buf, sublen, subbuf);
+ }
+ } else {
+ MAYBE_ADD_SPACE();
+ Buf_AddByte(buf, *rp);
+ }
+ }
+ wp += pat->matches[0].rm_eo;
+ if (pat->flags & VAR_SUB_GLOBAL) {
+ flags |= REG_NOTBOL;
+ if (pat->matches[0].rm_so == 0 && pat->matches[0].rm_eo == 0) {
+ MAYBE_ADD_SPACE();
+ Buf_AddByte(buf, *wp);
+ wp++;
+
+ }
+ if (*wp)
+ goto tryagain;
+ }
+ if (*wp) {
+ MAYBE_ADD_SPACE();
+ Buf_AddBytes(buf, strlen(wp), wp);
+ }
+ break;
+ default:
+ VarREError(xrv, &pat->re, "Unexpected regex error");
+ /* fall through */
+ case REG_NOMATCH:
+ if (*wp) {
+ MAYBE_ADD_SPACE();
+ Buf_AddBytes(buf,strlen(wp),wp);
+ }
+ break;
+ }
+ return(addSpace||added);
+}
+#endif
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarLoopExpand --
+ * Implements the :@<temp>@<string>@ modifier of ODE make.
+ * We set the temp variable named in pattern.lhs to word and expand
+ * pattern.rhs storing the result in the passed buffer.
+ *
+ * Input:
+ * word Word to modify
+ * addSpace True if space should be added before
+ * other characters
+ * buf Buffer for result
+ * pattern Datafor substitution
+ *
+ * Results:
+ * TRUE if a space is needed before more characters are added.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarLoopExpand(GNode *ctx MAKE_ATTR_UNUSED,
+ Var_Parse_State *vpstate MAKE_ATTR_UNUSED,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *loopp)
+{
+ VarLoop_t *loop = (VarLoop_t *)loopp;
+ char *s;
+ int slen;
+
+ if (word && *word) {
+ Var_Set(loop->tvar, word, loop->ctxt, VAR_NO_EXPORT);
+ s = Var_Subst(NULL, loop->str, loop->ctxt, loop->errnum | VARF_WANTRES);
+ if (s != NULL && *s != '\0') {
+ if (addSpace && *s != '\n')
+ Buf_AddByte(buf, ' ');
+ Buf_AddBytes(buf, (slen = strlen(s)), s);
+ addSpace = (slen > 0 && s[slen - 1] != '\n');
+ }
+ free(s);
+ }
+ return addSpace;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarSelectWords --
+ * Implements the :[start..end] modifier.
+ * This is a special case of VarModify since we want to be able
+ * to scan the list backwards if start > end.
+ *
+ * Input:
+ * str String whose words should be trimmed
+ * seldata words to select
+ *
+ * Results:
+ * A string of all the words selected.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarSelectWords(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ const char *str, VarSelectWords_t *seldata)
+{
+ Buffer buf; /* Buffer for the new string */
+ Boolean addSpace; /* TRUE if need to add a space to the
+ * buffer before adding the trimmed
+ * word */
+ char **av; /* word list */
+ char *as; /* word list memory */
+ int ac, i;
+ int start, end, step;
+
+ Buf_Init(&buf, 0);
+ addSpace = FALSE;
+
+ if (vpstate->oneBigWord) {
+ /* fake what brk_string() would do if there were only one word */
+ ac = 1;
+ av = bmake_malloc((ac + 1) * sizeof(char *));
+ as = bmake_strdup(str);
+ av[0] = as;
+ av[1] = NULL;
+ } else {
+ av = brk_string(str, &ac, FALSE, &as);
+ }
+
+ /*
+ * Now sanitize seldata.
+ * If seldata->start or seldata->end are negative, convert them to
+ * the positive equivalents (-1 gets converted to argc, -2 gets
+ * converted to (argc-1), etc.).
+ */
+ if (seldata->start < 0)
+ seldata->start = ac + seldata->start + 1;
+ if (seldata->end < 0)
+ seldata->end = ac + seldata->end + 1;
+
+ /*
+ * We avoid scanning more of the list than we need to.
+ */
+ if (seldata->start > seldata->end) {
+ start = MIN(ac, seldata->start) - 1;
+ end = MAX(0, seldata->end - 1);
+ step = -1;
+ } else {
+ start = MAX(0, seldata->start - 1);
+ end = MIN(ac, seldata->end);
+ step = 1;
+ }
+
+ for (i = start;
+ (step < 0 && i >= end) || (step > 0 && i < end);
+ i += step) {
+ if (av[i] && *av[i]) {
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(&buf, vpstate->varSpace);
+ }
+ Buf_AddBytes(&buf, strlen(av[i]), av[i]);
+ addSpace = TRUE;
+ }
+ }
+
+ free(as);
+ free(av);
+
+ return Buf_Destroy(&buf, FALSE);
+}
+
+
+/*-
+ * VarRealpath --
+ * Replace each word with the result of realpath()
+ * if successful.
+ */
+static Boolean
+VarRealpath(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate,
+ char *word, Boolean addSpace, Buffer *buf,
+ void *patternp MAKE_ATTR_UNUSED)
+{
+ struct stat st;
+ char rbuf[MAXPATHLEN];
+ char *rp;
+
+ if (addSpace && vpstate->varSpace) {
+ Buf_AddByte(buf, vpstate->varSpace);
+ }
+ addSpace = TRUE;
+ rp = cached_realpath(word, rbuf);
+ if (rp && *rp == '/' && stat(rp, &st) == 0)
+ word = rp;
+
+ Buf_AddBytes(buf, strlen(word), word);
+ return(addSpace);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarModify --
+ * Modify each of the words of the passed string using the given
+ * function. Used to implement all modifiers.
+ *
+ * Input:
+ * str String whose words should be trimmed
+ * modProc Function to use to modify them
+ * datum Datum to pass it
+ *
+ * Results:
+ * A string of all the words modified appropriately.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarModify(GNode *ctx, Var_Parse_State *vpstate,
+ const char *str,
+ Boolean (*modProc)(GNode *, Var_Parse_State *, char *,
+ Boolean, Buffer *, void *),
+ void *datum)
+{
+ Buffer buf; /* Buffer for the new string */
+ Boolean addSpace; /* TRUE if need to add a space to the
+ * buffer before adding the trimmed
+ * word */
+ char **av; /* word list */
+ char *as; /* word list memory */
+ int ac, i;
+
+ Buf_Init(&buf, 0);
+ addSpace = FALSE;
+
+ if (vpstate->oneBigWord) {
+ /* fake what brk_string() would do if there were only one word */
+ ac = 1;
+ av = bmake_malloc((ac + 1) * sizeof(char *));
+ as = bmake_strdup(str);
+ av[0] = as;
+ av[1] = NULL;
+ } else {
+ av = brk_string(str, &ac, FALSE, &as);
+ }
+
+ for (i = 0; i < ac; i++) {
+ addSpace = (*modProc)(ctx, vpstate, av[i], addSpace, &buf, datum);
+ }
+
+ free(as);
+ free(av);
+
+ return Buf_Destroy(&buf, FALSE);
+}
+
+
+static int
+VarWordCompare(const void *a, const void *b)
+{
+ int r = strcmp(*(const char * const *)a, *(const char * const *)b);
+ return r;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarOrder --
+ * Order the words in the string.
+ *
+ * Input:
+ * str String whose words should be sorted.
+ * otype How to order: s - sort, x - random.
+ *
+ * Results:
+ * A string containing the words ordered.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarOrder(const char *str, const char otype)
+{
+ Buffer buf; /* Buffer for the new string */
+ char **av; /* word list [first word does not count] */
+ char *as; /* word list memory */
+ int ac, i;
+
+ Buf_Init(&buf, 0);
+
+ av = brk_string(str, &ac, FALSE, &as);
+
+ if (ac > 0)
+ switch (otype) {
+ case 's': /* sort alphabetically */
+ qsort(av, ac, sizeof(char *), VarWordCompare);
+ break;
+ case 'x': /* randomize */
+ {
+ int rndidx;
+ char *t;
+
+ /*
+ * We will use [ac..2] range for mod factors. This will produce
+ * random numbers in [(ac-1)..0] interval, and minimal
+ * reasonable value for mod factor is 2 (the mod 1 will produce
+ * 0 with probability 1).
+ */
+ for (i = ac-1; i > 0; i--) {
+ rndidx = random() % (i + 1);
+ if (i != rndidx) {
+ t = av[i];
+ av[i] = av[rndidx];
+ av[rndidx] = t;
+ }
+ }
+ }
+ } /* end of switch */
+
+ for (i = 0; i < ac; i++) {
+ Buf_AddBytes(&buf, strlen(av[i]), av[i]);
+ if (i != ac - 1)
+ Buf_AddByte(&buf, ' ');
+ }
+
+ free(as);
+ free(av);
+
+ return Buf_Destroy(&buf, FALSE);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarUniq --
+ * Remove adjacent duplicate words.
+ *
+ * Input:
+ * str String whose words should be sorted
+ *
+ * Results:
+ * A string containing the resulting words.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarUniq(const char *str)
+{
+ Buffer buf; /* Buffer for new string */
+ char **av; /* List of words to affect */
+ char *as; /* Word list memory */
+ int ac, i, j;
+
+ Buf_Init(&buf, 0);
+ av = brk_string(str, &ac, FALSE, &as);
+
+ if (ac > 1) {
+ for (j = 0, i = 1; i < ac; i++)
+ if (strcmp(av[i], av[j]) != 0 && (++j != i))
+ av[j] = av[i];
+ ac = j + 1;
+ }
+
+ for (i = 0; i < ac; i++) {
+ Buf_AddBytes(&buf, strlen(av[i]), av[i]);
+ if (i != ac - 1)
+ Buf_AddByte(&buf, ' ');
+ }
+
+ free(as);
+ free(av);
+
+ return Buf_Destroy(&buf, FALSE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarRange --
+ * Return an integer sequence
+ *
+ * Input:
+ * str String whose words provide default range
+ * ac range length, if 0 use str words
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarRange(const char *str, int ac)
+{
+ Buffer buf; /* Buffer for new string */
+ char tmp[32]; /* each element */
+ char **av; /* List of words to affect */
+ char *as; /* Word list memory */
+ int i, n;
+
+ Buf_Init(&buf, 0);
+ if (ac > 0) {
+ as = NULL;
+ av = NULL;
+ } else {
+ av = brk_string(str, &ac, FALSE, &as);
+ }
+ for (i = 0; i < ac; i++) {
+ n = snprintf(tmp, sizeof(tmp), "%d", 1 + i);
+ if (n >= (int)sizeof(tmp))
+ break;
+ Buf_AddBytes(&buf, n, tmp);
+ if (i != ac - 1)
+ Buf_AddByte(&buf, ' ');
+ }
+
+ free(as);
+ free(av);
+
+ return Buf_Destroy(&buf, FALSE);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarGetPattern --
+ * Pass through the tstr looking for 1) escaped delimiters,
+ * '$'s and backslashes (place the escaped character in
+ * uninterpreted) and 2) unescaped $'s that aren't before
+ * the delimiter (expand the variable substitution unless flags
+ * has VAR_NOSUBST set).
+ * Return the expanded string or NULL if the delimiter was missing
+ * If pattern is specified, handle escaped ampersands, and replace
+ * unescaped ampersands with the lhs of the pattern.
+ *
+ * Results:
+ * A string of all the words modified appropriately.
+ * If length is specified, return the string length of the buffer
+ * If flags is specified and the last character of the pattern is a
+ * $ set the VAR_MATCH_END bit of flags.
+ *
+ * Side Effects:
+ * None.
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarGetPattern(GNode *ctxt, Var_Parse_State *vpstate MAKE_ATTR_UNUSED,
+ int flags, const char **tstr, int delim, int *vflags,
+ int *length, VarPattern *pattern)
+{
+ const char *cp;
+ char *rstr;
+ Buffer buf;
+ int junk;
+ int errnum = flags & VARF_UNDEFERR;
+
+ Buf_Init(&buf, 0);
+ if (length == NULL)
+ length = &junk;
+
+#define IS_A_MATCH(cp, delim) \
+ ((cp[0] == '\\') && ((cp[1] == delim) || \
+ (cp[1] == '\\') || (cp[1] == '$') || (pattern && (cp[1] == '&'))))
+
+ /*
+ * Skim through until the matching delimiter is found;
+ * pick up variable substitutions on the way. Also allow
+ * backslashes to quote the delimiter, $, and \, but don't
+ * touch other backslashes.
+ */
+ for (cp = *tstr; *cp && (*cp != delim); cp++) {
+ if (IS_A_MATCH(cp, delim)) {
+ Buf_AddByte(&buf, cp[1]);
+ cp++;
+ } else if (*cp == '$') {
+ if (cp[1] == delim) {
+ if (vflags == NULL)
+ Buf_AddByte(&buf, *cp);
+ else
+ /*
+ * Unescaped $ at end of pattern => anchor
+ * pattern at end.
+ */
+ *vflags |= VAR_MATCH_END;
+ } else {
+ if (vflags == NULL || (*vflags & VAR_NOSUBST) == 0) {
+ char *cp2;
+ int len;
+ void *freeIt;
+
+ /*
+ * If unescaped dollar sign not before the
+ * delimiter, assume it's a variable
+ * substitution and recurse.
+ */
+ cp2 = Var_Parse(cp, ctxt, errnum | VARF_WANTRES, &len,
+ &freeIt);
+ Buf_AddBytes(&buf, strlen(cp2), cp2);
+ free(freeIt);
+ cp += len - 1;
+ } else {
+ const char *cp2 = &cp[1];
+
+ if (*cp2 == PROPEN || *cp2 == BROPEN) {
+ /*
+ * Find the end of this variable reference
+ * and suck it in without further ado.
+ * It will be interperated later.
+ */
+ int have = *cp2;
+ int want = (*cp2 == PROPEN) ? PRCLOSE : BRCLOSE;
+ int depth = 1;
+
+ for (++cp2; *cp2 != '\0' && depth > 0; ++cp2) {
+ if (cp2[-1] != '\\') {
+ if (*cp2 == have)
+ ++depth;
+ if (*cp2 == want)
+ --depth;
+ }
+ }
+ Buf_AddBytes(&buf, cp2 - cp, cp);
+ cp = --cp2;
+ } else
+ Buf_AddByte(&buf, *cp);
+ }
+ }
+ }
+ else if (pattern && *cp == '&')
+ Buf_AddBytes(&buf, pattern->leftLen, pattern->lhs);
+ else
+ Buf_AddByte(&buf, *cp);
+ }
+
+ if (*cp != delim) {
+ *tstr = cp;
+ *length = 0;
+ return NULL;
+ }
+
+ *tstr = ++cp;
+ *length = Buf_Size(&buf);
+ rstr = Buf_Destroy(&buf, FALSE);
+ if (DEBUG(VAR))
+ fprintf(debug_file, "Modifier pattern: \"%s\"\n", rstr);
+ return rstr;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarQuote --
+ * Quote shell meta-characters and space characters in the string
+ * if quoteDollar is set, also quote and double any '$' characters.
+ *
+ * Results:
+ * The quoted string
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarQuote(char *str, Boolean quoteDollar)
+{
+
+ Buffer buf;
+ const char *newline;
+ size_t nlen;
+
+ if ((newline = Shell_GetNewline()) == NULL)
+ newline = "\\\n";
+ nlen = strlen(newline);
+
+ Buf_Init(&buf, 0);
+
+ for (; *str != '\0'; str++) {
+ if (*str == '\n') {
+ Buf_AddBytes(&buf, nlen, newline);
+ continue;
+ }
+ if (isspace((unsigned char)*str) || ismeta((unsigned char)*str))
+ Buf_AddByte(&buf, '\\');
+ Buf_AddByte(&buf, *str);
+ if (quoteDollar && *str == '$')
+ Buf_AddBytes(&buf, 2, "\\$");
+ }
+
+ str = Buf_Destroy(&buf, FALSE);
+ if (DEBUG(VAR))
+ fprintf(debug_file, "QuoteMeta: [%s]\n", str);
+ return str;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarHash --
+ * Hash the string using the MurmurHash3 algorithm.
+ * Output is computed using 32bit Little Endian arithmetic.
+ *
+ * Input:
+ * str String to modify
+ *
+ * Results:
+ * Hash value of str, encoded as 8 hex digits.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarHash(char *str)
+{
+ static const char hexdigits[16] = "0123456789abcdef";
+ Buffer buf;
+ size_t len, len2;
+ unsigned char *ustr = (unsigned char *)str;
+ uint32_t h, k, c1, c2;
+
+ h = 0x971e137bU;
+ c1 = 0x95543787U;
+ c2 = 0x2ad7eb25U;
+ len2 = strlen(str);
+
+ for (len = len2; len; ) {
+ k = 0;
+ switch (len) {
+ default:
+ k = (ustr[3] << 24) | (ustr[2] << 16) | (ustr[1] << 8) | ustr[0];
+ len -= 4;
+ ustr += 4;
+ break;
+ case 3:
+ k |= (ustr[2] << 16);
+ case 2:
+ k |= (ustr[1] << 8);
+ case 1:
+ k |= ustr[0];
+ len = 0;
+ }
+ c1 = c1 * 5 + 0x7b7d159cU;
+ c2 = c2 * 5 + 0x6bce6396U;
+ k *= c1;
+ k = (k << 11) ^ (k >> 21);
+ k *= c2;
+ h = (h << 13) ^ (h >> 19);
+ h = h * 5 + 0x52dce729U;
+ h ^= k;
+ }
+ h ^= len2;
+ h *= 0x85ebca6b;
+ h ^= h >> 13;
+ h *= 0xc2b2ae35;
+ h ^= h >> 16;
+
+ Buf_Init(&buf, 0);
+ for (len = 0; len < 8; ++len) {
+ Buf_AddByte(&buf, hexdigits[h & 15]);
+ h >>= 4;
+ }
+
+ return Buf_Destroy(&buf, FALSE);
+}
+
+static char *
+VarStrftime(const char *fmt, int zulu, time_t utc)
+{
+ char buf[BUFSIZ];
+
+ if (!utc)
+ time(&utc);
+ if (!*fmt)
+ fmt = "%c";
+ strftime(buf, sizeof(buf), fmt, zulu ? gmtime(&utc) : localtime(&utc));
+
+ buf[sizeof(buf) - 1] = '\0';
+ return bmake_strdup(buf);
+}
+
+/*
+ * Now we need to apply any modifiers the user wants applied.
+ * These are:
+ * :M<pattern> words which match the given <pattern>.
+ * <pattern> is of the standard file
+ * wildcarding form.
+ * :N<pattern> words which do not match the given <pattern>.
+ * :S<d><pat1><d><pat2><d>[1gW]
+ * Substitute <pat2> for <pat1> in the value
+ * :C<d><pat1><d><pat2><d>[1gW]
+ * Substitute <pat2> for regex <pat1> in the value
+ * :H Substitute the head of each word
+ * :T Substitute the tail of each word
+ * :E Substitute the extension (minus '.') of
+ * each word
+ * :R Substitute the root of each word
+ * (pathname minus the suffix).
+ * :O ("Order") Alphabeticaly sort words in variable.
+ * :Ox ("intermiX") Randomize words in variable.
+ * :u ("uniq") Remove adjacent duplicate words.
+ * :tu Converts the variable contents to uppercase.
+ * :tl Converts the variable contents to lowercase.
+ * :ts[c] Sets varSpace - the char used to
+ * separate words to 'c'. If 'c' is
+ * omitted then no separation is used.
+ * :tW Treat the variable contents as a single
+ * word, even if it contains spaces.
+ * (Mnemonic: one big 'W'ord.)
+ * :tw Treat the variable contents as multiple
+ * space-separated words.
+ * (Mnemonic: many small 'w'ords.)
+ * :[index] Select a single word from the value.
+ * :[start..end] Select multiple words from the value.
+ * :[*] or :[0] Select the entire value, as a single
+ * word. Equivalent to :tW.
+ * :[@] Select the entire value, as multiple
+ * words. Undoes the effect of :[*].
+ * Equivalent to :tw.
+ * :[#] Returns the number of words in the value.
+ *
+ * :?<true-value>:<false-value>
+ * If the variable evaluates to true, return
+ * true value, else return the second value.
+ * :lhs=rhs Like :S, but the rhs goes to the end of
+ * the invocation.
+ * :sh Treat the current value as a command
+ * to be run, new value is its output.
+ * The following added so we can handle ODE makefiles.
+ * :@<tmpvar>@<newval>@
+ * Assign a temporary local variable <tmpvar>
+ * to the current value of each word in turn
+ * and replace each word with the result of
+ * evaluating <newval>
+ * :D<newval> Use <newval> as value if variable defined
+ * :U<newval> Use <newval> as value if variable undefined
+ * :L Use the name of the variable as the value.
+ * :P Use the path of the node that has the same
+ * name as the variable as the value. This
+ * basically includes an implied :L so that
+ * the common method of refering to the path
+ * of your dependent 'x' in a rule is to use
+ * the form '${x:P}'.
+ * :!<cmd>! Run cmd much the same as :sh run's the
+ * current value of the variable.
+ * The ::= modifiers, actually assign a value to the variable.
+ * Their main purpose is in supporting modifiers of .for loop
+ * iterators and other obscure uses. They always expand to
+ * nothing. In a target rule that would otherwise expand to an
+ * empty line they can be preceded with @: to keep make happy.
+ * Eg.
+ *
+ * foo: .USE
+ * .for i in ${.TARGET} ${.TARGET:R}.gz
+ * @: ${t::=$i}
+ * @echo blah ${t:T}
+ * .endfor
+ *
+ * ::=<str> Assigns <str> as the new value of variable.
+ * ::?=<str> Assigns <str> as value of variable if
+ * it was not already set.
+ * ::+=<str> Appends <str> to variable.
+ * ::!=<cmd> Assigns output of <cmd> as the new value of
+ * variable.
+ */
+
+/* we now have some modifiers with long names */
+#define STRMOD_MATCH(s, want, n) \
+ (strncmp(s, want, n) == 0 && (s[n] == endc || s[n] == ':'))
+#define STRMOD_MATCHX(s, want, n) \
+ (strncmp(s, want, n) == 0 && (s[n] == endc || s[n] == ':' || s[n] == '='))
+#define CHARMOD_MATCH(c) (c == endc || c == ':')
+
+static char *
+ApplyModifiers(char *nstr, const char *tstr,
+ int startc, int endc,
+ Var *v, GNode *ctxt, int flags,
+ int *lengthPtr, void **freePtr)
+{
+ const char *start;
+ const char *cp; /* Secondary pointer into str (place marker
+ * for tstr) */
+ char *newStr; /* New value to return */
+ char *ep;
+ char termc; /* Character which terminated scan */
+ int cnt; /* Used to count brace pairs when variable in
+ * in parens or braces */
+ char delim;
+ int modifier; /* that we are processing */
+ Var_Parse_State parsestate; /* Flags passed to helper functions */
+ time_t utc; /* for VarStrftime */
+
+ delim = '\0';
+ parsestate.oneBigWord = FALSE;
+ parsestate.varSpace = ' '; /* word separator */
+
+ start = cp = tstr;
+
+ while (*tstr && *tstr != endc) {
+
+ if (*tstr == '$') {
+ /*
+ * We may have some complex modifiers in a variable.
+ */
+ void *freeIt;
+ char *rval;
+ int rlen;
+ int c;
+
+ rval = Var_Parse(tstr, ctxt, flags, &rlen, &freeIt);
+
+ /*
+ * If we have not parsed up to endc or ':',
+ * we are not interested.
+ */
+ if (rval != NULL && *rval &&
+ (c = tstr[rlen]) != '\0' &&
+ c != ':' &&
+ c != endc) {
+ free(freeIt);
+ goto apply_mods;
+ }
+
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "Got '%s' from '%.*s'%.*s\n",
+ rval, rlen, tstr, rlen, tstr + rlen);
+ }
+
+ tstr += rlen;
+
+ if (rval != NULL && *rval) {
+ int used;
+
+ nstr = ApplyModifiers(nstr, rval,
+ 0, 0, v, ctxt, flags, &used, freePtr);
+ if (nstr == var_Error
+ || (nstr == varNoError && (flags & VARF_UNDEFERR) == 0)
+ || strlen(rval) != (size_t) used) {
+ free(freeIt);
+ goto out; /* error already reported */
+ }
+ }
+ free(freeIt);
+ if (*tstr == ':')
+ tstr++;
+ else if (!*tstr && endc) {
+ Error("Unclosed variable specification after complex modifier (expecting '%c') for %s", endc, v->name);
+ goto out;
+ }
+ continue;
+ }
+ apply_mods:
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "Applying[%s] :%c to \"%s\"\n", v->name,
+ *tstr, nstr);
+ }
+ newStr = var_Error;
+ switch ((modifier = *tstr)) {
+ case ':':
+ {
+ if (tstr[1] == '=' ||
+ (tstr[2] == '=' &&
+ (tstr[1] == '!' || tstr[1] == '+' || tstr[1] == '?'))) {
+ /*
+ * "::=", "::!=", "::+=", or "::?="
+ */
+ GNode *v_ctxt; /* context where v belongs */
+ const char *emsg;
+ char *sv_name;
+ VarPattern pattern;
+ int how;
+ int vflags;
+
+ if (v->name[0] == 0)
+ goto bad_modifier;
+
+ v_ctxt = ctxt;
+ sv_name = NULL;
+ ++tstr;
+ if (v->flags & VAR_JUNK) {
+ /*
+ * We need to bmake_strdup() it incase
+ * VarGetPattern() recurses.
+ */
+ sv_name = v->name;
+ v->name = bmake_strdup(v->name);
+ } else if (ctxt != VAR_GLOBAL) {
+ Var *gv = VarFind(v->name, ctxt, 0);
+ if (gv == NULL)
+ v_ctxt = VAR_GLOBAL;
+ else
+ VarFreeEnv(gv, TRUE);
+ }
+
+ switch ((how = *tstr)) {
+ case '+':
+ case '?':
+ case '!':
+ cp = &tstr[2];
+ break;
+ default:
+ cp = ++tstr;
+ break;
+ }
+ delim = startc == PROPEN ? PRCLOSE : BRCLOSE;
+ pattern.flags = 0;
+
+ vflags = (flags & VARF_WANTRES) ? 0 : VAR_NOSUBST;
+ pattern.rhs = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim, &vflags,
+ &pattern.rightLen,
+ NULL);
+ if (v->flags & VAR_JUNK) {
+ /* restore original name */
+ free(v->name);
+ v->name = sv_name;
+ }
+ if (pattern.rhs == NULL)
+ goto cleanup;
+
+ termc = *--cp;
+ delim = '\0';
+
+ if (flags & VARF_WANTRES) {
+ switch (how) {
+ case '+':
+ Var_Append(v->name, pattern.rhs, v_ctxt);
+ break;
+ case '!':
+ newStr = Cmd_Exec(pattern.rhs, &emsg);
+ if (emsg)
+ Error(emsg, nstr);
+ else
+ Var_Set(v->name, newStr, v_ctxt, 0);
+ free(newStr);
+ break;
+ case '?':
+ if ((v->flags & VAR_JUNK) == 0)
+ break;
+ /* FALLTHROUGH */
+ default:
+ Var_Set(v->name, pattern.rhs, v_ctxt, 0);
+ break;
+ }
+ }
+ free(UNCONST(pattern.rhs));
+ newStr = varNoError;
+ break;
+ }
+ goto default_case; /* "::<unrecognised>" */
+ }
+ case '@':
+ {
+ VarLoop_t loop;
+ int vflags = VAR_NOSUBST;
+
+ cp = ++tstr;
+ delim = '@';
+ if ((loop.tvar = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim,
+ &vflags, &loop.tvarLen,
+ NULL)) == NULL)
+ goto cleanup;
+
+ if ((loop.str = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim,
+ &vflags, &loop.strLen,
+ NULL)) == NULL)
+ goto cleanup;
+
+ termc = *cp;
+ delim = '\0';
+
+ loop.errnum = flags & VARF_UNDEFERR;
+ loop.ctxt = ctxt;
+ newStr = VarModify(ctxt, &parsestate, nstr, VarLoopExpand,
+ &loop);
+ Var_Delete(loop.tvar, ctxt);
+ free(loop.tvar);
+ free(loop.str);
+ break;
+ }
+ case '_': /* remember current value */
+ cp = tstr + 1; /* make sure it is set */
+ if (STRMOD_MATCHX(tstr, "_", 1)) {
+ if (tstr[1] == '=') {
+ char *np;
+ int n;
+
+ cp++;
+ n = strcspn(cp, ":)}");
+ np = bmake_strndup(cp, n+1);
+ np[n] = '\0';
+ cp = tstr + 2 + n;
+ Var_Set(np, nstr, ctxt, 0);
+ free(np);
+ } else {
+ Var_Set("_", nstr, ctxt, 0);
+ }
+ newStr = nstr;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+ case 'D':
+ case 'U':
+ {
+ Buffer buf; /* Buffer for patterns */
+ int nflags;
+
+ if (flags & VARF_WANTRES) {
+ int wantres;
+ if (*tstr == 'U')
+ wantres = ((v->flags & VAR_JUNK) != 0);
+ else
+ wantres = ((v->flags & VAR_JUNK) == 0);
+ nflags = flags & ~VARF_WANTRES;
+ if (wantres)
+ nflags |= VARF_WANTRES;
+ } else
+ nflags = flags;
+ /*
+ * Pass through tstr looking for 1) escaped delimiters,
+ * '$'s and backslashes (place the escaped character in
+ * uninterpreted) and 2) unescaped $'s that aren't before
+ * the delimiter (expand the variable substitution).
+ * The result is left in the Buffer buf.
+ */
+ Buf_Init(&buf, 0);
+ for (cp = tstr + 1;
+ *cp != endc && *cp != ':' && *cp != '\0';
+ cp++) {
+ if ((*cp == '\\') &&
+ ((cp[1] == ':') ||
+ (cp[1] == '$') ||
+ (cp[1] == endc) ||
+ (cp[1] == '\\')))
+ {
+ Buf_AddByte(&buf, cp[1]);
+ cp++;
+ } else if (*cp == '$') {
+ /*
+ * If unescaped dollar sign, assume it's a
+ * variable substitution and recurse.
+ */
+ char *cp2;
+ int len;
+ void *freeIt;
+
+ cp2 = Var_Parse(cp, ctxt, nflags, &len, &freeIt);
+ Buf_AddBytes(&buf, strlen(cp2), cp2);
+ free(freeIt);
+ cp += len - 1;
+ } else {
+ Buf_AddByte(&buf, *cp);
+ }
+ }
+
+ termc = *cp;
+
+ if ((v->flags & VAR_JUNK) != 0)
+ v->flags |= VAR_KEEP;
+ if (nflags & VARF_WANTRES) {
+ newStr = Buf_Destroy(&buf, FALSE);
+ } else {
+ newStr = nstr;
+ Buf_Destroy(&buf, TRUE);
+ }
+ break;
+ }
+ case 'L':
+ {
+ if ((v->flags & VAR_JUNK) != 0)
+ v->flags |= VAR_KEEP;
+ newStr = bmake_strdup(v->name);
+ cp = ++tstr;
+ termc = *tstr;
+ break;
+ }
+ case 'P':
+ {
+ GNode *gn;
+
+ if ((v->flags & VAR_JUNK) != 0)
+ v->flags |= VAR_KEEP;
+ gn = Targ_FindNode(v->name, TARG_NOCREATE);
+ if (gn == NULL || gn->type & OP_NOPATH) {
+ newStr = NULL;
+ } else if (gn->path) {
+ newStr = bmake_strdup(gn->path);
+ } else {
+ newStr = Dir_FindFile(v->name, Suff_FindPath(gn));
+ }
+ if (!newStr) {
+ newStr = bmake_strdup(v->name);
+ }
+ cp = ++tstr;
+ termc = *tstr;
+ break;
+ }
+ case '!':
+ {
+ const char *emsg;
+ VarPattern pattern;
+ pattern.flags = 0;
+
+ delim = '!';
+ emsg = NULL;
+ cp = ++tstr;
+ if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim,
+ NULL, &pattern.rightLen,
+ NULL)) == NULL)
+ goto cleanup;
+ if (flags & VARF_WANTRES)
+ newStr = Cmd_Exec(pattern.rhs, &emsg);
+ else
+ newStr = varNoError;
+ free(UNCONST(pattern.rhs));
+ if (emsg)
+ Error(emsg, nstr);
+ termc = *cp;
+ delim = '\0';
+ if (v->flags & VAR_JUNK) {
+ v->flags |= VAR_KEEP;
+ }
+ break;
+ }
+ case '[':
+ {
+ /*
+ * Look for the closing ']', recursively
+ * expanding any embedded variables.
+ *
+ * estr is a pointer to the expanded result,
+ * which we must free().
+ */
+ char *estr;
+
+ cp = tstr+1; /* point to char after '[' */
+ delim = ']'; /* look for closing ']' */
+ estr = VarGetPattern(ctxt, &parsestate,
+ flags, &cp, delim,
+ NULL, NULL, NULL);
+ if (estr == NULL)
+ goto cleanup; /* report missing ']' */
+ /* now cp points just after the closing ']' */
+ delim = '\0';
+ if (cp[0] != ':' && cp[0] != endc) {
+ /* Found junk after ']' */
+ free(estr);
+ goto bad_modifier;
+ }
+ if (estr[0] == '\0') {
+ /* Found empty square brackets in ":[]". */
+ free(estr);
+ goto bad_modifier;
+ } else if (estr[0] == '#' && estr[1] == '\0') {
+ /* Found ":[#]" */
+
+ /*
+ * We will need enough space for the decimal
+ * representation of an int. We calculate the
+ * space needed for the octal representation,
+ * and add enough slop to cope with a '-' sign
+ * (which should never be needed) and a '\0'
+ * string terminator.
+ */
+ int newStrSize =
+ (sizeof(int) * CHAR_BIT + 2) / 3 + 2;
+
+ newStr = bmake_malloc(newStrSize);
+ if (parsestate.oneBigWord) {
+ strncpy(newStr, "1", newStrSize);
+ } else {
+ /* XXX: brk_string() is a rather expensive
+ * way of counting words. */
+ char **av;
+ char *as;
+ int ac;
+
+ av = brk_string(nstr, &ac, FALSE, &as);
+ snprintf(newStr, newStrSize, "%d", ac);
+ free(as);
+ free(av);
+ }
+ termc = *cp;
+ free(estr);
+ break;
+ } else if (estr[0] == '*' && estr[1] == '\0') {
+ /* Found ":[*]" */
+ parsestate.oneBigWord = TRUE;
+ newStr = nstr;
+ termc = *cp;
+ free(estr);
+ break;
+ } else if (estr[0] == '@' && estr[1] == '\0') {
+ /* Found ":[@]" */
+ parsestate.oneBigWord = FALSE;
+ newStr = nstr;
+ termc = *cp;
+ free(estr);
+ break;
+ } else {
+ /*
+ * We expect estr to contain a single
+ * integer for :[N], or two integers
+ * separated by ".." for :[start..end].
+ */
+ VarSelectWords_t seldata = { 0, 0 };
+
+ seldata.start = strtol(estr, &ep, 0);
+ if (ep == estr) {
+ /* Found junk instead of a number */
+ free(estr);
+ goto bad_modifier;
+ } else if (ep[0] == '\0') {
+ /* Found only one integer in :[N] */
+ seldata.end = seldata.start;
+ } else if (ep[0] == '.' && ep[1] == '.' &&
+ ep[2] != '\0') {
+ /* Expecting another integer after ".." */
+ ep += 2;
+ seldata.end = strtol(ep, &ep, 0);
+ if (ep[0] != '\0') {
+ /* Found junk after ".." */
+ free(estr);
+ goto bad_modifier;
+ }
+ } else {
+ /* Found junk instead of ".." */
+ free(estr);
+ goto bad_modifier;
+ }
+ /*
+ * Now seldata is properly filled in,
+ * but we still have to check for 0 as
+ * a special case.
+ */
+ if (seldata.start == 0 && seldata.end == 0) {
+ /* ":[0]" or perhaps ":[0..0]" */
+ parsestate.oneBigWord = TRUE;
+ newStr = nstr;
+ termc = *cp;
+ free(estr);
+ break;
+ } else if (seldata.start == 0 ||
+ seldata.end == 0) {
+ /* ":[0..N]" or ":[N..0]" */
+ free(estr);
+ goto bad_modifier;
+ }
+ /*
+ * Normal case: select the words
+ * described by seldata.
+ */
+ newStr = VarSelectWords(ctxt, &parsestate,
+ nstr, &seldata);
+
+ termc = *cp;
+ free(estr);
+ break;
+ }
+
+ }
+ case 'g':
+ cp = tstr + 1; /* make sure it is set */
+ if (STRMOD_MATCHX(tstr, "gmtime", 6)) {
+ if (tstr[6] == '=') {
+ utc = strtoul(&tstr[7], &ep, 10);
+ cp = ep;
+ } else {
+ utc = 0;
+ cp = tstr + 6;
+ }
+ newStr = VarStrftime(nstr, 1, utc);
+ termc = *cp;
+ } else {
+ goto default_case;
+ }
+ break;
+ case 'h':
+ cp = tstr + 1; /* make sure it is set */
+ if (STRMOD_MATCH(tstr, "hash", 4)) {
+ newStr = VarHash(nstr);
+ cp = tstr + 4;
+ termc = *cp;
+ } else {
+ goto default_case;
+ }
+ break;
+ case 'l':
+ cp = tstr + 1; /* make sure it is set */
+ if (STRMOD_MATCHX(tstr, "localtime", 9)) {
+ if (tstr[9] == '=') {
+ utc = strtoul(&tstr[10], &ep, 10);
+ cp = ep;
+ } else {
+ utc = 0;
+ cp = tstr + 9;
+ }
+ newStr = VarStrftime(nstr, 0, utc);
+ termc = *cp;
+ } else {
+ goto default_case;
+ }
+ break;
+ case 't':
+ {
+ cp = tstr + 1; /* make sure it is set */
+ if (tstr[1] != endc && tstr[1] != ':') {
+ if (tstr[1] == 's') {
+ /*
+ * Use the char (if any) at tstr[2]
+ * as the word separator.
+ */
+ VarPattern pattern;
+
+ if (tstr[2] != endc &&
+ (tstr[3] == endc || tstr[3] == ':')) {
+ /* ":ts<unrecognised><endc>" or
+ * ":ts<unrecognised>:" */
+ parsestate.varSpace = tstr[2];
+ cp = tstr + 3;
+ } else if (tstr[2] == endc || tstr[2] == ':') {
+ /* ":ts<endc>" or ":ts:" */
+ parsestate.varSpace = 0; /* no separator */
+ cp = tstr + 2;
+ } else if (tstr[2] == '\\') {
+ const char *xp = &tstr[3];
+ int base = 8; /* assume octal */
+
+ switch (tstr[3]) {
+ case 'n':
+ parsestate.varSpace = '\n';
+ cp = tstr + 4;
+ break;
+ case 't':
+ parsestate.varSpace = '\t';
+ cp = tstr + 4;
+ break;
+ case 'x':
+ base = 16;
+ xp++;
+ goto get_numeric;
+ case '0':
+ base = 0;
+ goto get_numeric;
+ default:
+ if (isdigit((unsigned char)tstr[3])) {
+
+ get_numeric:
+ parsestate.varSpace =
+ strtoul(xp, &ep, base);
+ if (*ep != ':' && *ep != endc)
+ goto bad_modifier;
+ cp = ep;
+ } else {
+ /*
+ * ":ts<backslash><unrecognised>".
+ */
+ goto bad_modifier;
+ }
+ break;
+ }
+ } else {
+ /*
+ * Found ":ts<unrecognised><unrecognised>".
+ */
+ goto bad_modifier;
+ }
+
+ termc = *cp;
+
+ /*
+ * We cannot be certain that VarModify
+ * will be used - even if there is a
+ * subsequent modifier, so do a no-op
+ * VarSubstitute now to for str to be
+ * re-expanded without the spaces.
+ */
+ pattern.flags = VAR_SUB_ONE;
+ pattern.lhs = pattern.rhs = "\032";
+ pattern.leftLen = pattern.rightLen = 1;
+
+ newStr = VarModify(ctxt, &parsestate, nstr,
+ VarSubstitute,
+ &pattern);
+ } else if (tstr[2] == endc || tstr[2] == ':') {
+ /*
+ * Check for two-character options:
+ * ":tu", ":tl"
+ */
+ if (tstr[1] == 'A') { /* absolute path */
+ newStr = VarModify(ctxt, &parsestate, nstr,
+ VarRealpath, NULL);
+ cp = tstr + 2;
+ termc = *cp;
+ } else if (tstr[1] == 'u') {
+ char *dp = bmake_strdup(nstr);
+ for (newStr = dp; *dp; dp++)
+ *dp = toupper((unsigned char)*dp);
+ cp = tstr + 2;
+ termc = *cp;
+ } else if (tstr[1] == 'l') {
+ char *dp = bmake_strdup(nstr);
+ for (newStr = dp; *dp; dp++)
+ *dp = tolower((unsigned char)*dp);
+ cp = tstr + 2;
+ termc = *cp;
+ } else if (tstr[1] == 'W' || tstr[1] == 'w') {
+ parsestate.oneBigWord = (tstr[1] == 'W');
+ newStr = nstr;
+ cp = tstr + 2;
+ termc = *cp;
+ } else {
+ /* Found ":t<unrecognised>:" or
+ * ":t<unrecognised><endc>". */
+ goto bad_modifier;
+ }
+ } else {
+ /*
+ * Found ":t<unrecognised><unrecognised>".
+ */
+ goto bad_modifier;
+ }
+ } else {
+ /*
+ * Found ":t<endc>" or ":t:".
+ */
+ goto bad_modifier;
+ }
+ break;
+ }
+ case 'N':
+ case 'M':
+ {
+ char *pattern;
+ const char *endpat; /* points just after end of pattern */
+ char *cp2;
+ Boolean copy; /* pattern should be, or has been, copied */
+ Boolean needSubst;
+ int nest;
+
+ copy = FALSE;
+ needSubst = FALSE;
+ nest = 1;
+ /*
+ * In the loop below, ignore ':' unless we are at
+ * (or back to) the original brace level.
+ * XXX This will likely not work right if $() and ${}
+ * are intermixed.
+ */
+ for (cp = tstr + 1;
+ *cp != '\0' && !(*cp == ':' && nest == 1);
+ cp++)
+ {
+ if (*cp == '\\' &&
+ (cp[1] == ':' ||
+ cp[1] == endc || cp[1] == startc)) {
+ if (!needSubst) {
+ copy = TRUE;
+ }
+ cp++;
+ continue;
+ }
+ if (*cp == '$') {
+ needSubst = TRUE;
+ }
+ if (*cp == '(' || *cp == '{')
+ ++nest;
+ if (*cp == ')' || *cp == '}') {
+ --nest;
+ if (nest == 0)
+ break;
+ }
+ }
+ termc = *cp;
+ endpat = cp;
+ if (copy) {
+ /*
+ * Need to compress the \:'s out of the pattern, so
+ * allocate enough room to hold the uncompressed
+ * pattern (note that cp started at tstr+1, so
+ * cp - tstr takes the null byte into account) and
+ * compress the pattern into the space.
+ */
+ pattern = bmake_malloc(cp - tstr);
+ for (cp2 = pattern, cp = tstr + 1;
+ cp < endpat;
+ cp++, cp2++)
+ {
+ if ((*cp == '\\') && (cp+1 < endpat) &&
+ (cp[1] == ':' || cp[1] == endc)) {
+ cp++;
+ }
+ *cp2 = *cp;
+ }
+ *cp2 = '\0';
+ endpat = cp2;
+ } else {
+ /*
+ * Either Var_Subst or VarModify will need a
+ * nul-terminated string soon, so construct one now.
+ */
+ pattern = bmake_strndup(tstr+1, endpat - (tstr + 1));
+ }
+ if (needSubst) {
+ /*
+ * pattern contains embedded '$', so use Var_Subst to
+ * expand it.
+ */
+ cp2 = pattern;
+ pattern = Var_Subst(NULL, cp2, ctxt, flags | VARF_WANTRES);
+ free(cp2);
+ }
+ if (DEBUG(VAR))
+ fprintf(debug_file, "Pattern[%s] for [%s] is [%s]\n",
+ v->name, nstr, pattern);
+ if (*tstr == 'M') {
+ newStr = VarModify(ctxt, &parsestate, nstr, VarMatch,
+ pattern);
+ } else {
+ newStr = VarModify(ctxt, &parsestate, nstr, VarNoMatch,
+ pattern);
+ }
+ free(pattern);
+ break;
+ }
+ case 'S':
+ {
+ VarPattern pattern;
+ Var_Parse_State tmpparsestate;
+
+ pattern.flags = 0;
+ tmpparsestate = parsestate;
+ delim = tstr[1];
+ tstr += 2;
+
+ /*
+ * If pattern begins with '^', it is anchored to the
+ * start of the word -- skip over it and flag pattern.
+ */
+ if (*tstr == '^') {
+ pattern.flags |= VAR_MATCH_START;
+ tstr += 1;
+ }
+
+ cp = tstr;
+ if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim,
+ &pattern.flags,
+ &pattern.leftLen,
+ NULL)) == NULL)
+ goto cleanup;
+
+ if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim, NULL,
+ &pattern.rightLen,
+ &pattern)) == NULL)
+ goto cleanup;
+
+ /*
+ * Check for global substitution. If 'g' after the final
+ * delimiter, substitution is global and is marked that
+ * way.
+ */
+ for (;; cp++) {
+ switch (*cp) {
+ case 'g':
+ pattern.flags |= VAR_SUB_GLOBAL;
+ continue;
+ case '1':
+ pattern.flags |= VAR_SUB_ONE;
+ continue;
+ case 'W':
+ tmpparsestate.oneBigWord = TRUE;
+ continue;
+ }
+ break;
+ }
+
+ termc = *cp;
+ newStr = VarModify(ctxt, &tmpparsestate, nstr,
+ VarSubstitute,
+ &pattern);
+
+ /*
+ * Free the two strings.
+ */
+ free(UNCONST(pattern.lhs));
+ free(UNCONST(pattern.rhs));
+ delim = '\0';
+ break;
+ }
+ case '?':
+ {
+ VarPattern pattern;
+ Boolean value;
+ int cond_rc;
+ int lhs_flags, rhs_flags;
+
+ /* find ':', and then substitute accordingly */
+ if (flags & VARF_WANTRES) {
+ cond_rc = Cond_EvalExpression(NULL, v->name, &value, 0, FALSE);
+ if (cond_rc == COND_INVALID) {
+ lhs_flags = rhs_flags = VAR_NOSUBST;
+ } else if (value) {
+ lhs_flags = 0;
+ rhs_flags = VAR_NOSUBST;
+ } else {
+ lhs_flags = VAR_NOSUBST;
+ rhs_flags = 0;
+ }
+ } else {
+ /* we are just consuming and discarding */
+ cond_rc = value = 0;
+ lhs_flags = rhs_flags = VAR_NOSUBST;
+ }
+ pattern.flags = 0;
+
+ cp = ++tstr;
+ delim = ':';
+ if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim, &lhs_flags,
+ &pattern.leftLen,
+ NULL)) == NULL)
+ goto cleanup;
+
+ /* BROPEN or PROPEN */
+ delim = endc;
+ if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, flags,
+ &cp, delim, &rhs_flags,
+ &pattern.rightLen,
+ NULL)) == NULL)
+ goto cleanup;
+
+ termc = *--cp;
+ delim = '\0';
+ if (cond_rc == COND_INVALID) {
+ Error("Bad conditional expression `%s' in %s?%s:%s",
+ v->name, v->name, pattern.lhs, pattern.rhs);
+ goto cleanup;
+ }
+
+ if (value) {
+ newStr = UNCONST(pattern.lhs);
+ free(UNCONST(pattern.rhs));
+ } else {
+ newStr = UNCONST(pattern.rhs);
+ free(UNCONST(pattern.lhs));
+ }
+ if (v->flags & VAR_JUNK) {
+ v->flags |= VAR_KEEP;
+ }
+ break;
+ }
+#ifndef NO_REGEX
+ case 'C':
+ {
+ VarREPattern pattern;
+ char *re;
+ int error;
+ Var_Parse_State tmpparsestate;
+
+ pattern.flags = 0;
+ tmpparsestate = parsestate;
+ delim = tstr[1];
+ tstr += 2;
+
+ cp = tstr;
+
+ if ((re = VarGetPattern(ctxt, &parsestate, flags, &cp, delim,
+ NULL, NULL, NULL)) == NULL)
+ goto cleanup;
+
+ if ((pattern.replace = VarGetPattern(ctxt, &parsestate,
+ flags, &cp, delim, NULL,
+ NULL, NULL)) == NULL){
+ free(re);
+ goto cleanup;
+ }
+
+ for (;; cp++) {
+ switch (*cp) {
+ case 'g':
+ pattern.flags |= VAR_SUB_GLOBAL;
+ continue;
+ case '1':
+ pattern.flags |= VAR_SUB_ONE;
+ continue;
+ case 'W':
+ tmpparsestate.oneBigWord = TRUE;
+ continue;
+ }
+ break;
+ }
+
+ termc = *cp;
+
+ error = regcomp(&pattern.re, re, REG_EXTENDED);
+ free(re);
+ if (error) {
+ *lengthPtr = cp - start + 1;
+ VarREError(error, &pattern.re, "RE substitution error");
+ free(pattern.replace);
+ goto cleanup;
+ }
+
+ pattern.nsub = pattern.re.re_nsub + 1;
+ if (pattern.nsub < 1)
+ pattern.nsub = 1;
+ if (pattern.nsub > 10)
+ pattern.nsub = 10;
+ pattern.matches = bmake_malloc(pattern.nsub *
+ sizeof(regmatch_t));
+ newStr = VarModify(ctxt, &tmpparsestate, nstr,
+ VarRESubstitute,
+ &pattern);
+ regfree(&pattern.re);
+ free(pattern.replace);
+ free(pattern.matches);
+ delim = '\0';
+ break;
+ }
+#endif
+ case 'q':
+ case 'Q':
+ if (tstr[1] == endc || tstr[1] == ':') {
+ newStr = VarQuote(nstr, modifier == 'q');
+ cp = tstr + 1;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+ case 'T':
+ if (tstr[1] == endc || tstr[1] == ':') {
+ newStr = VarModify(ctxt, &parsestate, nstr, VarTail,
+ NULL);
+ cp = tstr + 1;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+ case 'H':
+ if (tstr[1] == endc || tstr[1] == ':') {
+ newStr = VarModify(ctxt, &parsestate, nstr, VarHead,
+ NULL);
+ cp = tstr + 1;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+ case 'E':
+ if (tstr[1] == endc || tstr[1] == ':') {
+ newStr = VarModify(ctxt, &parsestate, nstr, VarSuffix,
+ NULL);
+ cp = tstr + 1;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+ case 'R':
+ if (tstr[1] == endc || tstr[1] == ':') {
+ newStr = VarModify(ctxt, &parsestate, nstr, VarRoot,
+ NULL);
+ cp = tstr + 1;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+ case 'r':
+ cp = tstr + 1; /* make sure it is set */
+ if (STRMOD_MATCHX(tstr, "range", 5)) {
+ int n;
+
+ if (tstr[5] == '=') {
+ n = strtoul(&tstr[6], &ep, 10);
+ cp = ep;
+ } else {
+ n = 0;
+ cp = tstr + 5;
+ }
+ newStr = VarRange(nstr, n);
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+ case 'O':
+ {
+ char otype;
+
+ cp = tstr + 1; /* skip to the rest in any case */
+ if (tstr[1] == endc || tstr[1] == ':') {
+ otype = 's';
+ termc = *cp;
+ } else if ( (tstr[1] == 'x') &&
+ (tstr[2] == endc || tstr[2] == ':') ) {
+ otype = tstr[1];
+ cp = tstr + 2;
+ termc = *cp;
+ } else {
+ goto bad_modifier;
+ }
+ newStr = VarOrder(nstr, otype);
+ break;
+ }
+ case 'u':
+ if (tstr[1] == endc || tstr[1] == ':') {
+ newStr = VarUniq(nstr);
+ cp = tstr + 1;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+#ifdef SUNSHCMD
+ case 's':
+ if (tstr[1] == 'h' && (tstr[2] == endc || tstr[2] == ':')) {
+ const char *emsg;
+ if (flags & VARF_WANTRES) {
+ newStr = Cmd_Exec(nstr, &emsg);
+ if (emsg)
+ Error(emsg, nstr);
+ } else
+ newStr = varNoError;
+ cp = tstr + 2;
+ termc = *cp;
+ break;
+ }
+ goto default_case;
+#endif
+ default:
+ default_case:
+ {
+#ifdef SYSVVARSUB
+ /*
+ * This can either be a bogus modifier or a System-V
+ * substitution command.
+ */
+ VarPattern pattern;
+ Boolean eqFound;
+
+ pattern.flags = 0;
+ eqFound = FALSE;
+ /*
+ * First we make a pass through the string trying
+ * to verify it is a SYSV-make-style translation:
+ * it must be: <string1>=<string2>)
+ */
+ cp = tstr;
+ cnt = 1;
+ while (*cp != '\0' && cnt) {
+ if (*cp == '=') {
+ eqFound = TRUE;
+ /* continue looking for endc */
+ }
+ else if (*cp == endc)
+ cnt--;
+ else if (*cp == startc)
+ cnt++;
+ if (cnt)
+ cp++;
+ }
+ if (*cp == endc && eqFound) {
+
+ /*
+ * Now we break this sucker into the lhs and
+ * rhs. We must null terminate them of course.
+ */
+ delim='=';
+ cp = tstr;
+ if ((pattern.lhs = VarGetPattern(ctxt, &parsestate,
+ flags, &cp, delim, &pattern.flags,
+ &pattern.leftLen, NULL)) == NULL)
+ goto cleanup;
+ delim = endc;
+ if ((pattern.rhs = VarGetPattern(ctxt, &parsestate,
+ flags, &cp, delim, NULL, &pattern.rightLen,
+ &pattern)) == NULL)
+ goto cleanup;
+
+ /*
+ * SYSV modifications happen through the whole
+ * string. Note the pattern is anchored at the end.
+ */
+ termc = *--cp;
+ delim = '\0';
+ if (pattern.leftLen == 0 && *nstr == '\0') {
+ newStr = nstr; /* special case */
+ } else {
+ newStr = VarModify(ctxt, &parsestate, nstr,
+ VarSYSVMatch,
+ &pattern);
+ }
+ free(UNCONST(pattern.lhs));
+ free(UNCONST(pattern.rhs));
+ } else
+#endif
+ {
+ Error("Unknown modifier '%c'", *tstr);
+ for (cp = tstr+1;
+ *cp != ':' && *cp != endc && *cp != '\0';
+ cp++)
+ continue;
+ termc = *cp;
+ newStr = var_Error;
+ }
+ }
+ }
+ if (DEBUG(VAR)) {
+ fprintf(debug_file, "Result[%s] of :%c is \"%s\"\n",
+ v->name, modifier, newStr);
+ }
+
+ if (newStr != nstr) {
+ if (*freePtr) {
+ free(nstr);
+ *freePtr = NULL;
+ }
+ nstr = newStr;
+ if (nstr != var_Error && nstr != varNoError) {
+ *freePtr = nstr;
+ }
+ }
+ if (termc == '\0' && endc != '\0') {
+ Error("Unclosed variable specification (expecting '%c') for \"%s\" (value \"%s\") modifier %c", endc, v->name, nstr, modifier);
+ } else if (termc == ':') {
+ cp++;
+ }
+ tstr = cp;
+ }
+ out:
+ *lengthPtr = tstr - start;
+ return (nstr);
+
+ bad_modifier:
+ /* "{(" */
+ Error("Bad modifier `:%.*s' for %s", (int)strcspn(tstr, ":)}"), tstr,
+ v->name);
+
+ cleanup:
+ *lengthPtr = cp - start;
+ if (delim != '\0')
+ Error("Unclosed substitution for %s (%c missing)",
+ v->name, delim);
+ free(*freePtr);
+ *freePtr = NULL;
+ return (var_Error);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Parse --
+ * Given the start of a variable invocation, extract the variable
+ * name and find its value, then modify it according to the
+ * specification.
+ *
+ * Input:
+ * str The string to parse
+ * ctxt The context for the variable
+ * flags VARF_UNDEFERR if undefineds are an error
+ * VARF_WANTRES if we actually want the result
+ * VARF_ASSIGN if we are in a := assignment
+ * lengthPtr OUT: The length of the specification
+ * freePtr OUT: Non-NULL if caller should free *freePtr
+ *
+ * Results:
+ * The (possibly-modified) value of the variable or var_Error if the
+ * specification is invalid. The length of the specification is
+ * placed in *lengthPtr (for invalid specifications, this is just
+ * 2...?).
+ * If *freePtr is non-NULL then it's a pointer that the caller
+ * should pass to free() to free memory used by the result.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+/* coverity[+alloc : arg-*4] */
+char *
+Var_Parse(const char *str, GNode *ctxt, int flags,
+ int *lengthPtr, void **freePtr)
+{
+ const char *tstr; /* Pointer into str */
+ Var *v; /* Variable in invocation */
+ Boolean haveModifier;/* TRUE if have modifiers for the variable */
+ char endc; /* Ending character when variable in parens
+ * or braces */
+ char startc; /* Starting character when variable in parens
+ * or braces */
+ int vlen; /* Length of variable name */
+ const char *start; /* Points to original start of str */
+ char *nstr; /* New string, used during expansion */
+ Boolean dynamic; /* TRUE if the variable is local and we're
+ * expanding it in a non-local context. This
+ * is done to support dynamic sources. The
+ * result is just the invocation, unaltered */
+ const char *extramodifiers; /* extra modifiers to apply first */
+ char name[2];
+
+ *freePtr = NULL;
+ extramodifiers = NULL;
+ dynamic = FALSE;
+ start = str;
+
+ startc = str[1];
+ if (startc != PROPEN && startc != BROPEN) {
+ /*
+ * If it's not bounded by braces of some sort, life is much simpler.
+ * We just need to check for the first character and return the
+ * value if it exists.
+ */
+
+ /* Error out some really stupid names */
+ if (startc == '\0' || strchr(")}:$", startc)) {
+ *lengthPtr = 1;
+ return var_Error;
+ }
+ name[0] = startc;
+ name[1] = '\0';
+
+ v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
+ if (v == NULL) {
+ *lengthPtr = 2;
+
+ if ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)) {
+ /*
+ * If substituting a local variable in a non-local context,
+ * assume it's for dynamic source stuff. We have to handle
+ * this specially and return the longhand for the variable
+ * with the dollar sign escaped so it makes it back to the
+ * caller. Only four of the local variables are treated
+ * specially as they are the only four that will be set
+ * when dynamic sources are expanded.
+ */
+ switch (str[1]) {
+ case '@':
+ return UNCONST("$(.TARGET)");
+ case '%':
+ return UNCONST("$(.MEMBER)");
+ case '*':
+ return UNCONST("$(.PREFIX)");
+ case '!':
+ return UNCONST("$(.ARCHIVE)");
+ }
+ }
+ /*
+ * Error
+ */
+ return (flags & VARF_UNDEFERR) ? var_Error : varNoError;
+ } else {
+ haveModifier = FALSE;
+ tstr = &str[1];
+ endc = str[1];
+ }
+ } else {
+ Buffer buf; /* Holds the variable name */
+ int depth = 1;
+
+ endc = startc == PROPEN ? PRCLOSE : BRCLOSE;
+ Buf_Init(&buf, 0);
+
+ /*
+ * Skip to the end character or a colon, whichever comes first.
+ */
+ for (tstr = str + 2; *tstr != '\0'; tstr++)
+ {
+ /*
+ * Track depth so we can spot parse errors.
+ */
+ if (*tstr == startc) {
+ depth++;
+ }
+ if (*tstr == endc) {
+ if (--depth == 0)
+ break;
+ }
+ if (depth == 1 && *tstr == ':') {
+ break;
+ }
+ /*
+ * A variable inside a variable, expand
+ */
+ if (*tstr == '$') {
+ int rlen;
+ void *freeIt;
+ char *rval = Var_Parse(tstr, ctxt, flags, &rlen, &freeIt);
+ if (rval != NULL) {
+ Buf_AddBytes(&buf, strlen(rval), rval);
+ }
+ free(freeIt);
+ tstr += rlen - 1;
+ }
+ else
+ Buf_AddByte(&buf, *tstr);
+ }
+ if (*tstr == ':') {
+ haveModifier = TRUE;
+ } else if (*tstr == endc) {
+ haveModifier = FALSE;
+ } else {
+ /*
+ * If we never did find the end character, return NULL
+ * right now, setting the length to be the distance to
+ * the end of the string, since that's what make does.
+ */
+ *lengthPtr = tstr - str;
+ Buf_Destroy(&buf, TRUE);
+ return (var_Error);
+ }
+ str = Buf_GetAll(&buf, &vlen);
+
+ /*
+ * At this point, str points into newly allocated memory from
+ * buf, containing only the name of the variable.
+ *
+ * start and tstr point into the const string that was pointed
+ * to by the original value of the str parameter. start points
+ * to the '$' at the beginning of the string, while tstr points
+ * to the char just after the end of the variable name -- this
+ * will be '\0', ':', PRCLOSE, or BRCLOSE.
+ */
+
+ v = VarFind(str, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
+ /*
+ * Check also for bogus D and F forms of local variables since we're
+ * in a local context and the name is the right length.
+ */
+ if ((v == NULL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) &&
+ (vlen == 2) && (str[1] == 'F' || str[1] == 'D') &&
+ strchr("@%?*!<>", str[0]) != NULL) {
+ /*
+ * Well, it's local -- go look for it.
+ */
+ name[0] = *str;
+ name[1] = '\0';
+ v = VarFind(name, ctxt, 0);
+
+ if (v != NULL) {
+ if (str[1] == 'D') {
+ extramodifiers = "H:";
+ }
+ else { /* F */
+ extramodifiers = "T:";
+ }
+ }
+ }
+
+ if (v == NULL) {
+ if (((vlen == 1) ||
+ (((vlen == 2) && (str[1] == 'F' || str[1] == 'D')))) &&
+ ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)))
+ {
+ /*
+ * If substituting a local variable in a non-local context,
+ * assume it's for dynamic source stuff. We have to handle
+ * this specially and return the longhand for the variable
+ * with the dollar sign escaped so it makes it back to the
+ * caller. Only four of the local variables are treated
+ * specially as they are the only four that will be set
+ * when dynamic sources are expanded.
+ */
+ switch (*str) {
+ case '@':
+ case '%':
+ case '*':
+ case '!':
+ dynamic = TRUE;
+ break;
+ }
+ } else if ((vlen > 2) && (*str == '.') &&
+ isupper((unsigned char) str[1]) &&
+ ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)))
+ {
+ int len;
+
+ len = vlen - 1;
+ if ((strncmp(str, ".TARGET", len) == 0) ||
+ (strncmp(str, ".ARCHIVE", len) == 0) ||
+ (strncmp(str, ".PREFIX", len) == 0) ||
+ (strncmp(str, ".MEMBER", len) == 0))
+ {
+ dynamic = TRUE;
+ }
+ }
+
+ if (!haveModifier) {
+ /*
+ * No modifiers -- have specification length so we can return
+ * now.
+ */
+ *lengthPtr = tstr - start + 1;
+ if (dynamic) {
+ char *pstr = bmake_strndup(start, *lengthPtr);
+ *freePtr = pstr;
+ Buf_Destroy(&buf, TRUE);
+ return(pstr);
+ } else {
+ Buf_Destroy(&buf, TRUE);
+ return (flags & VARF_UNDEFERR) ? var_Error : varNoError;
+ }
+ } else {
+ /*
+ * Still need to get to the end of the variable specification,
+ * so kludge up a Var structure for the modifications
+ */
+ v = bmake_malloc(sizeof(Var));
+ v->name = UNCONST(str);
+ Buf_Init(&v->val, 1);
+ v->flags = VAR_JUNK;
+ Buf_Destroy(&buf, FALSE);
+ }
+ } else
+ Buf_Destroy(&buf, TRUE);
+ }
+
+ if (v->flags & VAR_IN_USE) {
+ Fatal("Variable %s is recursive.", v->name);
+ /*NOTREACHED*/
+ } else {
+ v->flags |= VAR_IN_USE;
+ }
+ /*
+ * Before doing any modification, we have to make sure the value
+ * has been fully expanded. If it looks like recursion might be
+ * necessary (there's a dollar sign somewhere in the variable's value)
+ * we just call Var_Subst to do any other substitutions that are
+ * necessary. Note that the value returned by Var_Subst will have
+ * been dynamically-allocated, so it will need freeing when we
+ * return.
+ */
+ nstr = Buf_GetAll(&v->val, NULL);
+ if (strchr(nstr, '$') != NULL) {
+ nstr = Var_Subst(NULL, nstr, ctxt, flags);
+ *freePtr = nstr;
+ }
+
+ v->flags &= ~VAR_IN_USE;
+
+ if ((nstr != NULL) && (haveModifier || extramodifiers != NULL)) {
+ void *extraFree;
+ int used;
+
+ extraFree = NULL;
+ if (extramodifiers != NULL) {
+ nstr = ApplyModifiers(nstr, extramodifiers, '(', ')',
+ v, ctxt, flags, &used, &extraFree);
+ }
+
+ if (haveModifier) {
+ /* Skip initial colon. */
+ tstr++;
+
+ nstr = ApplyModifiers(nstr, tstr, startc, endc,
+ v, ctxt, flags, &used, freePtr);
+ tstr += used;
+ free(extraFree);
+ } else {
+ *freePtr = extraFree;
+ }
+ }
+ if (*tstr) {
+ *lengthPtr = tstr - start + 1;
+ } else {
+ *lengthPtr = tstr - start;
+ }
+
+ if (v->flags & VAR_FROM_ENV) {
+ Boolean destroy = FALSE;
+
+ if (nstr != Buf_GetAll(&v->val, NULL)) {
+ destroy = TRUE;
+ } else {
+ /*
+ * Returning the value unmodified, so tell the caller to free
+ * the thing.
+ */
+ *freePtr = nstr;
+ }
+ VarFreeEnv(v, destroy);
+ } else if (v->flags & VAR_JUNK) {
+ /*
+ * Perform any free'ing needed and set *freePtr to NULL so the caller
+ * doesn't try to free a static pointer.
+ * If VAR_KEEP is also set then we want to keep str as is.
+ */
+ if (!(v->flags & VAR_KEEP)) {
+ if (*freePtr) {
+ free(nstr);
+ *freePtr = NULL;
+ }
+ if (dynamic) {
+ nstr = bmake_strndup(start, *lengthPtr);
+ *freePtr = nstr;
+ } else {
+ nstr = (flags & VARF_UNDEFERR) ? var_Error : varNoError;
+ }
+ }
+ if (nstr != Buf_GetAll(&v->val, NULL))
+ Buf_Destroy(&v->val, TRUE);
+ free(v->name);
+ free(v);
+ }
+ return (nstr);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Subst --
+ * Substitute for all variables in the given string in the given context
+ * If flags & VARF_UNDEFERR, Parse_Error will be called when an undefined
+ * variable is encountered.
+ *
+ * Input:
+ * var Named variable || NULL for all
+ * str the string which to substitute
+ * ctxt the context wherein to find variables
+ * flags VARF_UNDEFERR if undefineds are an error
+ * VARF_WANTRES if we actually want the result
+ * VARF_ASSIGN if we are in a := assignment
+ *
+ * Results:
+ * The resulting string.
+ *
+ * Side Effects:
+ * None. The old string must be freed by the caller
+ *-----------------------------------------------------------------------
+ */
+char *
+Var_Subst(const char *var, const char *str, GNode *ctxt, int flags)
+{
+ Buffer buf; /* Buffer for forming things */
+ char *val; /* Value to substitute for a variable */
+ int length; /* Length of the variable invocation */
+ Boolean trailingBslash; /* variable ends in \ */
+ void *freeIt = NULL; /* Set if it should be freed */
+ static Boolean errorReported; /* Set true if an error has already
+ * been reported to prevent a plethora
+ * of messages when recursing */
+
+ Buf_Init(&buf, 0);
+ errorReported = FALSE;
+ trailingBslash = FALSE;
+
+ while (*str) {
+ if (*str == '\n' && trailingBslash)
+ Buf_AddByte(&buf, ' ');
+ if (var == NULL && (*str == '$') && (str[1] == '$')) {
+ /*
+ * A dollar sign may be escaped either with another dollar sign.
+ * In such a case, we skip over the escape character and store the
+ * dollar sign into the buffer directly.
+ */
+ if (save_dollars && (flags & VARF_ASSIGN))
+ Buf_AddByte(&buf, *str);
+ str++;
+ Buf_AddByte(&buf, *str);
+ str++;
+ } else if (*str != '$') {
+ /*
+ * Skip as many characters as possible -- either to the end of
+ * the string or to the next dollar sign (variable invocation).
+ */
+ const char *cp;
+
+ for (cp = str++; *str != '$' && *str != '\0'; str++)
+ continue;
+ Buf_AddBytes(&buf, str - cp, cp);
+ } else {
+ if (var != NULL) {
+ int expand;
+ for (;;) {
+ if (str[1] == '\0') {
+ /* A trailing $ is kind of a special case */
+ Buf_AddByte(&buf, str[0]);
+ str++;
+ expand = FALSE;
+ } else if (str[1] != PROPEN && str[1] != BROPEN) {
+ if (str[1] != *var || strlen(var) > 1) {
+ Buf_AddBytes(&buf, 2, str);
+ str += 2;
+ expand = FALSE;
+ }
+ else
+ expand = TRUE;
+ break;
+ }
+ else {
+ const char *p;
+
+ /*
+ * Scan up to the end of the variable name.
+ */
+ for (p = &str[2]; *p &&
+ *p != ':' && *p != PRCLOSE && *p != BRCLOSE; p++)
+ if (*p == '$')
+ break;
+ /*
+ * A variable inside the variable. We cannot expand
+ * the external variable yet, so we try again with
+ * the nested one
+ */
+ if (*p == '$') {
+ Buf_AddBytes(&buf, p - str, str);
+ str = p;
+ continue;
+ }
+
+ if (strncmp(var, str + 2, p - str - 2) != 0 ||
+ var[p - str - 2] != '\0') {
+ /*
+ * Not the variable we want to expand, scan
+ * until the next variable
+ */
+ for (;*p != '$' && *p != '\0'; p++)
+ continue;
+ Buf_AddBytes(&buf, p - str, str);
+ str = p;
+ expand = FALSE;
+ }
+ else
+ expand = TRUE;
+ break;
+ }
+ }
+ if (!expand)
+ continue;
+ }
+
+ val = Var_Parse(str, ctxt, flags, &length, &freeIt);
+
+ /*
+ * When we come down here, val should either point to the
+ * value of this variable, suitably modified, or be NULL.
+ * Length should be the total length of the potential
+ * variable invocation (from $ to end character...)
+ */
+ if (val == var_Error || val == varNoError) {
+ /*
+ * If performing old-time variable substitution, skip over
+ * the variable and continue with the substitution. Otherwise,
+ * store the dollar sign and advance str so we continue with
+ * the string...
+ */
+ if (oldVars) {
+ str += length;
+ } else if ((flags & VARF_UNDEFERR) || val == var_Error) {
+ /*
+ * If variable is undefined, complain and skip the
+ * variable. The complaint will stop us from doing anything
+ * when the file is parsed.
+ */
+ if (!errorReported) {
+ Parse_Error(PARSE_FATAL,
+ "Undefined variable \"%.*s\"",length,str);
+ }
+ str += length;
+ errorReported = TRUE;
+ } else {
+ Buf_AddByte(&buf, *str);
+ str += 1;
+ }
+ } else {
+ /*
+ * We've now got a variable structure to store in. But first,
+ * advance the string pointer.
+ */
+ str += length;
+
+ /*
+ * Copy all the characters from the variable value straight
+ * into the new string.
+ */
+ length = strlen(val);
+ Buf_AddBytes(&buf, length, val);
+ trailingBslash = length > 0 && val[length - 1] == '\\';
+ }
+ free(freeIt);
+ freeIt = NULL;
+ }
+ }
+
+ return Buf_DestroyCompact(&buf);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_GetTail --
+ * Return the tail from each of a list of words. Used to set the
+ * System V local variables.
+ *
+ * Input:
+ * file Filename to modify
+ *
+ * Results:
+ * The resulting string.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+#if 0
+char *
+Var_GetTail(char *file)
+{
+ return(VarModify(file, VarTail, NULL));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_GetHead --
+ * Find the leading components of a (list of) filename(s).
+ * XXX: VarHead does not replace foo by ., as (sun) System V make
+ * does.
+ *
+ * Input:
+ * file Filename to manipulate
+ *
+ * Results:
+ * The leading components.
+ *
+ * Side Effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------
+ */
+char *
+Var_GetHead(char *file)
+{
+ return(VarModify(file, VarHead, NULL));
+}
+#endif
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Init --
+ * Initialize the module
+ *
+ * Results:
+ * None
+ *
+ * Side Effects:
+ * The VAR_CMD and VAR_GLOBAL contexts are created
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Init(void)
+{
+ VAR_INTERNAL = Targ_NewGN("Internal");
+ VAR_GLOBAL = Targ_NewGN("Global");
+ VAR_CMD = Targ_NewGN("Command");
+
+}
+
+
+void
+Var_End(void)
+{
+}
+
+
+/****************** PRINT DEBUGGING INFO *****************/
+static void
+VarPrintVar(void *vp)
+{
+ Var *v = (Var *)vp;
+ fprintf(debug_file, "%-16s = %s\n", v->name, Buf_GetAll(&v->val, NULL));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Dump --
+ * print all variables in a context
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Dump(GNode *ctxt)
+{
+ Hash_Search search;
+ Hash_Entry *h;
+
+ for (h = Hash_EnumFirst(&ctxt->context, &search);
+ h != NULL;
+ h = Hash_EnumNext(&search)) {
+ VarPrintVar(Hash_GetValue(h));
+ }
+}
diff --git a/usr.bin/man/man.1 b/usr.bin/man/man.1
new file mode 100644
index 0000000..49b3bf3
--- /dev/null
+++ b/usr.bin/man/man.1
@@ -0,0 +1,269 @@
+.\" $NetBSD: man.1,v 1.29 2016/06/16 15:10:58 abhinav Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)man.1 8.2 (Berkeley) 1/2/94
+.\"
+.Dd June 16, 2016
+.Dt MAN 1
+.Os
+.Sh NAME
+.Nm man
+.Nd display the on-line manual pages
+.Pq aka Dq Em man pages
+.Sh SYNOPSIS
+.Nm
+.Oo Fl acw Ns \&| Ns Fl h Oc
+.Op Fl C Ar file
+.Op Fl M Ar path
+.Op Fl m Ar path
+.Op Fl S Ar srch
+.Oo
+.Op Fl s
+.Ar section
+.Oc
+.Ar name Ar ...
+.Nm
+.Op Fl C Ar file
+.Fl f
+.Ar command Ar ...
+.Nm
+.Op Fl C Ar file
+.Fl k
+.Ar keyword Ar ...
+.Nm
+.Fl p
+.Sh DESCRIPTION
+The
+.Nm
+utility displays the manual pages named on the command line.
+Its options are as follows:
+.Bl -tag -width indent
+.It Fl a
+Display all of the man pages for a specified
+.Ar section
+and
+.Ar name
+combination.
+(Normally, only the first man page found is displayed.)
+.It Fl C
+Use the specified
+.Ar file
+instead of the default configuration file.
+This permits users to configure their own man environment.
+See
+.Xr man.conf 5
+for a description of the contents of this file.
+.It Fl c
+Copy the man page to the standard output instead of using
+.Xr more 1
+to paginate it.
+This is done by default if the standard output is not a terminal device.
+.It Fl f
+Synonym for
+.Xr whatis 1 .
+It searches man pages for
+.Ar command
+in their names and displays header lines from all matching pages.
+.It Fl h
+Display only the
+.Dq Tn SYNOPSIS
+lines of the requested man pages.
+For commands, this is typically the command line usage information.
+For library functions, this usually contains the required include
+files and function prototypes.
+.It Fl k
+Search man pages for
+.Ar keyword Ns Pq s ,
+in the same manner as
+.Xr apropos 1 .
+.It Fl M
+Override the list of standard directories which
+.Nm
+searches for man pages.
+The supplied
+.Ar path
+must be a colon
+.Pq Dq \&:
+separated list of directories.
+This search path may also be set using the environment variable
+.Ev MANPATH .
+The subdirectories to be searched, and their search order,
+is specified by the
+.Dq _subdir
+line in the
+.Nm
+configuration file.
+.It Fl m
+Augment the list of standard directories which
+.Nm
+searches for man pages.
+The supplied
+.Ar path
+must be a colon
+.Pq Dq \&:
+separated list of directories.
+These directories will be searched before the standard directories or
+the directories specified using the
+.Fl M
+option or the
+.Ev MANPATH
+environment variable.
+The subdirectories to be searched, and their search order,
+is specified by the
+.Dq _subdir
+line in the
+.Nm
+configuration file.
+.It Fl p
+Print the search path for the manual pages.
+.It Fl s
+Restrict the directories that
+.Nm
+will search to the specified section.
+The
+.Nm
+configuration file (see
+.Xr man.conf 5 )
+specifies the possible
+.Ar section
+values that are currently available.
+.It Fl S
+Display only man pages that have the specified string in the directory
+part of their filenames.
+This allows the man page search process criteria to be
+narrowed without having to change the MANPATH or
+.Dq _default
+variables.
+.It Fl w
+List the pathnames of the man pages which
+.Nm
+would display for the specified
+.Ar section
+and
+.Ar name
+combination.
+.El
+.Pp
+If the
+.Ql Fl s
+option is not specified,
+there is more than one argument,
+the
+.Ql Fl k
+option is not used, and the first argument is a valid section, then that
+argument will be used as if specified by the
+.Ql Fl s
+option.
+.Pp
+If
+.Ar name
+is given with a full path (beginning with
+.Ql Pa \&/ )
+or a relative path that begins with
+.Ql Pa .\&/
+or
+.Ql Pa .\&./ ,
+then
+.Nm
+interprets it as a file specification, so that you can do
+.Nm
+.Cm ./foo.5
+or even
+.Nm
+.Cm /cd/foo/bar.1.gz .
+If
+.Ar name
+contains
+.Ql Pa /
+but does not match one of the above cases, then the
+search path is used; this allows you to request
+machine-specific man pages, such as
+.Nm Cm vax/boot .
+.Sh ENVIRONMENT
+.Bl -tag -width MANPATHX
+.It Ev MACHINE
+As some man pages are intended only for specific architectures,
+.Nm
+searches any subdirectories,
+with the same name as the current architecture,
+in every directory which it searches.
+Machine specific areas are checked before general areas.
+The current machine type may be overridden by setting the environment
+variable
+.Ev MACHINE
+to the name of a specific architecture.
+Machine-specific man pages may also be requested by
+prepending the relevant subdirectory name to the page name,
+separated by
+.Ql Pa \&/ .
+.It Ev MANPATH
+The standard search path used by
+.Nm
+may be overridden by specifying a path in the
+.Ev MANPATH
+environment variable.
+The format of the path is a colon
+.Pq Dq \&:
+separated list of directories.
+The subdirectories to be searched as well as their search order
+is specified by the
+.Dq _subdir
+line in the
+.Nm
+configuration file.
+.It Ev PAGER
+The pagination command used for writing the output.
+If the
+.Ev PAGER
+environment variable is null or not set, the standard pagination program
+.Xr more 1
+will be used.
+.El
+.Sh FILES
+.Bl -hang -width /etc/man.conf -compact
+.It Pa /etc/man.conf
+default man configuration file.
+.El
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr whatis 1 ,
+.Xr whereis 1 ,
+.Xr man.conf 5 ,
+.Xr mdoc 7 ,
+.Xr mdoc.samples 7
+.Sh STANDARDS
+.Nm
+conforms to
+.St -xcu5 .
+.\"and is expected to conform to
+.\".St -p1003.2-?? .
+.Sh BUGS
+The on-line man pages are, by necessity, forgiving toward stupid
+display devices, causing a few man pages to be not as nicely formatted
+as their typeset counterparts.
diff --git a/usr.bin/man/man.c b/usr.bin/man/man.c
new file mode 100644
index 0000000..cba36bc
--- /dev/null
+++ b/usr.bin/man/man.c
@@ -0,0 +1,1077 @@
+/* $NetBSD: man.c,v 1.67 2018/06/15 20:16:35 mrg Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1987, 1993, 1994, 1995\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)man.c 8.17 (Berkeley) 1/31/95";
+#else
+__RCSID("$NetBSD: man.c,v 1.67 2018/06/15 20:16:35 mrg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <glob.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+#include <locale.h>
+
+#include "manconf.h"
+#include "pathnames.h"
+
+#ifndef MAN_DEBUG
+#define MAN_DEBUG 0 /* debug path output */
+#endif
+
+/*
+ * manstate: structure collecting the current global state so we can
+ * easily identify it and pass it to helper functions in one arg.
+ */
+struct manstate {
+ /* command line flags */
+ int all; /* -a: show all matches rather than first */
+ int cat; /* -c: do not use a pager */
+ char *conffile; /* -C: use alternate config file */
+ int how; /* -h: show SYNOPSIS only */
+ char *manpath; /* -M: alternate MANPATH */
+ char *addpath; /* -m: add these dirs to front of manpath */
+ char *pathsearch; /* -S: path of man must contain this string */
+ char *sectionname; /* -s: limit search to a given man section */
+ int where; /* -w: just show paths of all matching files */
+ int getpath; /* -p: print the path of directories containing man pages */
+
+ /* important tags from the config file */
+ TAG *defaultpath; /* _default: default MANPATH */
+ TAG *subdirs; /* _subdir: default subdir search list */
+ TAG *suffixlist; /* _suffix: for files that can be cat()'d */
+ TAG *buildlist; /* _build: for files that must be built */
+
+ /* tags for internal use */
+ TAG *intmp; /* _intmp: tmp files we must cleanup */
+ TAG *missinglist; /* _missing: pages we couldn't find */
+ TAG *mymanpath; /* _new_path: final version of MANPATH */
+ TAG *section; /* <sec>: tag for m.sectionname */
+
+ /* other misc stuff */
+ const char *pager; /* pager to use */
+ size_t pagerlen; /* length of the above */
+ const char *machine; /* machine */
+ const char *machclass; /* machine class */
+};
+
+/*
+ * prototypes
+ */
+static void build_page(const char *, char **, struct manstate *);
+static void cat(const char *);
+static const char *check_pager(const char *);
+static int cleanup(void);
+static void how(const char *);
+static void jump(char **, const char *, const char *) __dead;
+static int manual(char *, struct manstate *, glob_t *);
+static void onsig(int) __dead;
+static void usage(void) __dead;
+static void addpath(struct manstate *, const char *, size_t, const char *);
+static const char *getclass(const char *);
+static void printmanpath(struct manstate *);
+
+/*
+ * main function
+ */
+int
+main(int argc, char **argv)
+{
+ static struct manstate m;
+ struct utsname utsname;
+ int ch, abs_section, found;
+ ENTRY *esubd, *epath;
+ char *p, **ap, *cmd;
+ size_t len;
+ glob_t pg;
+
+ setprogname(argv[0]);
+ setlocale(LC_ALL, "");
+ /*
+ * parse command line...
+ */
+ while ((ch = getopt(argc, argv, "-aC:cfhkM:m:P:ps:S:w")) != -1)
+ switch (ch) {
+ case 'a':
+ m.all = 1;
+ break;
+ case 'C':
+ m.conffile = optarg;
+ break;
+ case 'c':
+ case '-': /* XXX: '-' is a deprecated version of '-c' */
+ m.cat = 1;
+ break;
+ case 'h':
+ m.how = 1;
+ break;
+ case 'm':
+ m.addpath = optarg;
+ break;
+ case 'M':
+ case 'P': /* -P for backward compatibility */
+ if ((m.manpath = strdup(optarg)) == NULL)
+ err(EXIT_FAILURE, "malloc failed");
+ break;
+ case 'p':
+ m.getpath = 1;
+ break;
+ /*
+ * The -f and -k options are backward compatible,
+ * undocumented ways of calling whatis(1) and apropos(1).
+ */
+ case 'f':
+ jump(argv, "-f", "whatis");
+ /* NOTREACHED */
+ case 'k':
+ jump(argv, "-k", "apropos");
+ /* NOTREACHED */
+ case 's':
+ if (m.sectionname != NULL)
+ usage();
+ m.sectionname = optarg;
+ break;
+ case 'S':
+ m.pathsearch = optarg;
+ break;
+ case 'w':
+ m.all = m.where = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!m.getpath && !argc)
+ usage();
+
+ /*
+ * read the configuration file and collect any other information
+ * we will need (machine type, pager, section [if specified
+ * without '-s'], and MANPATH through the environment).
+ */
+ config(m.conffile); /* exits on error ... */
+
+ if ((m.machine = getenv("MACHINE")) == NULL) {
+ if (uname(&utsname) == -1)
+ err(EXIT_FAILURE, "uname");
+ m.machine = utsname.machine;
+ }
+
+ m.machclass = getclass(m.machine);
+
+ if (!m.cat && !m.how && !m.where) { /* if we need a pager ... */
+ if (!isatty(STDOUT_FILENO)) {
+ m.cat = 1;
+ } else {
+ if ((m.pager = getenv("PAGER")) != NULL &&
+ m.pager[0] != '\0')
+ m.pager = check_pager(m.pager);
+ else
+ m.pager = _PATH_PAGER;
+ m.pagerlen = strlen(m.pager);
+ }
+ }
+
+ /* do we need to set m.section to a non-null value? */
+ if (m.sectionname) {
+
+ m.section = gettag(m.sectionname, 0); /* -s must be a section */
+ if (m.section == NULL)
+ errx(EXIT_FAILURE, "unknown section: %s", m.sectionname);
+
+ } else if (argc > 1) {
+
+ m.section = gettag(*argv, 0); /* might be a section? */
+ if (m.section) {
+ argv++;
+ argc--;
+ }
+
+ }
+
+ if (m.manpath == NULL)
+ m.manpath = getenv("MANPATH"); /* note: -M overrides getenv */
+
+
+ /*
+ * get default values from config file, plus create the tags we
+ * use for keeping internal state. make sure all our mallocs
+ * go through.
+ */
+ /* from cfg file */
+ m.defaultpath = gettag("_default", 1);
+ m.subdirs = gettag("_subdir", 1);
+ m.suffixlist = gettag("_suffix", 1);
+ m.buildlist = gettag("_build", 1);
+ /* internal use */
+ m.mymanpath = gettag("_new_path", 1);
+ m.missinglist = gettag("_missing", 1);
+ m.intmp = gettag("_intmp", 1);
+ if (!m.defaultpath || !m.subdirs || !m.suffixlist || !m.buildlist ||
+ !m.mymanpath || !m.missinglist || !m.intmp)
+ errx(EXIT_FAILURE, "malloc failed");
+
+ /*
+ * are we using a section whose elements are all absolute paths?
+ * (we only need to look at the first entry on the section list,
+ * as config() will ensure that any additional entries will match
+ * the first one.)
+ */
+ abs_section = (m.section != NULL &&
+ !TAILQ_EMPTY(&m.section->entrylist) &&
+ *(TAILQ_FIRST(&m.section->entrylist)->s) == '/');
+
+ /*
+ * now that we have all the data we need, we must determine the
+ * manpath we are going to use to find the requested entries using
+ * the following steps...
+ *
+ * [1] if the user specified a section and that section's elements
+ * from the config file are all absolute paths, then we override
+ * defaultpath and -M/MANPATH with the section's absolute paths.
+ */
+ if (abs_section) {
+ m.manpath = NULL; /* ignore -M/MANPATH */
+ m.defaultpath = m.section; /* overwrite _default path */
+ m.section = NULL; /* promoted to defaultpath */
+ }
+
+ /*
+ * [2] section can now only be non-null if the user asked for
+ * a section and that section's elements did not have
+ * absolute paths. in this case we use the section's
+ * elements to override _subdir from the config file.
+ *
+ * after this step, we are done processing "m.section"...
+ */
+ if (m.section)
+ m.subdirs = m.section;
+
+ /*
+ * [3] we need to setup the path we want to use (m.mymanpath).
+ * if the user gave us a path (m.manpath) use it, otherwise
+ * go with the default. in either case we need to append
+ * the subdir and machine spec to each element of the path.
+ *
+ * for absolute section paths that come from the config file,
+ * we only append the subdir spec if the path ends in
+ * a '/' --- elements that do not end in '/' are assumed to
+ * not have subdirectories. this is mainly for backward compat,
+ * but it allows non-subdir configs like:
+ * sect3 /usr/share/man/{old/,}cat3
+ * doc /usr/{pkg,share}/doc/{sendmail/op,sendmail/intro}
+ *
+ * note that we try and be careful to not put double slashes
+ * in the path (e.g. we want /usr/share/man/man1, not
+ * /usr/share/man//man1) because "more" will put the filename
+ * we generate in its prompt and the double slashes look ugly.
+ */
+ if (m.manpath) {
+
+ /* note: strtok is going to destroy m.manpath */
+ for (p = strtok(m.manpath, ":") ; p ; p = strtok(NULL, ":")) {
+ len = strlen(p);
+ if (len < 1)
+ continue;
+ TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q)
+ addpath(&m, p, len, esubd->s);
+ }
+
+ } else {
+
+ TAILQ_FOREACH(epath, &m.defaultpath->entrylist, q) {
+ /* handle trailing "/" magic here ... */
+ if (abs_section && epath->s[epath->len - 1] != '/') {
+ addpath(&m, "", 1, epath->s);
+ continue;
+ }
+
+ TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q)
+ addpath(&m, epath->s, epath->len, esubd->s);
+ }
+
+ }
+
+ /*
+ * [4] finally, prepend the "-m" m.addpath to mymanpath if it
+ * was specified. subdirs and machine are always applied to
+ * m.addpath.
+ */
+ if (m.addpath) {
+
+ /* note: strtok is going to destroy m.addpath */
+ for (p = strtok(m.addpath, ":") ; p ; p = strtok(NULL, ":")) {
+ len = strlen(p);
+ if (len < 1)
+ continue;
+ TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q)
+ addpath(&m, p, len, esubd->s);
+ }
+
+ }
+
+ if (m.getpath)
+ printmanpath(&m);
+
+ /*
+ * now m.mymanpath is complete!
+ */
+#if MAN_DEBUG
+ printf("mymanpath:\n");
+ TAILQ_FOREACH(epath, &m.mymanpath->entrylist, q) {
+ printf("\t%s\n", epath->s);
+ }
+#endif
+
+ /*
+ * start searching for matching files and format them if necessary.
+ * setup an interrupt handler so that we can ensure that temporary
+ * files go away.
+ */
+ (void)signal(SIGINT, onsig);
+ (void)signal(SIGHUP, onsig);
+ (void)signal(SIGPIPE, onsig);
+
+ memset(&pg, 0, sizeof(pg));
+ for (found = 0; *argv; ++argv)
+ if (manual(*argv, &m, &pg)) {
+ found = 1;
+ }
+
+ /* if nothing found, we're done. */
+ if (!found) {
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * handle the simple display cases first (m.cat, m.how, m.where)
+ */
+ if (m.cat) {
+ for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
+ if (**ap == '\0')
+ continue;
+ cat(*ap);
+ }
+ exit(cleanup());
+ }
+ if (m.how) {
+ for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
+ if (**ap == '\0')
+ continue;
+ how(*ap);
+ }
+ exit(cleanup());
+ }
+ if (m.where) {
+ for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
+ if (**ap == '\0')
+ continue;
+ (void)printf("%s\n", *ap);
+ }
+ exit(cleanup());
+ }
+
+ /*
+ * normal case - we display things in a single command, so
+ * build a list of things to display. first compute total
+ * length of buffer we will need so we can malloc it.
+ */
+ for (ap = pg.gl_pathv, len = m.pagerlen + 1; *ap != NULL; ++ap) {
+ if (**ap == '\0')
+ continue;
+ len += strlen(*ap) + 1;
+ }
+ if ((cmd = malloc(len)) == NULL) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ /* now build the command string... */
+ p = cmd;
+ len = m.pagerlen;
+ memcpy(p, m.pager, len);
+ p += len;
+ *p++ = ' ';
+ for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
+ if (**ap == '\0')
+ continue;
+ len = strlen(*ap);
+ memcpy(p, *ap, len);
+ p += len;
+ *p++ = ' ';
+ }
+ *--p = '\0';
+
+ /* Use system(3) in case someone's pager is "pager arg1 arg2". */
+ (void)system(cmd);
+
+ exit(cleanup());
+}
+
+static int
+manual_find_literalfile(struct manstate *mp, char **pv)
+{
+ ENTRY *suffix;
+ int found;
+ char buf[MAXPATHLEN];
+ const char *p;
+ int suflen;
+
+ found = 0;
+
+ /*
+ * Expand both '*' and suffix to force an actual
+ * match via fnmatch(3). Since the only match in pg
+ * is the literal file, the match is genuine.
+ */
+
+ TAILQ_FOREACH(suffix, &mp->buildlist->entrylist, q) {
+ for (p = suffix->s, suflen = 0;
+ *p != '\0' && !isspace((unsigned char)*p);
+ ++p)
+ ++suflen;
+ if (*p == '\0')
+ continue;
+
+ (void)snprintf(buf, sizeof(buf), "*%.*s", suflen, suffix->s);
+
+ if (!fnmatch(buf, *pv, 0)) {
+ if (!mp->where)
+ build_page(p + 1, pv, mp);
+ found = 1;
+ break;
+ }
+ }
+
+ return found;
+}
+
+static int
+manual_find_buildkeyword(const char *prefix, const char *escpage,
+ struct manstate *mp, char **pv)
+{
+ ENTRY *suffix;
+ int found;
+ char buf[MAXPATHLEN];
+ const char *p;
+ int suflen;
+
+ found = 0;
+ /* Try the _build keywords next. */
+ TAILQ_FOREACH(suffix, &mp->buildlist->entrylist, q) {
+ for (p = suffix->s, suflen = 0;
+ *p != '\0' && !isspace((unsigned char)*p);
+ ++p)
+ ++suflen;
+ if (*p == '\0')
+ continue;
+
+ (void)snprintf(buf, sizeof(buf), "%s%s%.*s",
+ prefix, escpage, suflen, suffix->s);
+ if (!fnmatch(buf, *pv, 0)) {
+ if (!mp->where)
+ build_page(p + 1, pv, mp);
+ found = 1;
+ break;
+ }
+ }
+
+ return found;
+}
+
+/*
+ * manual --
+ * Search the manuals for the pages.
+ */
+static int
+manual(char *page, struct manstate *mp, glob_t *pg)
+{
+ ENTRY *suffix, *mdir;
+ int anyfound, error, found;
+ size_t cnt;
+ char *p, buf[MAXPATHLEN], *escpage, *eptr;
+ static const char escglob[] = "\\~?*{}[]";
+
+ anyfound = 0;
+
+ /*
+ * Fixup page which may contain glob(3) special characters, e.g.
+ * the famous "No man page for [" FAQ.
+ */
+ if ((escpage = malloc((2 * strlen(page)) + 1)) == NULL) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ p = page;
+ eptr = escpage;
+
+ while (*p) {
+ if (strchr(escglob, *p) != NULL) {
+ *eptr++ = '\\';
+ *eptr++ = *p++;
+ } else
+ *eptr++ = *p++;
+ }
+
+ *eptr = '\0';
+
+ /*
+ * If 'page' is given with an absolute path,
+ * or a relative path explicitly beginning with "./"
+ * or "../", then interpret it as a file specification.
+ */
+ if ((page[0] == '/')
+ || (page[0] == '.' && page[1] == '/')
+ || (page[0] == '.' && page[1] == '.' && page[2] == '/')
+ ) {
+ /* check if file actually exists */
+ (void)strlcpy(buf, escpage, sizeof(buf));
+ error = glob(buf, GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg);
+ if (error != 0) {
+ if (error == GLOB_NOMATCH) {
+ goto notfound;
+ } else {
+ errx(EXIT_FAILURE, "glob failed");
+ }
+ }
+
+ if (pg->gl_matchc == 0)
+ goto notfound;
+
+ /* literal file only yields one match */
+ cnt = pg->gl_pathc - pg->gl_matchc;
+
+ if (manual_find_literalfile(mp, &pg->gl_pathv[cnt])) {
+ anyfound = 1;
+ } else {
+ /* It's not a man page, forget about it. */
+ *pg->gl_pathv[cnt] = '\0';
+ }
+
+ notfound:
+ if (!anyfound) {
+ if (addentry(mp->missinglist, page, 0) < 0) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ }
+ free(escpage);
+ return anyfound;
+ }
+
+ /* For each man directory in mymanpath ... */
+ TAILQ_FOREACH(mdir, &mp->mymanpath->entrylist, q) {
+
+ /*
+ * use glob(3) to look in the filesystem for matching files.
+ * match any suffix here, as we will check that later.
+ */
+ (void)snprintf(buf, sizeof(buf), "%s/%s.*", mdir->s, escpage);
+ if ((error = glob(buf,
+ GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg)) != 0) {
+ if (error == GLOB_NOMATCH)
+ continue;
+ else {
+ warn("globbing");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ }
+ if (pg->gl_matchc == 0)
+ continue;
+
+ /*
+ * start going through the matches glob(3) just found and
+ * use m.pathsearch (if present) to filter out pages we
+ * don't want. then verify the suffix is valid, and build
+ * the page if we have a _build suffix.
+ */
+ for (cnt = pg->gl_pathc - pg->gl_matchc;
+ cnt < pg->gl_pathc; ++cnt) {
+
+ /* filter on directory path name */
+ if (mp->pathsearch) {
+ p = strstr(pg->gl_pathv[cnt], mp->pathsearch);
+ if (!p || strchr(p, '/') == NULL) {
+ *pg->gl_pathv[cnt] = '\0'; /* zap! */
+ continue;
+ }
+ }
+
+ /*
+ * Try the _suffix keywords first.
+ *
+ * XXX
+ * Older versions of man.conf didn't have the _suffix
+ * keywords, it was assumed that everything was a .0.
+ * We just test for .0 first, it's fast and probably
+ * going to hit.
+ */
+ (void)snprintf(buf, sizeof(buf), "*/%s.0", escpage);
+ if (!fnmatch(buf, pg->gl_pathv[cnt], 0))
+ goto next;
+
+ found = 0;
+ TAILQ_FOREACH(suffix, &mp->suffixlist->entrylist, q) {
+ (void)snprintf(buf,
+ sizeof(buf), "*/%s%s", escpage,
+ suffix->s);
+ if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) {
+ found = 1;
+ break;
+ }
+ }
+ if (found)
+ goto next;
+
+ /* Try the _build keywords next. */
+ found = manual_find_buildkeyword("*/", escpage,
+ mp, &pg->gl_pathv[cnt]);
+ if (found) {
+next: anyfound = 1;
+ if (!mp->all) {
+ /* Delete any other matches. */
+ while (++cnt< pg->gl_pathc)
+ *pg->gl_pathv[cnt] = '\0';
+ break;
+ }
+ continue;
+ }
+
+ /* It's not a man page, forget about it. */
+ *pg->gl_pathv[cnt] = '\0';
+ }
+
+ if (anyfound && !mp->all)
+ break;
+ }
+
+ /* If not found, enter onto the missing list. */
+ if (!anyfound) {
+ if (addentry(mp->missinglist, page, 0) < 0) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ free(escpage);
+ return anyfound;
+}
+
+/*
+ * A do-nothing counterpart to fmtcheck(3) that only supplies the
+ * __format_arg marker. Actual fmtcheck(3) call is done once in
+ * config().
+ */
+__always_inline __format_arg(2)
+static inline const char *
+fmtcheck_ok(const char *userfmt, const char *template)
+{
+ return userfmt;
+}
+
+/*
+ * build_page --
+ * Build a man page for display.
+ */
+static void
+build_page(const char *fmt, char **pathp, struct manstate *mp)
+{
+ static int warned;
+ int olddir, fd, n;
+ size_t tmpdirlen;
+ char *p, *b;
+ char buf[MAXPATHLEN], cmd[MAXPATHLEN], tpath[MAXPATHLEN];
+ const char *tmpdir;
+
+ /* Let the user know this may take awhile. */
+ if (!warned) {
+ warned = 1;
+ warnx("Formatting manual page...");
+ }
+
+ /*
+ * Historically man chdir'd to the root of the man tree.
+ * This was used in man pages that contained relative ".so"
+ * directives (including other man pages for command aliases etc.)
+ * It even went one step farther, by examining the first line
+ * of the man page and parsing the .so filename so it would
+ * make hard(?) links to the cat'ted man pages for space savings.
+ * (We don't do that here, but we could).
+ */
+
+ /* copy and find the end */
+ for (b = buf, p = *pathp; (*b++ = *p++) != '\0';)
+ continue;
+
+ /*
+ * skip the last two path components, page name and man[n] ...
+ * (e.g. buf will be "/usr/share/man" and p will be "man1/man.1")
+ * we also save a pointer to our current directory so that we
+ * can fchdir() back to it. this allows relative MANDIR paths
+ * to work with multiple man pages... e.g. consider:
+ * cd /usr/share && man -M ./man cat ls
+ * when no "cat1" subdir files are present.
+ */
+ olddir = -1;
+ for (--b, --p, n = 2; b != buf; b--, p--)
+ if (*b == '/')
+ if (--n == 0) {
+ *b = '\0';
+ olddir = open(".", O_RDONLY);
+ (void) chdir(buf);
+ p++;
+ break;
+ }
+
+
+ /* advance fmt past the suffix spec to the printf format string */
+ for (; *fmt && isspace((unsigned char)*fmt); ++fmt)
+ continue;
+
+ /*
+ * Get a temporary file and build a version of the file
+ * to display. Replace the old file name with the new one.
+ */
+ if ((tmpdir = getenv("TMPDIR")) == NULL)
+ tmpdir = _PATH_TMP;
+ tmpdirlen = strlen(tmpdir);
+ (void)snprintf(tpath, sizeof (tpath), "%s%s%s", tmpdir,
+ (tmpdirlen > 0 && tmpdir[tmpdirlen-1] == '/') ? "" : "/", TMPFILE);
+ if ((fd = mkstemp(tpath)) == -1) {
+ warn("%s", tpath);
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ (void)snprintf(buf, sizeof(buf), "%s > %s", fmt, tpath);
+ (void)snprintf(cmd, sizeof(cmd), fmtcheck_ok(buf, "%s"), p);
+ (void)system(cmd);
+ (void)close(fd);
+ if ((*pathp = strdup(tpath)) == NULL) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ /* Link the built file into the remove-when-done list. */
+ if (addentry(mp->intmp, *pathp, 0) < 0) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ /* restore old directory so relative manpaths still work */
+ if (olddir != -1) {
+ fchdir(olddir);
+ close(olddir);
+ }
+}
+
+/*
+ * how --
+ * display how information
+ */
+static void
+how(const char *fname)
+{
+ FILE *fp;
+
+ int lcnt, print;
+ char buf[256];
+ const char *p;
+
+ if (!(fp = fopen(fname, "r"))) {
+ warn("%s", fname);
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+#define S1 "SYNOPSIS"
+#define S2 "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"
+#define D1 "DESCRIPTION"
+#define D2 "D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN"
+ for (lcnt = print = 0; fgets(buf, sizeof(buf), fp);) {
+ if (!strncmp(buf, S1, sizeof(S1) - 1) ||
+ !strncmp(buf, S2, sizeof(S2) - 1)) {
+ print = 1;
+ continue;
+ } else if (!strncmp(buf, D1, sizeof(D1) - 1) ||
+ !strncmp(buf, D2, sizeof(D2) - 1)) {
+ if (fp)
+ (void)fclose(fp);
+ return;
+ }
+ if (!print)
+ continue;
+ if (*buf == '\n')
+ ++lcnt;
+ else {
+ for(; lcnt; --lcnt)
+ (void)putchar('\n');
+ for (p = buf; isspace((unsigned char)*p); ++p)
+ continue;
+ (void)fputs(p, stdout);
+ }
+ }
+ (void)fclose(fp);
+}
+
+/*
+ * cat --
+ * cat out the file
+ */
+static void
+cat(const char *fname)
+{
+ int fd;
+ ssize_t n;
+ char buf[2048];
+
+ if ((fd = open(fname, O_RDONLY, 0)) < 0) {
+ warn("%s", fname);
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ while ((n = read(fd, buf, sizeof(buf))) > 0)
+ if (write(STDOUT_FILENO, buf, (size_t)n) != n) {
+ warn("write");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ if (n == -1) {
+ warn("read");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ (void)close(fd);
+}
+
+/*
+ * check_pager --
+ * check the user supplied page information
+ */
+static const char *
+check_pager(const char *name)
+{
+ const char *p;
+
+ /*
+ * if the user uses "more", we make it "more -s"; watch out for
+ * PAGER = "mypager /usr/ucb/more"
+ */
+ for (p = name; *p && !isspace((unsigned char)*p); ++p)
+ continue;
+ for (; p > name && *p != '/'; --p);
+ if (p != name)
+ ++p;
+
+ /* make sure it's "more", not "morex" */
+ if (!strncmp(p, "more", 4) && (!p[4] || isspace((unsigned char)p[4]))){
+ char *newname;
+ (void)asprintf(&newname, "%s %s", p, "-s");
+ name = newname;
+ }
+
+ return name;
+}
+
+/*
+ * jump --
+ * strip out flag argument and jump
+ */
+static void
+jump(char **argv, const char *flag, const char *name)
+{
+ char **arg;
+
+ argv[0] = __UNCONST(name);
+ for (arg = argv + 1; *arg; ++arg)
+ if (!strcmp(*arg, flag))
+ break;
+ for (; *arg; ++arg)
+ arg[0] = arg[1];
+ execvp(name, argv);
+ err(EXIT_FAILURE, "Cannot execute `%s'", name);
+}
+
+/*
+ * onsig --
+ * If signaled, delete the temporary files.
+ */
+static void
+onsig(int signo)
+{
+
+ (void)cleanup();
+
+ (void)raise_default_signal(signo);
+
+ /* NOTREACHED */
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * cleanup --
+ * Clean up temporary files, show any error messages.
+ */
+static int
+cleanup(void)
+{
+ TAG *intmpp, *missp;
+ ENTRY *ep;
+ int rval;
+
+ rval = EXIT_SUCCESS;
+ /*
+ * note that _missing and _intmp were created by main(), so
+ * gettag() cannot return NULL here.
+ */
+ missp = gettag("_missing", 0); /* missing man pages */
+ intmpp = gettag("_intmp", 0); /* tmp files we need to unlink */
+
+ TAILQ_FOREACH(ep, &missp->entrylist, q) {
+ warnx("no entry for %s in the manual.", ep->s);
+ rval = EXIT_FAILURE;
+ }
+
+ TAILQ_FOREACH(ep, &intmpp->entrylist, q)
+ (void)unlink(ep->s);
+
+ return rval;
+}
+
+static const char *
+getclass(const char *machine)
+{
+ char buf[BUFSIZ];
+ TAG *t;
+ snprintf(buf, sizeof(buf), "_%s", machine);
+ t = gettag(buf, 0);
+ return t != NULL && !TAILQ_EMPTY(&t->entrylist) ?
+ TAILQ_FIRST(&t->entrylist)->s : NULL;
+}
+
+static void
+addpath(struct manstate *m, const char *dir, size_t len, const char *sub)
+{
+ char buf[2 * MAXPATHLEN + 1];
+ (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,%s%s%s}",
+ dir, (dir[len - 1] == '/') ? "" : "/", sub, m->machine,
+ m->machclass ? "/" : "", m->machclass ? m->machclass : "",
+ m->machclass ? "," : "");
+ if (addentry(m->mymanpath, buf, 0) < 0)
+ errx(EXIT_FAILURE, "malloc failed");
+}
+
+/*
+ * usage --
+ * print usage message and die
+ */
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s [-acw|-h] [-C cfg] [-M path] "
+ "[-m path] [-S srch] [[-s] sect] name ...\n", getprogname());
+ (void)fprintf(stderr, "Usage: %s [-C file] -f command ...\n", getprogname());
+ (void)fprintf(stderr,
+ "Usage: %s [-C file] -k keyword ...\n",
+ getprogname());
+ (void)fprintf(stderr, "Usage: %s -p\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * printmanpath --
+ * Prints a list of directories containing man pages.
+ */
+static void
+printmanpath(struct manstate *m)
+{
+ ENTRY *epath;
+ char **ap;
+ glob_t pg;
+ struct stat sb;
+ TAG *path = m->mymanpath;
+
+ /* the tail queue is empty if no _default tag is defined in * man.conf */
+ if (TAILQ_EMPTY(&path->entrylist))
+ errx(EXIT_FAILURE, "Empty manpath");
+
+ TAILQ_FOREACH(epath, &path->entrylist, q) {
+ if (glob(epath->s, GLOB_BRACE | GLOB_NOSORT, NULL, &pg) != 0)
+ err(EXIT_FAILURE, "glob failed");
+
+ if (pg.gl_matchc == 0) {
+ globfree(&pg);
+ continue;
+ }
+
+ for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
+ /* Skip cat page directories */
+ if (strstr(*ap, "/cat") != NULL)
+ continue;
+ /* Skip non-directories. */
+ if (stat(*ap, &sb) == 0 && S_ISDIR(sb.st_mode))
+ printf("%s\n", *ap);
+ }
+ globfree(&pg);
+ }
+}
diff --git a/usr.bin/man/man.conf.5 b/usr.bin/man/man.conf.5
new file mode 100644
index 0000000..b89c74b
--- /dev/null
+++ b/usr.bin/man/man.conf.5
@@ -0,0 +1,286 @@
+.\" $NetBSD: man.conf.5,v 1.27 2016/06/16 15:11:43 abhinav Exp $
+.\"
+.\" Copyright (c) 1989, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)man.conf.5 8.5 (Berkeley) 1/2/94
+.\"
+.Dd June 16, 2016
+.Dt MAN.CONF 5
+.Os
+.Sh NAME
+.Nm man.conf
+.Nd configuration file for manual pages
+.Sh DESCRIPTION
+The
+.Nm
+file contains the default configuration used by
+.Xr man 1 ,
+.Xr apropos 1 ,
+.Xr whatis 1 ,
+.Xr catman 8 ,
+and
+.Xr makemandb 8
+to find manual pages and information about manual pages (e.g. the
+whatis database).
+.Pp
+Manual pages are located by searching an ordered set of directories
+called the
+.Dq man path
+for a file that matches the name of the requested page.
+Each directory in the search path usually has a set of subdirectories
+in it (though this is not required).
+When subdirectories are used, there are normally two subdirectories
+for each section of the manual.
+One subdirectory contains formatted copies of that section's manual
+pages that can be directly displayed to a terminal, while the other
+section subdirectory contains unformatted copies of the pages (see
+.Xr nroff 1
+and
+.Xr mdoc 7 ) .
+Formatted manual pages are normally named with a trailing
+.Dq \.0
+suffix.
+.Pp
+The
+.Nm
+file contains comment and configuration lines.
+Comment lines start with the
+.Dq #
+character.
+Blank lines are also treated as comment lines.
+Configuration lines consist of a configuration keyword followed by a
+configuration string.
+There are two types of configuration keywords: control keywords and
+section keywords.
+Control keywords must start with the
+.Dq _
+character.
+The following control keywords are currently defined:
+.Bl -tag -width XXmachineX
+.It _build
+Identifies the set of suffixes used for manual pages that must be
+formatted for display and the command that should be used to format
+them.
+Manual file names, regardless of their format, are expected to end in a
+.Dq \.*
+pattern, i.e. a
+.Dq \&\.
+followed by some suffix.
+The first field of a _build line contains a man page suffix specification.
+The suffix specification may contain the normal shell globbing characters
+(NOT including curly braces
+.Pq Dq {} ) .
+The rest of the _build line is a shell command line whose standard
+output is a formatted manual page that can be directly displayed to
+the user.
+There should be exactly one occurrence of the string
+.Dq %s
+in the shell command line, and it will
+be replaced by the name of the file which is being formatted.
+.It _crunch
+Used by
+.Xr catman 8
+to determine how to crunch formatted pages
+which originally were compressed man pages: The first field lists a suffix
+which indicates what kind of compression were used to compress the man page.
+The rest of the line must be a shell command line, used to compress the
+formatted pages.
+There should be exactly one occurrence of the string
+.Dq %s
+in the shell command line, and it will
+be replaced by the name of the output file.
+.It _default
+Contains the system-wide default man path used to search for man pages.
+.It _mandb
+Defines the full pathname (not just a directory path) for a database to
+be used
+by the
+.Xr apropos 1
+and
+.Xr whatis 1
+commands.
+The pathname may contain the normal shell globbing characters,
+including curly braces
+.Pq Dq {} ;
+to escape a shell globbing character,
+precede it with a backslash
+.Pq Dq \e .
+.It _subdir
+Contains the list (in search order) of section subdirectories which will
+be searched in any man path directory named with a trailing slash
+.Pq Dq /
+character.
+This list is also used, even if there is no trailing slash character,
+when a path is specified to the
+.Xr man 1
+utility by the user, by the
+.Ev MANPATH
+environment variable, or by the
+.Fl M
+and
+.Fl m
+options.
+.It _suffix
+identifies the set of suffixes used for formatted man pages
+(the
+.Dq \.0
+suffix is normally used here).
+Formatted man pages can be directly displayed to the user.
+Each suffix may contain the normal shell globbing characters (NOT
+including curly braces
+.Pq Dq {} ) .
+.It _version
+Contains the version of the configuration file.
+.It _ Ns Aq machine
+Defines additional paths to be searched for the particular
+.Dv machine
+whose literal value is taken from
+.Xr uname 1
+.Fl m .
+For example on an
+.Dv amd64 ,
+.Dv _amd64
+is used.
+.El
+.Pp
+Section configuration lines in
+.Nm
+consist of a section keyword naming the section and a configuration
+string that defines the directory or subdirectory path that the section's
+manual pages are located in.
+The path may contain the normal shell globbing characters,
+including curly braces
+.Pq Dq {} ;
+to escape a shell globbing character,
+precede it with a backslash
+.Pq Dq \e .
+Section keywords must not start with the
+.Dq _
+character.
+.Pp
+A section path may contain either a list of absolute directories or
+a list of or relative directories (but not both).
+Relative directory paths are treated as a list of subdirectories that
+are appended to the current man path directory being searched.
+Section configuration lines with absolute directory paths (starting with
+.Dq / )
+completely replace the current man search path directory with their
+content.
+.Pp
+Section configuration lines with absolute directory paths ending
+with a trailing slash character are expected to contain subdirectories
+of manual pages, (see the keyword
+.Dq _subdir
+above).
+The
+.Dq _subdir
+subdirectory list is not applied to absolute section directories
+if there is no trailing slash.
+.Pp
+In addition to the above rules, the
+.Xr man 1
+command also always checks in each directory that it searches for
+a subdirectory with the same name as the current machine type.
+If the machine-specific directory is found, it is also searched.
+This allows the manual to contain machine-specific man pages.
+Note that the machine subdirectory does not need to be specified
+in the
+.Nm
+file.
+.Pp
+Multiple specifications for all types of
+.Nm
+configuration lines are
+cumulative and the entries are used in the order listed in the file;
+multiple entries may be listed per line, as well.
+.Sh FILES
+.Bl -tag -width /etc/man.conf -compact
+.It Pa /etc/man.conf
+Standard manual configuration file.
+.El
+.Sh EXAMPLES
+Given the following
+.Nm
+file:
+.Bd -literal -offset indent
+_version BSD.2
+_subdir cat[123]
+_suffix .0
+_build .[1-9] nroff -man %s
+_build .tbl tbl %s | nroff -man
+_i386 x86
+_default /usr/share/man/
+sect3 /usr/share/man/{old/,}cat3
+.Ed
+.Pp
+By default, the command
+.Dq Li man mktemp
+will search for
+.Dq mktemp. Ns Aq any_digit
+and
+.Dq mktemp.tbl
+in the directories
+.Dq Pa /usr/share/man/cat1 ,
+.Dq Pa /usr/share/man/cat2 ,
+and
+.Dq Pa /usr/share/man/cat3 .
+If on a machine of type
+.Dq vax ,
+the subdirectory
+.Dq vax
+in each
+directory would be searched as well, before the directory was
+searched.
+.Pp
+If
+.Dq mktemp.tbl
+was found first, the command
+.Dq Li tbl Ao manual page Ac | nroff -man
+would be run to build a man page for display to the user.
+.Pp
+The command
+.Dq Li man sect3 mktemp
+would search the directories
+.Dq Pa /usr/share/man/old/cat3
+and
+.Dq Pa /usr/share/man/cat3 ,
+in that order, for
+the mktemp manual page.
+If a subdirectory with the same name as the current machine type
+existed in any of them, it would be searched as well, before each
+of them were searched.
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr machine 1 ,
+.Xr man 1 ,
+.Xr whatis 1 ,
+.Xr whereis 1 ,
+.Xr fnmatch 3 ,
+.Xr glob 3 ,
+.Xr catman 8 ,
+.Xr makemandb 8
diff --git a/usr.bin/man/manconf.c b/usr.bin/man/manconf.c
new file mode 100644
index 0000000..f12d031
--- /dev/null
+++ b/usr.bin/man/manconf.c
@@ -0,0 +1,272 @@
+/* $NetBSD: manconf.c,v 1.8 2014/02/17 02:53:48 uwe Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * manconf.c: provides interface for reading man.conf files
+ *
+ * note that this code is shared across all programs that read man.conf.
+ * (currently: apropos, catman, makewhatis, man, and whatis...)
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)config.c 8.8 (Berkeley) 1/31/95";
+#else
+__RCSID("$NetBSD: manconf.c,v 1.8 2014/02/17 02:53:48 uwe Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "manconf.h"
+#include "pathnames.h"
+
+TAILQ_HEAD(_head, _tag);
+static struct _head head; /* 'head' -- top level data structure */
+
+/*
+ * xstrdup: like strdup, but also returns length of string in lenp
+ */
+static char *
+xstrdup(const char *str, size_t *lenp)
+{
+ size_t len;
+ char *copy;
+
+ len = strlen(str) + 1;
+ copy = malloc(len);
+ if (!copy)
+ return NULL;
+ (void)memcpy(copy, str, len);
+ if (lenp)
+ *lenp = len - 1; /* subtract out the null */
+ return copy;
+}
+
+/*
+ * config --
+ *
+ * Read the configuration file and build a doubly linked
+ * list off of "head" that looks like:
+ *
+ * tag1 <-> entry <-> entry <-> entry
+ * |
+ * tag2 <-> entry <-> entry <-> entry
+ *
+ * note: will err/errx out on error (fopen or malloc failure)
+ */
+void
+config(const char *fname)
+{
+ TAG *tp;
+ FILE *cfp;
+ size_t len;
+ int lcnt;
+ char *p, *t, type;
+
+ if (fname == NULL)
+ fname = _PATH_MANCONF;
+ if ((cfp = fopen(fname, "r")) == NULL)
+ err(EXIT_FAILURE, "%s", fname);
+ TAILQ_INIT(&head);
+ for (lcnt = 1; (p = fgetln(cfp, &len)) != NULL; ++lcnt) {
+ if (len == 1) /* Skip empty lines. */
+ continue;
+ if (p[len - 1] != '\n') { /* Skip corrupted lines. */
+ warnx("%s: line %d corrupted", fname, lcnt);
+ continue;
+ }
+ p[len - 1] = '\0'; /* Terminate the line. */
+
+ /* Skip leading space. */
+ for (/*EMPTY*/; *p != '\0' && isspace((unsigned char)*p); ++p)
+ continue;
+ /* Skip empty/comment lines. */
+ if (*p == '\0' || *p == '#')
+ continue;
+ /* Find first token. */
+ for (t = p; *t && !isspace((unsigned char)*t); ++t)
+ continue;
+ if (*t == '\0') /* Need more than one token.*/
+ continue;
+ *t = '\0';
+
+ tp = gettag(p, 1);
+ if (!tp)
+ errx(EXIT_FAILURE, "gettag: malloc failed");
+
+ /*
+ * Attach new records. Check to see if it is a
+ * section record or not.
+ */
+
+ if (*p == '_') { /* not a section record */
+ /*
+ * Special cases: _build and _crunch take the
+ * rest of the line as a single entry.
+ */
+ if (!strcmp(p, "_build") || !strcmp(p, "_crunch")) {
+ const char *u;
+
+ /*
+ * The reason we're not just using
+ * strtok(3) for all of the parsing is
+ * so we don't get caught if a line
+ * has only a single token on it.
+ */
+ while (*++t && isspace((unsigned char)*t));
+#ifndef HAVE_NBTOOL_CONFIG_H
+ /* pre-verify user-supplied command format */
+ u = t;
+ while (*u && !isspace((unsigned char)*u))
+ ++u;
+ while (*u && isspace((unsigned char)*u))
+ ++u;
+ if (fmtcheck(u, "%s") != u) {
+ warnx("%s:%d: invalid %s command ignored",
+ fname, lcnt, p);
+ continue;
+ }
+#endif /* !HAVE_NBTOOL_CONFIG_H */
+ if (addentry(tp, t, 0) == -1)
+ errx(EXIT_FAILURE,
+ "addentry: malloc failed");
+ } else {
+ for (++t; (p = strtok(t, " \t\n")) != NULL;
+ t = NULL) {
+ if (addentry(tp, p, 0) == -1)
+ errx(EXIT_FAILURE,
+ "addentry: malloc failed");
+ }
+ }
+
+ } else { /* section record */
+
+ /*
+ * section entries can either be all absolute
+ * paths or all relative paths, but not both.
+ */
+ type = (char)((TAILQ_FIRST(&tp->entrylist) != NULL) ?
+ *(TAILQ_FIRST(&tp->entrylist)->s) : '\0');
+
+ for (++t; (p = strtok(t, " \t\n")) != NULL; t = NULL) {
+
+ /* ensure an assigned type */
+ if (type == 0)
+ type = *p;
+
+ /* check for illegal mix */
+ if (*p != type) {
+ warnx("section %s: %s: invalid entry, does not match previous types",
+ tp->s, p);
+ warnx("man.conf cannot mix absolute and relative paths in an entry");
+ continue;
+ }
+ if (addentry(tp, p, 0) == -1)
+ errx(EXIT_FAILURE,
+ "addentry: malloc failed");
+ }
+ }
+ }
+ (void)fclose(cfp);
+}
+
+/*
+ * gettag --
+ * if (!create) return tag for given name if it exists, or NULL otherwise
+ *
+ * if (create) return tag for given name if it exists, try and create
+ * a new tag if it does not exist. return NULL if unable to create new
+ * tag.
+ */
+TAG *
+gettag(const char *name, int create)
+{
+ TAG *tp;
+
+ TAILQ_FOREACH(tp, &head, q)
+ if (!strcmp(name, tp->s))
+ return tp;
+ if (!create)
+ return NULL;
+
+ /* try and add it in */
+ tp = malloc(sizeof(*tp));
+ if (tp)
+ tp->s = xstrdup(name, &tp->len);
+ if (!tp || !tp->s) {
+ if (tp)
+ free(tp);
+ return NULL;
+ }
+ TAILQ_INIT(&tp->entrylist);
+ TAILQ_INSERT_TAIL(&head, tp, q);
+ return tp;
+}
+
+/*
+ * addentry --
+ * add an entry to a list.
+ * returns -1 if malloc failed, otherwise 0.
+ */
+int
+addentry(TAG *tp, const char *newent, int ishead)
+{
+ ENTRY *ep;
+
+ ep = malloc(sizeof(*ep));
+ if (ep)
+ ep->s = xstrdup(newent, &ep->len);
+ if (!ep || !ep->s) {
+ if (ep)
+ free(ep);
+ return -1;
+ }
+ if (ishead)
+ TAILQ_INSERT_HEAD(&tp->entrylist, ep, q);
+ else
+ TAILQ_INSERT_TAIL(&tp->entrylist, ep, q);
+
+ return 0;
+}
diff --git a/usr.bin/man/manconf.h b/usr.bin/man/manconf.h
new file mode 100644
index 0000000..ab87728
--- /dev/null
+++ b/usr.bin/man/manconf.h
@@ -0,0 +1,60 @@
+/* $NetBSD: manconf.h,v 1.3 2006/04/10 14:39:06 chuck Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)config.h 8.4 (Berkeley) 12/18/93
+ */
+
+/*
+ * manconf.h: common data structures and APIs shared across all programs
+ * that access man.conf (currently: apropos, catman, makewhatis, man, and
+ * whatis).
+ */
+
+/* TAG: top-level structure (one per section/reserved word) */
+typedef struct _tag {
+ TAILQ_ENTRY(_tag) q; /* Queue of tags */
+
+ TAILQ_HEAD(tqh, _entry) entrylist; /* Queue of entries */
+ char *s; /* Associated string */
+ size_t len; /* Length of 's' */
+} TAG;
+
+/* ENTRY: each TAG has one or more ENTRY strings linked off of it */
+typedef struct _entry {
+ TAILQ_ENTRY(_entry) q; /* Queue of entries */
+
+ char *s; /* Associated string */
+ size_t len; /* Length of 's' */
+} ENTRY;
+
+int addentry(TAG *, const char *, int);
+void config(const char *);
+TAG *gettag(const char *, int);
diff --git a/usr.bin/man/pathnames.h b/usr.bin/man/pathnames.h
new file mode 100644
index 0000000..15aa211
--- /dev/null
+++ b/usr.bin/man/pathnames.h
@@ -0,0 +1,39 @@
+/* $NetBSD: pathnames.h,v 1.7 2016/05/21 20:54:34 christos Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)pathnames.h 8.3 (Berkeley) 1/2/94
+ */
+
+#include <paths.h>
+
+#define _PATH_MANCONF "/etc/man.conf"
+#define _PATH_PAGER "/usr/bin/more -s"
+#define _PATH_WHATIS "whatis.db"
+#define TMPFILE "man.XXXXXX"
diff --git a/usr.bin/mesg/mesg.1 b/usr.bin/mesg/mesg.1
new file mode 100644
index 0000000..e5d9100
--- /dev/null
+++ b/usr.bin/mesg/mesg.1
@@ -0,0 +1,89 @@
+.\" $NetBSD: mesg.1,v 1.10 2017/07/03 21:34:20 wiz Exp $
+.\"
+.\" Copyright (c) 1987, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)mesg.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt MESG 1
+.Os
+.Sh NAME
+.Nm mesg
+.Nd display (do not display) messages from other users
+.Sh SYNOPSIS
+.Nm
+.Op Cm n | Cm y
+.Sh DESCRIPTION
+The
+.Nm
+utility is invoked by a user to control write access others
+have to the terminal device associated with the standard error
+output.
+Write access is allowed by default, and programs such as
+.Xr talk 1
+and
+.Xr write 1
+may display messages on the terminal.
+.Pp
+Options available:
+.Bl -tag -width flag
+.It Cm n
+Disallows messages.
+.It Cm y
+Permits messages to be displayed.
+.El
+.Pp
+If no arguments are given,
+.Nm
+displays the present message status to the standard error output.
+.Pp
+The
+.Nm
+utility exits with one of the following values:
+.Pp
+.Bl -tag -width flag -compact -offset indent
+.It Li "\ 0"
+Messages are allowed.
+.It Li "\ 1"
+Messages are not allowed.
+.It Li ">1"
+An error has occurred.
+.El
+.Sh FILES
+.Bl -tag -width /dev/[pt]ty[pq]? -compact
+.It Pa /dev/[pt]ty[pq]?
+.El
+.Sh SEE ALSO
+.Xr biff 1 ,
+.Xr talk 1 ,
+.Xr write 1
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v6 .
diff --git a/usr.bin/mesg/mesg.c b/usr.bin/mesg/mesg.c
new file mode 100644
index 0000000..cc54a75
--- /dev/null
+++ b/usr.bin/mesg/mesg.c
@@ -0,0 +1,108 @@
+/* $NetBSD: mesg.c,v 1.8 2008/07/21 14:19:24 lukem Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993
+ * The Regents of the University of California. All rights reserved.
+ * (c) UNIX System Laboratories, Inc.
+ * All or some portions of this file are derived from material licensed
+ * to the University of California by American Telephone and Telegraph
+ * Co. or Unix System Laboratories, Inc. and are reproduced herein with
+ * the permission of UNIX System Laboratories, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1987, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mesg.c 8.2 (Berkeley) 1/21/94";
+#endif
+__RCSID("$NetBSD: mesg.c,v 1.8 2008/07/21 14:19:24 lukem Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ char *tty;
+ int ch;
+
+ setprogname(*argv);
+
+ while ((ch = getopt(argc, argv, "")) != -1)
+ switch (ch) {
+ case '?':
+ default:
+ goto usage;
+ }
+ argc -= optind;
+ argv += optind;
+
+ if ((tty = ttyname(STDIN_FILENO)) == NULL &&
+ (tty = ttyname(STDOUT_FILENO)) == NULL &&
+ (tty = ttyname(STDERR_FILENO)) == NULL)
+ err(2, "ttyname");
+ if (stat(tty, &sb) == -1)
+ err(2, "%s", tty);
+
+ if (*argv == NULL) {
+ if (sb.st_mode & S_IWGRP) {
+ (void)fprintf(stderr, "is y\n");
+ return 0;
+ }
+ (void)fprintf(stderr, "is n\n");
+ return 1;
+ }
+
+ switch (*argv[0]) {
+ case 'y':
+ if (chmod(tty, sb.st_mode | S_IWGRP) == -1)
+ err(2, "%s", tty);
+ return 0;
+ case 'n':
+ if (chmod(tty, sb.st_mode & ~S_IWGRP) == -1)
+ err(2, "%s", tty);
+ return 1;
+ }
+
+usage: (void)fprintf(stderr, "Usage: %s [y | n]\n", getprogname());
+ return 2;
+}
diff --git a/usr.bin/mkfifo/mkfifo.1 b/usr.bin/mkfifo/mkfifo.1
new file mode 100644
index 0000000..a178dc2
--- /dev/null
+++ b/usr.bin/mkfifo/mkfifo.1
@@ -0,0 +1,86 @@
+.\" $NetBSD: mkfifo.1,v 1.13 2017/07/04 07:03:32 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)mkfifo.1 8.2 (Berkeley) 1/5/94
+.\"
+.Dd January 5, 1994
+.Dt MKFIFO 1
+.Os
+.Sh NAME
+.Nm mkfifo
+.Nd make fifos
+.Sh SYNOPSIS
+.Nm
+.Op Fl m Ar mode
+.Ar fifo_name ...
+.Sh DESCRIPTION
+.Nm
+creates the fifos requested, in the order specified,
+using mode
+.Li \&0666
+modified by the current
+.Xr umask 2 .
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl m
+Set the file permission bits of newly-created fifos to
+.Ar mode .
+The mode is specified as in
+.Xr chmod 1 .
+In symbolic mode strings, the
+.Dq +
+and
+.Dq -
+operators are interpreted relative to an assumed initial mode of
+.Dq a=rw
+.El
+.Pp
+.Nm
+requires write permission in the parent directory.
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr mkdir 1 ,
+.Xr rm 1 ,
+.Xr mkfifo 2 ,
+.Xr mknod 8
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2-92
+compliant.
+.Sh HISTORY
+.Nm
+command appeared in
+.Bx 4.4 .
diff --git a/usr.bin/mkfifo/mkfifo.c b/usr.bin/mkfifo/mkfifo.c
new file mode 100644
index 0000000..9d49ec5
--- /dev/null
+++ b/usr.bin/mkfifo/mkfifo.c
@@ -0,0 +1,107 @@
+/* $NetBSD: mkfifo.c,v 1.13 2011/09/04 20:30:34 joerg Exp $ */
+
+/*
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1990, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mkfifo.c 8.2 (Berkeley) 1/5/94";
+#endif
+__RCSID("$NetBSD: mkfifo.c,v 1.13 2011/09/04 20:30:34 joerg Exp $");
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <err.h>
+
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ int ch, exitval;
+ void *set;
+ mode_t mode;
+
+ setlocale (LC_ALL, "");
+
+ /* The default mode is the value of the bitwise inclusive or of
+ S_IRUSR, S_IWUSR, S_IRGRP, S_IWGRP, S_IROTH, and S_IWOTH
+ modified by the file creation mask */
+ mode = 0666 & ~umask(0);
+
+ while ((ch = getopt(argc, argv, "m:")) != -1)
+ switch(ch) {
+ case 'm':
+ if (!(set = setmode(optarg))) {
+ err(1, "Cannot set file mode `%s'", optarg);
+ /* NOTREACHED */
+ }
+ /* In symbolic mode strings, the + and - operators are
+ interpreted relative to an assumed initial mode of
+ a=rw. */
+ mode = getmode(set, 0666);
+ free(set);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+ if (argv[0] == NULL)
+ usage();
+
+ for (exitval = 0; *argv; ++argv) {
+ if (mkfifo(*argv, mode) < 0) {
+ warn("%s", *argv);
+ exitval = 1;
+ }
+ }
+ exit(exitval);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: mkfifo [-m mode] fifoname ...\n");
+ exit(1);
+}
diff --git a/usr.bin/mkstr/mkstr.1 b/usr.bin/mkstr/mkstr.1
new file mode 100644
index 0000000..5fd812d
--- /dev/null
+++ b/usr.bin/mkstr/mkstr.1
@@ -0,0 +1,131 @@
+.\" $NetBSD: mkstr.1,v 1.13 2017/07/03 21:34:20 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)mkstr.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt MKSTR 1
+.Os
+.Sh NAME
+.Nm mkstr
+.Nd create an error message file by massaging C source
+.Sh SYNOPSIS
+.Nm
+.Op Fl
+.Ar messagefile
+.Ar prefix file ...
+.Sh DESCRIPTION
+.Nm
+creates files containing error messages extracted from C source,
+and restructures the same C source, to use the created error message
+file.
+The intent of
+.Nm
+was to reduce the size of large programs and
+reduce swapping (see
+.Sx BUGS
+section below).
+.Pp
+.Nm
+processes each of the specified
+.Ar files ,
+placing a restructured version of the input in a file whose name
+consists of the specified
+.Ar prefix
+and the original name.
+A typical usage of
+.Nm
+is
+.Bd -literal -offset indent
+mkstr pistrings xx *.c
+.Ed
+.Pp
+This command causes all the error messages from the C source
+files in the current directory to be placed in the file
+.Ar pistrings
+and restructured copies of the sources to be placed in
+files whose names are prefixed with
+.Ar \&xx .
+.Pp
+Options:
+.Bl -tag -width indent
+.It Fl
+Error messages are placed at the end of the specified
+message file for recompiling part of a large
+.Nm
+ed
+program.
+.El
+.Pp
+.Nm
+finds error messages in the source by
+searching for the string
+.Li \&`error("'
+in the input stream.
+Each time it occurs, the C string starting at the
+.Sq \&"\&
+is stored
+in the message file followed by a null character and a new-line character;
+The new source is restructured with
+.Xr lseek 2
+pointers into the error message file for retrieval.
+.Bd -literal -offset indent
+char efilname = "/usr/lib/pi_strings";
+int efil = -1;
+
+error(a1, a2, a3, a4)
+\&{
+ char buf[256];
+
+ if (efil < 0) {
+ efil = open(efilname, 0);
+ if (efil < 0) {
+oops:
+ perror(efilname);
+ exit 1 ;
+ }
+ }
+ if (lseek(efil, a1, 0) < 0 || read(efil, buf, 256) \*[Le] 0)
+ goto oops;
+ printf(buf, a2, a3, a4);
+}
+.Ed
+.Sh SEE ALSO
+.Xr xstr 1 ,
+.Xr lseek 2
+.Sh HISTORY
+.Nm
+appeared in
+.Bx 1 .
+.Sh BUGS
+.Nm
+was intended for the limited architecture of the PDP-11 family.
+Very few programs actually use it.
+It is not an efficient method, the error messages
+should be stored in the program text.
diff --git a/usr.bin/mkstr/mkstr.c b/usr.bin/mkstr/mkstr.c
new file mode 100644
index 0000000..0bfe6ca
--- /dev/null
+++ b/usr.bin/mkstr/mkstr.c
@@ -0,0 +1,320 @@
+/* $NetBSD: mkstr.c,v 1.17 2016/09/05 00:40:29 sevan Exp $ */
+
+/*
+ * Copyright (c) 1980, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1980, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mkstr.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: mkstr.c,v 1.17 2016/09/05 00:40:29 sevan Exp $");
+#endif
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define ungetchar(c) ungetc(c, stdin)
+
+/*
+ * mkstr - create a string error message file by massaging C source
+ *
+ * Bill Joy UCB August 1977
+ *
+ * Modified March 1978 to hash old messages to be able to recompile
+ * without addding messages to the message file (usually)
+ *
+ * Based on an earlier program conceived by Bill Joy and Chuck Haley
+ *
+ * Program to create a string error message file
+ * from a group of C programs. Arguments are the name
+ * of the file where the strings are to be placed, the
+ * prefix of the new files where the processed source text
+ * is to be placed, and the files to be processed.
+ *
+ * The program looks for 'error("' in the source stream.
+ * Whenever it finds this, the following characters from the '"'
+ * to a '"' are replaced by 'seekpt' where seekpt is a
+ * pointer into the error message file.
+ * If the '(' is not immediately followed by a '"' no change occurs.
+ *
+ * The optional '-' causes strings to be added at the end of the
+ * existing error message file for recompilation of single routines.
+ */
+
+
+FILE *mesgread, *mesgwrite;
+char *progname;
+const char usagestr[] = "usage: %s [ - ] mesgfile prefix file ...\n";
+char name[100], *np;
+
+void process(void);
+int match(const char *);
+int octdigit(char);
+void inithash(void);
+long hashit(const char *, char, long);
+void copystr(void);
+int fgetNUL(char *, int, FILE *);
+
+int
+main(int argc, char *argv[])
+{
+ char addon = 0;
+
+ argc--, progname = *argv++;
+ if (argc > 1 && argv[0][0] == '-')
+ addon++, argc--, argv++;
+ if (argc < 3)
+ fprintf(stderr, usagestr, progname), exit(1);
+ mesgwrite = fopen(argv[0], addon ? "a" : "w");
+ if (mesgwrite == NULL)
+ perror(argv[0]), exit(1);
+ mesgread = fopen(argv[0], "r");
+ if (mesgread == NULL)
+ perror(argv[0]), exit(1);
+ inithash();
+ argc--, argv++;
+ strlcpy(name, argv[0], sizeof(name));
+ np = name + strlen(name);
+ argc--, argv++;
+ do {
+ strlcpy(np, argv[0], sizeof(name) - (np - name));
+ if (freopen(name, "w", stdout) == NULL)
+ perror(name), exit(1);
+ if (freopen(argv[0], "r", stdin) == NULL)
+ perror(argv[0]), exit(1);
+ process();
+ argc--, argv++;
+ } while (argc > 0);
+ exit(0);
+}
+
+void
+process(void)
+{
+ int c;
+
+ for (;;) {
+ c = getchar();
+ if (c == EOF)
+ return;
+ if (c != 'e') {
+ putchar(c);
+ continue;
+ }
+ if (match("error(")) {
+ printf("error(");
+ c = getchar();
+ if (c != '"')
+ putchar(c);
+ else
+ copystr();
+ }
+ }
+}
+
+int
+match(const char *ocp)
+{
+ const char *cp;
+ int c;
+
+ for (cp = ocp + 1; *cp; cp++) {
+ c = getchar();
+ if (c != *cp) {
+ while (ocp < cp)
+ putchar(*ocp++);
+ ungetchar(c);
+ return (0);
+ }
+ }
+ return (1);
+}
+
+void
+copystr(void)
+{
+ int c, ch;
+ char buf[512];
+ char *cp = buf;
+
+ for (;;) {
+ c = getchar();
+ if (c == EOF)
+ break;
+ switch (c) {
+
+ case '"':
+ *cp++ = 0;
+ goto out;
+ case '\\':
+ c = getchar();
+ switch (c) {
+
+ case 'b':
+ c = '\b';
+ break;
+ case 't':
+ c = '\t';
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case 'n':
+ c = '\n';
+ break;
+ case '\n':
+ continue;
+ case 'f':
+ c = '\f';
+ break;
+ case '0':
+ c = 0;
+ break;
+ case '\\':
+ break;
+ default:
+ if (!octdigit(c))
+ break;
+ c -= '0';
+ ch = getchar();
+ if (!octdigit(ch))
+ break;
+ c <<= 7, c += ch - '0';
+ ch = getchar();
+ if (!octdigit(ch))
+ break;
+ c <<= 3, c+= ch - '0', ch = -1;
+ break;
+ }
+ }
+ *cp++ = c;
+ }
+out:
+ *cp = 0;
+ printf("%ld", hashit(buf, 1, 0));
+}
+
+int
+octdigit(char c)
+{
+
+ return (c >= '0' && c <= '7');
+}
+
+void
+inithash(void)
+{
+ char buf[512];
+ long mesgpt = 0;
+
+ rewind(mesgread);
+ while (fgetNUL(buf, sizeof buf, mesgread) != 0) {
+ hashit(buf, 0, mesgpt);
+ mesgpt += strlen(buf) + 2;
+ }
+}
+
+#define NBUCKETS 511
+
+struct hash {
+ long hval;
+ long hpt;
+ struct hash *hnext;
+} *bucket[NBUCKETS];
+
+long
+hashit(const char *str, char really, long fakept)
+{
+ int i;
+ struct hash *hp;
+ char buf[512];
+ long hashval = 0;
+ const char *cp;
+
+#ifdef __GNUC__
+ hp = NULL; /* XXX gcc */
+#endif
+ if (really)
+ fflush(mesgwrite);
+ for (cp = str; *cp;)
+ hashval = (hashval << 1) + *cp++;
+ i = hashval % NBUCKETS;
+ if (i < 0)
+ i += NBUCKETS;
+ if (really != 0)
+ for (hp = bucket[i]; hp != 0; hp = hp->hnext)
+ if (hp->hval == hashval) {
+ fseek(mesgread, hp->hpt, 0);
+ fgetNUL(buf, sizeof buf, mesgread);
+/*
+ fprintf(stderr, "Got (from %ld) %s\n", hp->hpt, buf);
+*/
+ if (strcmp(buf, str) == 0)
+ break;
+ }
+ if (!really || hp == 0) {
+ hp = (struct hash *) calloc(1, sizeof *hp);
+ hp->hnext = bucket[i];
+ hp->hval = hashval;
+ hp->hpt = really ? ftell(mesgwrite) : fakept;
+ if (really) {
+ fwrite(str, sizeof (char), strlen(str) + 1, mesgwrite);
+ fwrite("\n", sizeof (char), 1, mesgwrite);
+ }
+ bucket[i] = hp;
+ }
+/*
+ fprintf(stderr, "%s hashed to %ld at %ld\n", str, hp->hval, hp->hpt);
+*/
+ return (hp->hpt);
+}
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+int
+fgetNUL(char *obuf, int rmdr, FILE *file)
+{
+ int c;
+ char *buf = obuf;
+
+ while (--rmdr > 0 && (c = getc(file)) != 0 && c != EOF)
+ *buf++ = c;
+ *buf++ = 0;
+ getc(file);
+ return ((feof(file) || ferror(file)) ? 0 : 1);
+}
diff --git a/usr.bin/newgrp/grutil.c b/usr.bin/newgrp/grutil.c
new file mode 100644
index 0000000..ee81590
--- /dev/null
+++ b/usr.bin/newgrp/grutil.c
@@ -0,0 +1,338 @@
+/* $NetBSD: grutil.c,v 1.4 2014/06/23 06:57:31 shm Exp $ */
+
+/*-
+ * Copyright (c) 2007 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Brian Ginsbach.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: grutil.c,v 1.4 2014/06/23 06:57:31 shm Exp $");
+
+#include <sys/param.h>
+#include <err.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+#ifdef LOGIN_CAP
+#include <login_cap.h>
+#endif
+
+#include "grutil.h"
+
+typedef enum {
+ ADDGRP_NOERROR = 0, /* must be zero */
+ ADDGRP_EMALLOC = 1,
+ ADDGRP_EGETGROUPS = 2,
+ ADDGRP_ESETGROUPS = 3
+} addgrp_ret_t;
+
+static void
+free_groups(void *groups)
+{
+ int oerrno;
+
+ oerrno = errno;
+ free(groups);
+ errno = oerrno;
+}
+
+static addgrp_ret_t
+alloc_groups(int *ngroups, gid_t **groups, int *ngroupsmax)
+{
+ *ngroupsmax = (int)sysconf(_SC_NGROUPS_MAX);
+ if (*ngroupsmax < 0)
+ *ngroupsmax = NGROUPS_MAX;
+
+ *groups = malloc(*ngroupsmax * sizeof(**groups));
+ if (*groups == NULL)
+ return ADDGRP_EMALLOC;
+
+ *ngroups = getgroups(*ngroupsmax, *groups);
+ if (*ngroups == -1) {
+ free_groups(*groups);
+ return ADDGRP_ESETGROUPS;
+ }
+ return ADDGRP_NOERROR;
+}
+
+static addgrp_ret_t
+addgid(gid_t *groups, int ngroups, int ngroupsmax, gid_t gid, int makespace)
+{
+ int i;
+
+ /* search for gid in supplemental group list */
+ for (i = 0; i < ngroups && groups[i] != gid; i++)
+ continue;
+
+ /* add the gid to the supplemental group list */
+ if (i == ngroups) {
+ if (ngroups < ngroupsmax)
+ groups[ngroups++] = gid;
+ else { /*
+ * setgroups(2) will fail with errno = EINVAL
+ * if ngroups > nmaxgroups. If makespace is
+ * set, replace the last group with the new
+ * one. Otherwise, fail the way setgroups(2)
+ * would if we passed the larger groups array.
+ */
+ if (makespace) {
+ /*
+ * Find a slot that doesn't contain
+ * the primary group.
+ */
+ struct passwd *pwd;
+ gid_t pgid;
+ pwd = getpwuid(getuid());
+ if (pwd == NULL)
+ goto error;
+ pgid = pwd->pw_gid;
+ for (i = ngroupsmax - 1; i >= 0; i--)
+ if (groups[i] != pgid)
+ break;
+ if (i < 0)
+ goto error;
+ groups[i] = gid;
+ }
+ else {
+ error:
+ errno = EINVAL;
+ return ADDGRP_ESETGROUPS;
+ }
+ }
+ if (setgroups(ngroups, groups) < 0)
+ return ADDGRP_ESETGROUPS;
+ }
+ return ADDGRP_NOERROR;
+}
+
+static addgrp_ret_t
+addgrp(gid_t newgid, int makespace)
+{
+ int ngroups, ngroupsmax;
+ addgrp_ret_t rval;
+ gid_t *groups;
+ gid_t oldgid;
+
+ oldgid = getgid();
+ if (oldgid == newgid) /* nothing to do */
+ return ADDGRP_NOERROR;
+
+ rval = alloc_groups(&ngroups, &groups, &ngroupsmax);
+ if (rval != ADDGRP_NOERROR)
+ return rval;
+
+ /*
+ * BSD based systems normally have the egid in the supplemental
+ * group list.
+ */
+#if (defined(BSD) && BSD >= 199306)
+ /*
+ * According to POSIX/XPG6:
+ * On system where the egid is normally in the supplemental group list
+ * (or whenever the old egid actually is in the supplemental group
+ * list):
+ * o If the new egid is in the supplemental group list,
+ * just change the egid.
+ * o If the new egid is not in the supplemental group list,
+ * add the new egid to the list if there is room.
+ */
+
+ rval = addgid(groups, ngroups, ngroupsmax, newgid, makespace);
+#else
+ /*
+ * According to POSIX/XPG6:
+ * On systems where the egid is not normally in the supplemental group
+ * list (or whenever the old egid is not in the supplemental group
+ * list):
+ * o If the new egid is in the supplemental group list, delete
+ * it from the list.
+ * o If the old egid is not in the supplemental group list,
+ * add the old egid to the list if there is room.
+ */
+ {
+ int i;
+
+ /* search for new egid in supplemental group list */
+ for (i = 0; i < ngroups && groups[i] != newgid; i++)
+ continue;
+
+ /* remove new egid from supplemental group list */
+ if (i != ngroups)
+ for (--ngroups; i < ngroups; i++)
+ groups[i] = groups[i + 1];
+
+ rval = addgid(groups, ngroups, ngroupsmax, oldgid, makespace);
+ }
+#endif
+ free_groups(groups);
+ return rval;
+}
+
+/*
+ * If newgrp fails, it returns (gid_t)-1 and the errno variable is
+ * set to:
+ * [EINVAL] Unknown group.
+ * [EPERM] Bad password.
+ */
+static gid_t
+newgrp(const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt)
+{
+ struct group *grp;
+ char **ap;
+ char *p;
+ gid_t *groups;
+ int ngroups, ngroupsmax;
+
+ if (gname == NULL)
+ return pwd->pw_gid;
+
+ grp = getgrnam(gname);
+
+#ifdef GRUTIL_ACCEPT_GROUP_NUMBERS
+ if (grp == NULL) {
+ gid_t gid;
+ if (*gname != '-') {
+ gid = (gid_t)strtol(gname, &p, 10);
+ if (*p == '\0')
+ grp = getgrgid(gid);
+ }
+ }
+#endif
+ if (grp == NULL) {
+ errno = EINVAL;
+ return (gid_t)-1;
+ }
+
+ if (ruid == 0 || pwd->pw_gid == grp->gr_gid)
+ return grp->gr_gid;
+
+ if (alloc_groups(&ngroups, &groups, &ngroupsmax) == ADDGRP_NOERROR) {
+ int i;
+ for (i = 0; i < ngroups; i++)
+ if (groups[i] == grp->gr_gid) {
+ free_groups(groups);
+ return grp->gr_gid;
+ }
+ free_groups(groups);
+ }
+
+ /*
+ * Check the group membership list in case the groups[] array
+ * was maxed out or the user has been added to it since login.
+ */
+ for (ap = grp->gr_mem; *ap != NULL; ap++)
+ if (strcmp(*ap, pwd->pw_name) == 0)
+ return grp->gr_gid;
+
+ if (*grp->gr_passwd != '\0') {
+ p = getpass(prompt);
+ if (strcmp(grp->gr_passwd, crypt(p, grp->gr_passwd)) == 0) {
+ (void)memset(p, '\0', _PASSWORD_LEN);
+ return grp->gr_gid;
+ }
+ (void)memset(p, '\0', _PASSWORD_LEN);
+ }
+
+ errno = EPERM;
+ return (gid_t)-1;
+}
+
+#ifdef GRUTIL_SETGROUPS_MAKESPACE
+# define ADDGRP_MAKESPACE 1
+#else
+# define ADDGRP_MAKESPACE 0
+#endif
+
+#ifdef GRUTIL_ALLOW_GROUP_ERRORS
+# define maybe_exit(e)
+#else
+# define maybe_exit(e) exit(e);
+#endif
+
+void
+addgroup(
+#ifdef LOGIN_CAP
+ login_cap_t *lc,
+#endif
+ const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt)
+{
+ pwd->pw_gid = newgrp(gname, pwd, ruid, prompt);
+ if (pwd->pw_gid == (gid_t)-1) {
+ switch (errno) {
+ case EINVAL:
+ warnx("Unknown group `%s'", gname);
+ maybe_exit(EXIT_FAILURE);
+ break;
+ case EPERM: /* password failure */
+ warnx("Sorry");
+ maybe_exit(EXIT_FAILURE);
+ break;
+ default: /* XXX - should never happen */
+ err(EXIT_FAILURE, "unknown error");
+ break;
+ }
+ pwd->pw_gid = getgid();
+ }
+
+ switch (addgrp(pwd->pw_gid, ADDGRP_MAKESPACE)) {
+ case ADDGRP_NOERROR:
+ break;
+ case ADDGRP_EMALLOC:
+ err(EXIT_FAILURE, "malloc");
+ break;
+ case ADDGRP_EGETGROUPS:
+ err(EXIT_FAILURE, "getgroups");
+ break;
+ case ADDGRP_ESETGROUPS:
+ switch(errno) {
+ case EINVAL:
+ warnx("setgroups: ngroups > ngroupsmax");
+ maybe_exit(EXIT_FAILURE);
+ break;
+ case EPERM:
+ case EFAULT:
+ default:
+ warn("setgroups");
+ maybe_exit(EXIT_FAILURE);
+ break;
+ }
+ break;
+ }
+
+#ifdef LOGIN_CAP
+ if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGID) == -1)
+ err(EXIT_FAILURE, "setting user context");
+#else
+ if (setgid(pwd->pw_gid) == -1)
+ err(EXIT_FAILURE, "setgid");
+#endif
+}
diff --git a/usr.bin/newgrp/grutil.h b/usr.bin/newgrp/grutil.h
new file mode 100644
index 0000000..957f798
--- /dev/null
+++ b/usr.bin/newgrp/grutil.h
@@ -0,0 +1,40 @@
+/* $NetBSD: grutil.h,v 1.2 2008/04/28 20:24:14 martin Exp $ */
+
+/*-
+ * Copyright (c) 2007 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Brian Ginsbach.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _GRUTIL_H_
+#define _GRUTIL_H_
+
+void addgroup(
+#ifdef LOGIN_CAP
+ login_cap_t *,
+#endif
+ const char *, struct passwd *, uid_t, const char *);
+
+#endif /* _GRUTIL_H_ */
diff --git a/usr.bin/newgrp/newgrp.1 b/usr.bin/newgrp/newgrp.1
new file mode 100644
index 0000000..4bd4785
--- /dev/null
+++ b/usr.bin/newgrp/newgrp.1
@@ -0,0 +1,121 @@
+.\" $NetBSD: newgrp.1,v 1.5 2017/07/03 21:34:20 wiz Exp $
+.\"
+.\" Copyright (c) 2007, The NetBSD Foundation.
+.\" All Rights Reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Brian Ginsbach.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd June 6, 2007
+.Dt NEWGRP 1
+.Os
+.Sh NAME
+.Nm newgrp
+.Nd change to a new primary group
+.Sh SYNOPSIS
+.Nm
+.Op Fl l
+.Op Ar group
+.Sh DESCRIPTION
+The
+.Nm
+command changes a user to a new primary group
+.Pq real and effective group ID
+by starting a new shell.
+The user remains logged in and the current directory
+and file creation mask remain unchanged.
+The user is always given a new shell even if
+the primary group change fails.
+.Pp
+The
+.Nm
+command accepts the following options:
+.Bl -tag -width indent
+.It Fl l
+The environment is changed to what would be expected if the user
+actually logged in again.
+This simulates a full login.
+.El
+.Pp
+The
+.Ar group
+is a group name or non-negative numeric group ID from the group database.
+The real and effective group IDs are set to
+.Ar group
+or the group ID associated with the group name.
+.Pp
+If
+.Ar group
+is not specified,
+.Nm
+restores the user's real and effective group IDs to the user's
+primary group specified in the password database.
+The user's supplementary group IDs are restored to the set specified
+for the user in the group database.
+.Pp
+If the user is not a member of the specified group, and the group
+requires a password, the user will be prompted for the group password.
+.Sh FILES
+.Bl -tag -width /etc/master.passwd -compact
+.It Pa /etc/group
+The group database
+.It Pa /etc/master.passwd
+The user database
+.It Pa /etc/passwd
+A Version 7 format password file
+.El
+.Sh EXIT STATUS
+If a new shell is started the exit status is the exit status of the shell.
+Otherwise the exit status will be >0.
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr groups 1 ,
+.Xr login 1 ,
+.Xr sh 1 ,
+.Xr su 1 ,
+.Xr umask 2 ,
+.Xr group 5 ,
+.Xr passwd 5 ,
+.Xr environ 7
+.Sh STANDARDS
+The
+.Nm
+command conforms to
+.St -p1003.1-2001 .
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v6 .
+A
+.Nm
+command appeared in
+.Nx 5.0 .
+.Sh BUGS
+There is no convenient way to enter a password into
+.Pa /etc/group .
+The use of group passwords is strongly discouraged
+since they are inherently insecure.
+It is not possible to stop users from obtaining the encrypted
+password from the group database.
diff --git a/usr.bin/newgrp/newgrp.c b/usr.bin/newgrp/newgrp.c
new file mode 100644
index 0000000..61309c3
--- /dev/null
+++ b/usr.bin/newgrp/newgrp.c
@@ -0,0 +1,195 @@
+/*-
+ * Copyright (c) 2007 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Brian Ginsbach.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: newgrp.c,v 1.7 2011/09/16 15:39:27 joerg Exp $");
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/types.h>
+
+#include <err.h>
+#include <grp.h>
+#include <libgen.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef LOGIN_CAP
+#include <login_cap.h>
+#endif
+
+#include "grutil.h"
+
+__dead static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: %s [-l] [group]\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ extern char **environ;
+ struct passwd *pwd;
+ int c, lflag;
+ char *shell, sbuf[MAXPATHLEN + 2];
+ uid_t uid;
+#ifdef LOGIN_CAP
+ login_cap_t *lc;
+ u_int flags = LOGIN_SETUSER;
+#endif
+
+ uid = getuid();
+ pwd = getpwuid(uid);
+ if (pwd == NULL)
+ errx(EXIT_FAILURE, "who are you?");
+
+#ifdef LOGIN_CAP
+ if ((lc = login_getclass(pwd->pw_class)) == NULL)
+ errx(EXIT_FAILURE, "%s: unknown login class", pwd->pw_class);
+#endif
+
+ (void)setprogname(argv[0]);
+ lflag = 0;
+ while ((c = getopt(argc, argv, "-l")) != -1) {
+ switch (c) {
+ case '-':
+ case 'l':
+ if (lflag)
+ usage();
+ lflag = 1;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0) {
+#if 0
+ pwd->pw_gid = newgrp(*argv, pwd);
+ addgrp(pwd->pw_gid);
+ if (setgid(pwd->pw_gid) < 0)
+ err(1, "setgid");
+#endif
+#ifdef LOGIN_CAP
+ addgroup(lc, *argv, pwd, getuid(), "Password:");
+#else
+ addgroup(*argv, pwd, getuid(), "Password:");
+#endif
+ } else {
+#ifdef LOGIN_CAP
+ flags |= LOGIN_SETGROUP;
+#else
+ if (initgroups(pwd->pw_name, pwd->pw_gid) == -1)
+ err(EXIT_FAILURE, "initgroups");
+ if (setgid(pwd->pw_gid) == -1)
+ err(EXIT_FAILURE, "setgid");
+#endif
+ }
+
+#ifdef LOGIN_CAP
+ if (setusercontext(lc, pwd, uid, flags) == -1)
+ err(EXIT_FAILURE, "setusercontext");
+ if (!lflag)
+ login_close(lc);
+#else
+ if (setuid(pwd->pw_uid) == -1)
+ err(EXIT_FAILURE, "setuid");
+#endif
+
+ if (*pwd->pw_shell == '\0') {
+#ifdef TRUST_ENV_SHELL
+ shell = getenv("SHELL");
+ if (shell != NULL)
+ pwd->pw_shell = shell;
+ else
+#endif
+ pwd->pw_shell = __UNCONST(_PATH_BSHELL);
+ }
+
+ shell = pwd->pw_shell;
+
+ if (lflag) {
+ char *term;
+#ifdef KERBEROS
+ char *krbtkfile;
+#endif
+
+ if (chdir(pwd->pw_dir) == -1)
+ warn("%s", pwd->pw_dir);
+
+ term = getenv("TERM");
+#ifdef KERBEROS
+ krbtkfile = getenv("KRBTKFILE");
+#endif
+
+ /* create an empty environment */
+ if ((environ = malloc(sizeof(char *))) == NULL)
+ err(EXIT_FAILURE, NULL);
+ environ[0] = NULL;
+#ifdef LOGIN_CAP
+ if (setusercontext(lc, pwd, uid, LOGIN_SETENV | LOGIN_SETPATH) == -1)
+ err(EXIT_FAILURE, "setusercontext");
+ login_close(lc);
+#else
+ (void)setenv("PATH", _PATH_DEFPATH, 1);
+#endif
+ if (term != NULL)
+ (void)setenv("TERM", term, 1);
+#ifdef KERBEROS
+ if (krbtkfile != NULL)
+ (void)setenv("KRBTKFILE", krbtkfile, 1);
+#endif
+
+ (void)setenv("LOGNAME", pwd->pw_name, 1);
+ (void)setenv("USER", pwd->pw_name, 1);
+ (void)setenv("HOME", pwd->pw_dir, 1);
+ (void)setenv("SHELL", pwd->pw_shell, 1);
+
+ sbuf[0] = '-';
+ (void)strlcpy(sbuf + 1, basename(pwd->pw_shell),
+ sizeof(sbuf) - 1);
+ shell = sbuf;
+ }
+
+ (void)execl(pwd->pw_shell, shell, NULL);
+ err(EXIT_FAILURE, "%s", pwd->pw_shell);
+ /* NOTREACHED */
+}
diff --git a/usr.bin/nice/nice.1 b/usr.bin/nice/nice.1
new file mode 100644
index 0000000..87c211a
--- /dev/null
+++ b/usr.bin/nice/nice.1
@@ -0,0 +1,120 @@
+.\" $NetBSD: nice.1,v 1.15 2012/03/22 07:58:19 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)nice.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt NICE 1
+.Os
+.Sh NAME
+.Nm nice
+.Nd execute a utility with an altered scheduling priority
+.Sh SYNOPSIS
+.Nm
+.Op Fl n Ar increment
+.Ar utility
+.Op Ar argument ...
+.Sh DESCRIPTION
+.Nm
+runs
+.Ar utility
+at an altered scheduling priority.
+If an
+.Ar increment
+is given, it is used; otherwise
+an increment of 10 is assumed.
+The super-user can run utilities with priorities higher than normal by using
+a negative
+.Ar increment .
+The priority can be adjusted over a
+range of -20 (the highest) to 20 (the lowest).
+A priority of 19 or 20
+will prevent a process from taking any cycles from others at nice 0 or
+better.
+.Pp
+Available options:
+.Bl -tag -width indent
+.It Fl n Ar increment
+A positive or negative decimal integer used to modify the system scheduling
+priority of
+.Ar utility .
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Bl -tag -width indent
+.It 1-125
+An error occurred in the
+.Nm
+utility.
+.It 126
+The
+.Ar utility
+was found but could not be invoked.
+.It 127
+The
+.Ar utility
+could not be found.
+.El
+.Pp
+Otherwise, the exit status of
+.Nm
+will be that of
+.Ar utility .
+.Sh COMPATIBILITY
+The historic
+.Fl Ns Ar increment
+option has been deprecated but is still supported in this implementation.
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr getpriority 2 ,
+.Xr setpriority 2 ,
+.Xr renice 8
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v6 .
+.Sh BUGS
+.Nm
+is built into
+.Xr csh 1
+with a slightly different syntax than described here.
+The form
+.Ql nice +10
+nices to positive nice, and
+.Ql nice \-10
+can be used
+by the super-user to give a process more of the processor.
diff --git a/usr.bin/nice/nice.c b/usr.bin/nice/nice.c
new file mode 100644
index 0000000..de5fe5b
--- /dev/null
+++ b/usr.bin/nice/nice.c
@@ -0,0 +1,123 @@
+/* $NetBSD: nice.c,v 1.15 2008/07/21 14:19:24 lukem Exp $ */
+
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)nice.c 5.4 (Berkeley) 6/1/90";
+#endif
+__RCSID("$NetBSD: nice.c,v 1.15 2008/07/21 14:19:24 lukem Exp $");
+#endif /* not lint */
+
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <locale.h>
+#include <ctype.h>
+#include <errno.h>
+#include <err.h>
+#include <unistd.h>
+
+#define DEFNICE 10
+
+static void usage(void) __dead;
+
+int
+main(int argc, char **argv)
+{
+ char *ep;
+ int niceness = DEFNICE;
+ int c;
+ long tmp;
+
+ setprogname(argv[0]);
+ (void)setlocale(LC_ALL, "");
+
+ /* handle obsolete -number syntax */
+ if (argc > 1 && argv[1][0] == '-' &&
+ isdigit((unsigned char)argv[1][1])) {
+ niceness = atoi (argv[1] + 1);
+ argc--; argv++;
+ }
+
+ while ((c = getopt (argc, argv, "n:")) != -1) {
+ switch (c) {
+ case 'n':
+ errno = 0;
+ tmp = strtol(optarg, &ep, 10);
+ if (*ep != '\0' || tmp < INT_MIN || tmp > INT_MAX)
+ errx(EXIT_FAILURE, "invalid argument: `%s'",
+ optarg);
+ niceness = (int)tmp;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ usage();
+
+ errno = 0;
+ niceness += getpriority(PRIO_PROCESS, 0);
+ if (errno) {
+ err(EXIT_FAILURE, "getpriority");
+ /* NOTREACHED */
+ }
+ if (setpriority(PRIO_PROCESS, 0, niceness) == -1) {
+ warn("setpriority");
+ }
+
+ (void)execvp(argv[0], &argv[0]);
+ err((errno == ENOENT || errno == ENOTDIR) ? 127 : 126, "%s", argv[0]);
+ /* NOTREACHED */
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "Usage: %s [ -n increment ] utility [ argument ...]\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/nl/nl.1 b/usr.bin/nl/nl.1
new file mode 100644
index 0000000..d795390
--- /dev/null
+++ b/usr.bin/nl/nl.1
@@ -0,0 +1,214 @@
+.\" $NetBSD: nl.1,v 1.16 2013/09/24 22:08:06 wiz Exp $
+.\"
+.\" Copyright (c) 1999 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Klaus Klein.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd May 1, 2013
+.Dt NL 1
+.Os
+.Sh NAME
+.Nm nl
+.Nd line numbering filter
+.Sh SYNOPSIS
+.Nm
+.Op Fl p
+.Op Fl b Ar type
+.Op Fl d Ar delim
+.Op Fl f Ar type
+.Op Fl h Ar type
+.Op Fl i Ar incr
+.Op Fl l Ar num
+.Op Fl n Ar format
+.Op Fl s Ar sep
+.Op Fl v Ar startnum
+.Op Fl w Ar width
+.Op Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility reads lines from the named
+.Ar file ,
+applies a configurable line numbering filter operation,
+and writes the result to the standard output.
+If
+.Ar file
+is a single dash
+.Pq Sq \&-
+or absent,
+.Nm
+reads from the standard input.
+.Pp
+The
+.Nm
+utility treats the text it reads in terms of logical pages.
+Unless specified otherwise, line numbering is reset at the start of each
+logical page.
+A logical page consists of a header, a body and a footer section; empty
+sections are valid.
+Different line numbering options are independently available for header,
+body and footer sections.
+.Pp
+The starts of logical page sections are signaled by input lines containing
+nothing but one of the following sequences of delimiter characters:
+.Bl -column "\e:\e:\e: " "header " -offset indent
+.It Em "Line" Ta Em "Start of"
+.It \e:\e:\e: header
+.It \e:\e: body
+.It \e: footer
+.El
+.Pp
+If the input does not contain any logical page section signaling directives,
+the text being read is assumed to consist of a single logical page body.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl b Ar type
+Specify the logical page body lines to be numbered.
+Recognized
+.Ar type
+arguments are:
+.Bl -tag -width pstringXX
+.It a
+Number all lines.
+.It t
+Number only non-empty lines.
+.It n
+No line numbering.
+.It p Ns Ar expr
+Number only those lines that contain the basic regular expression specified
+by
+.Ar expr .
+.El
+.Pp
+The default
+.Ar type
+for logical page body lines is t.
+.It Fl d Ar delim
+Specify the delimiter characters used to indicate the start of a logical
+page section in the input file.
+At most two characters may be specified; if only one character is specified,
+the first character is replaced and the second character remains unchanged.
+The default
+.Ar delim
+characters are ``\e:''.
+.It Fl f Ar type
+Specify the same as
+.Fl b Ar type
+except for logical page footer lines.
+The default
+.Ar type
+for logical page footer lines is n.
+.It Fl h Ar type
+Specify the same as
+.Fl b Ar type
+except for logical page header lines.
+The default
+.Ar type
+for logical page header lines is n.
+.It Fl i Ar incr
+Specify the increment value used to number logical page lines.
+The default
+.Ar incr
+value is 1.
+.It Fl l Ar num
+If numbering of all lines is specified for the current logical section
+using the corresponding
+.Fl b
+a,
+.Fl f
+a
+or
+.Fl h
+a
+option,
+specify the number of adjacent blank lines to be considered as one.
+For example,
+.Fl l
+2 results in only the second adjacent blank line being numbered.
+The default
+.Ar num
+value is 1.
+.It Fl n Ar format
+Specify the line numbering output format.
+Recognized
+.Ar format
+arguments are:
+.Pp
+.Bl -tag -width lnXX -compact -offset indent
+.It ln
+Left justified.
+.It rn
+Right justified, leading zeros suppressed.
+.It rz
+Right justified, leading zeros kept.
+.El
+.Pp
+The default
+.Ar format
+is rn.
+.It Fl p
+Specify that line numbering should not be restarted at logical page delimiters.
+.It Fl s Ar sep
+Specify the characters used in separating the line number and the corresponding
+text line.
+The default
+.Ar sep
+setting is a single tab character.
+.It Fl v Ar startnum
+Specify the initial value used to number logical page lines; see also the
+description of the
+.Fl p
+option.
+The default
+.Ar startnum
+value is 1.
+.It Fl w Ar width
+Specify the number of characters to be occupied by the line number;
+if the
+.Ar width
+is insufficient to hold the line number, it will be truncated to its
+.Ar width
+least significant digits.
+The default
+.Ar width
+is 6.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr pr 1
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -xpg4.2
+specification.
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.At V.2 .
diff --git a/usr.bin/nl/nl.c b/usr.bin/nl/nl.c
new file mode 100644
index 0000000..14054ad
--- /dev/null
+++ b/usr.bin/nl/nl.c
@@ -0,0 +1,406 @@
+/* $NetBSD: nl.c,v 1.12 2013/09/17 20:00:50 wiz Exp $ */
+
+/*-
+ * Copyright (c) 1999 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Klaus Klein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1999\
+ The NetBSD Foundation, Inc. All rights reserved.");
+__RCSID("$NetBSD: nl.c,v 1.12 2013/09/17 20:00:50 wiz Exp $");
+#endif
+
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+
+typedef enum {
+ number_all, /* number all lines */
+ number_nonempty, /* number non-empty lines */
+ number_none, /* no line numbering */
+ number_regex /* number lines matching regular expression */
+} numbering_type;
+
+struct numbering_property {
+ const char * const name; /* for diagnostics */
+ numbering_type type; /* numbering type */
+ regex_t expr; /* for type == number_regex */
+};
+
+/* line numbering formats */
+#define FORMAT_LN "%-*d" /* left justified, leading zeros suppressed */
+#define FORMAT_RN "%*d" /* right justified, leading zeros suppressed */
+#define FORMAT_RZ "%0*d" /* right justified, leading zeros kept */
+
+#define FOOTER 0
+#define BODY 1
+#define HEADER 2
+#define NP_LAST HEADER
+
+static struct numbering_property numbering_properties[NP_LAST + 1] = {
+ { "footer", number_none, { 0, 0, 0, 0 } },
+ { "body", number_nonempty, { 0, 0, 0, 0 } },
+ { "header", number_none, { 0, 0, 0, 0 } },
+};
+
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+/*
+ * Maximum number of characters required for a decimal representation of a
+ * (signed) int; courtesy of tzcode.
+ */
+#define INT_STRLEN_MAXIMUM \
+ ((sizeof (int) * CHAR_BIT - 1) * 302 / 1000 + 2)
+
+static void filter(void);
+static void parse_numbering(const char *, int);
+static void usage(void) __attribute__((__noreturn__));
+
+/*
+ * Pointer to dynamically allocated input line buffer, and its size.
+ */
+static char *buffer;
+static size_t buffersize;
+
+/*
+ * Dynamically allocated buffer suitable for string representation of ints.
+ */
+static char *intbuffer;
+static size_t intbuffersize;
+
+/*
+ * Configurable parameters.
+ */
+/* delimiter characters that indicate the start of a logical page section */
+static char delim[2] = { '\\', ':' };
+
+/* line numbering format */
+static const char *format = FORMAT_RN;
+
+/* increment value used to number logical page lines */
+static int incr = 1;
+
+/* number of adjacent blank lines to be considered (and numbered) as one */
+static unsigned int nblank = 1;
+
+/* whether to restart numbering at logical page delimiters */
+static int restart = 1;
+
+/* characters used in separating the line number and the corrsp. text line */
+static const char *sep = "\t";
+
+/* initial value used to number logical page lines */
+static int startnum = 1;
+
+/* number of characters to be used for the line number */
+/* should be unsigned but required signed by `*' precision conversion */
+static int width = 6;
+
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ long val;
+ unsigned long uval;
+ char *ep;
+
+ (void)setlocale(LC_ALL, "");
+
+ /*
+ * Note: this implementation strictly conforms to the XBD Utility
+ * Syntax Guidelines and does not permit the optional `file' operand
+ * to be intermingled with the options, which is defined in the
+ * XCU specification (Issue 5) but declared an obsolescent feature that
+ * will be removed from a future issue. It shouldn't matter, though.
+ */
+ while ((c = getopt(argc, argv, "pb:d:f:h:i:l:n:s:v:w:")) != -1) {
+ switch (c) {
+ case 'p':
+ restart = 0;
+ break;
+ case 'b':
+ parse_numbering(optarg, BODY);
+ break;
+ case 'd':
+ if (optarg[0] != '\0')
+ delim[0] = optarg[0];
+ if (optarg[1] != '\0')
+ delim[1] = optarg[1];
+ /* at most two delimiter characters */
+ if (optarg[2] != '\0') {
+ errx(EXIT_FAILURE,
+ "invalid delim argument -- %s",
+ optarg);
+ /* NOTREACHED */
+ }
+ break;
+ case 'f':
+ parse_numbering(optarg, FOOTER);
+ break;
+ case 'h':
+ parse_numbering(optarg, HEADER);
+ break;
+ case 'i':
+ errno = 0;
+ val = strtol(optarg, &ep, 10);
+ if ((ep != NULL && *ep != '\0') ||
+ ((val == LONG_MIN || val == LONG_MAX) && errno != 0))
+ errx(EXIT_FAILURE,
+ "invalid incr argument -- %s", optarg);
+ incr = (int)val;
+ break;
+ case 'l':
+ errno = 0;
+ uval = strtoul(optarg, &ep, 10);
+ if ((ep != NULL && *ep != '\0') ||
+ (uval == ULONG_MAX && errno != 0))
+ errx(EXIT_FAILURE,
+ "invalid num argument -- %s", optarg);
+ nblank = (unsigned int)uval;
+ break;
+ case 'n':
+ if (strcmp(optarg, "ln") == 0) {
+ format = FORMAT_LN;
+ } else if (strcmp(optarg, "rn") == 0) {
+ format = FORMAT_RN;
+ } else if (strcmp(optarg, "rz") == 0) {
+ format = FORMAT_RZ;
+ } else
+ errx(EXIT_FAILURE,
+ "illegal format -- %s", optarg);
+ break;
+ case 's':
+ sep = optarg;
+ break;
+ case 'v':
+ errno = 0;
+ val = strtol(optarg, &ep, 10);
+ if ((ep != NULL && *ep != '\0') ||
+ ((val == LONG_MIN || val == LONG_MAX) && errno != 0))
+ errx(EXIT_FAILURE,
+ "invalid startnum value -- %s", optarg);
+ startnum = (int)val;
+ break;
+ case 'w':
+ errno = 0;
+ val = strtol(optarg, &ep, 10);
+ if ((ep != NULL && *ep != '\0') ||
+ ((val == LONG_MIN || val == LONG_MAX) && errno != 0))
+ errx(EXIT_FAILURE,
+ "invalid width value -- %s", optarg);
+ width = (int)val;
+ if (!(width > 0))
+ errx(EXIT_FAILURE,
+ "width argument must be > 0 -- %d",
+ width);
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ switch (argc) {
+ case 0:
+ break;
+ case 1:
+ if (strcmp(argv[0], "-") != 0 &&
+ freopen(argv[0], "r", stdin) == NULL)
+ err(EXIT_FAILURE, "Cannot open `%s'", argv[0]);
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+
+ /* Determine the maximum input line length to operate on. */
+ if ((val = sysconf(_SC_LINE_MAX)) == -1) /* ignore errno */
+ val = LINE_MAX;
+ /* Allocate sufficient buffer space (including the terminating NUL). */
+ buffersize = (size_t)val + 1;
+ if ((buffer = malloc(buffersize)) == NULL)
+ err(EXIT_FAILURE, "Cannot allocate input line buffer");
+
+ /* Allocate a buffer suitable for preformatting line number. */
+ intbuffersize = max((int)INT_STRLEN_MAXIMUM, width) + 1; /* NUL */
+ if ((intbuffer = malloc(intbuffersize)) == NULL)
+ err(EXIT_FAILURE, "cannot allocate preformatting buffer");
+
+ /* Do the work. */
+ filter();
+
+ return EXIT_SUCCESS;
+ /* NOTREACHED */
+}
+
+static void
+filter(void)
+{
+ int line; /* logical line number */
+ int section; /* logical page section */
+ unsigned int adjblank; /* adjacent blank lines */
+ int consumed; /* intbuffer measurement */
+ int donumber, idx;
+
+ adjblank = 0;
+ line = startnum;
+ section = BODY;
+#ifdef __GNUC__
+ donumber = 0; /* avoid bogus `uninitialized' warning */
+#endif
+
+ while (fgets(buffer, (int)buffersize, stdin) != NULL) {
+ for (idx = FOOTER; idx <= NP_LAST; idx++) {
+ /* Does it look like a delimiter? */
+ if (buffer[2 * idx + 0] == delim[0] &&
+ buffer[2 * idx + 1] == delim[1]) {
+ /* Was this the whole line? */
+ if (buffer[2 * idx + 2] == '\n') {
+ section = idx;
+ adjblank = 0;
+ if (restart)
+ line = startnum;
+ goto nextline;
+ }
+ } else {
+ break;
+ }
+ }
+
+ switch (numbering_properties[section].type) {
+ case number_all:
+ /*
+ * Doing this for number_all only is disputable, but
+ * the standard expresses an explicit dependency on
+ * `-b a' etc.
+ */
+ if (buffer[0] == '\n' && ++adjblank < nblank)
+ donumber = 0;
+ else
+ donumber = 1, adjblank = 0;
+ break;
+ case number_nonempty:
+ donumber = (buffer[0] != '\n');
+ break;
+ case number_none:
+ donumber = 0;
+ break;
+ case number_regex:
+ donumber =
+ (regexec(&numbering_properties[section].expr,
+ buffer, 0, NULL, 0) == 0);
+ break;
+ }
+
+ if (donumber) {
+ consumed = snprintf(intbuffer, intbuffersize, format,
+ width, line);
+ (void)printf("%s",
+ intbuffer + max(0, consumed - width));
+ line += incr;
+ } else {
+ (void)printf("%*s", width, "");
+ }
+ (void)printf("%s%s", sep, buffer);
+
+ if (ferror(stdout))
+ err(EXIT_FAILURE, "output error");
+nextline:
+ ;
+ }
+
+ if (ferror(stdin))
+ err(EXIT_FAILURE, "input error");
+}
+
+/*
+ * Various support functions.
+ */
+
+static void
+parse_numbering(const char *argstr, int section)
+{
+ int error;
+ char errorbuf[NL_TEXTMAX];
+
+ switch (argstr[0]) {
+ case 'a':
+ numbering_properties[section].type = number_all;
+ break;
+ case 'n':
+ numbering_properties[section].type = number_none;
+ break;
+ case 't':
+ numbering_properties[section].type = number_nonempty;
+ break;
+ case 'p':
+ /* If there was a previous expression, throw it away. */
+ if (numbering_properties[section].type == number_regex)
+ regfree(&numbering_properties[section].expr);
+ else
+ numbering_properties[section].type = number_regex;
+
+ /* Compile/validate the supplied regular expression. */
+ if ((error = regcomp(&numbering_properties[section].expr,
+ &argstr[1], REG_NEWLINE|REG_NOSUB)) != 0) {
+ (void)regerror(error,
+ &numbering_properties[section].expr,
+ errorbuf, sizeof (errorbuf));
+ errx(EXIT_FAILURE,
+ "%s expr: %s -- %s",
+ numbering_properties[section].name, errorbuf,
+ &argstr[1]);
+ }
+ break;
+ default:
+ errx(EXIT_FAILURE,
+ "illegal %s line numbering type -- %s",
+ numbering_properties[section].name, argstr);
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s [-p] [-b type] [-d delim] [-f type] "
+ "[-h type] [-i incr] [-l num]\n\t[-n format] [-s sep] "
+ "[-v startnum] [-w width] [file]\n", getprogname());
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/nohup/nohup.1 b/usr.bin/nohup/nohup.1
new file mode 100644
index 0000000..71a76f1
--- /dev/null
+++ b/usr.bin/nohup/nohup.1
@@ -0,0 +1,115 @@
+.\" $NetBSD: nohup.1,v 1.15 2012/03/29 18:33:19 wiz Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)nohup.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd July 15, 2005
+.Dt NOHUP 1
+.Os
+.Sh NAME
+.Nm nohup
+.Nd invoke a command immune to hangups
+.Sh SYNOPSIS
+.Nm
+.Ar utility
+.Op Ar arg ...
+.Sh DESCRIPTION
+The
+.Nm
+command allows the specified utility to be protected from termination
+if the user should become logged out
+(for example, due to a modem line or TCP/IP connection being dropped).
+To do this,
+.Nm
+sets the
+.Dv SIGHUP
+.Xr signal 3
+.Pq Dq terminal line hangup
+to be ignored,
+then executes
+.Ar utility
+along with any arguments.
+.Pp
+If the standard output is a terminal, the standard output is
+appended to the file
+.Pa nohup.out
+in the current directory.
+If standard error is a terminal, it is directed to the same place
+as the standard output.
+If the output file
+.Pa nohup.out
+cannot be created in the current directory,
+.Nm
+attempts to create the file in the user's home directory.
+If the file
+.Pa nohup.out
+cannot be created,
+either in the current directory or the user's home directory,
+.Nm
+will exit without invoking
+.Ar utility ,
+with an exit value as described below.
+.Sh ENVIRONMENT
+The following variable is used by
+.Nm :
+.Bl -tag -width flag
+.It Ev HOME
+User's home directory.
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Bl -tag -width Ds
+.It 126
+The
+.Ar utility
+was found but could not be invoked.
+.It 127
+The
+.Ar utility
+could not be found or an error occurred in
+.Nm .
+.El
+.Pp
+Otherwise, the exit status of
+.Nm
+will be that of
+.Ar utility .
+.Sh SEE ALSO
+.Xr signal 3
+.Sh STANDARDS
+The
+.Nm
+command is expected to be
+.St -p1003.2
+compatible.
diff --git a/usr.bin/nohup/nohup.c b/usr.bin/nohup/nohup.c
new file mode 100644
index 0000000..d177c51
--- /dev/null
+++ b/usr.bin/nohup/nohup.c
@@ -0,0 +1,142 @@
+/* $NetBSD: nohup.c,v 1.15 2011/09/06 18:24:15 joerg Exp $ */
+
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)nohup.c 5.4 (Berkeley) 6/1/90";
+#endif
+__RCSID("$NetBSD: nohup.c,v 1.15 2011/09/06 18:24:15 joerg Exp $");
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+static void dofile(void);
+__dead static void usage(void);
+
+/* nohup shall exit with one of the following values:
+ 126 - The utility was found but could not be invoked.
+ 127 - An error occurred in the nohup utility, or the utility could
+ not be found. */
+#define EXIT_NOEXEC 126
+#define EXIT_NOTFOUND 127
+#define EXIT_MISC 127
+
+int
+main(int argc, char **argv)
+{
+ int exit_status;
+
+ while (getopt(argc, argv, "") != -1) {
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ if (isatty(STDOUT_FILENO))
+ dofile();
+ if (isatty(STDERR_FILENO) && dup2(STDOUT_FILENO, STDERR_FILENO) == -1) {
+ /* may have just closed stderr */
+ (void)fprintf(stdin, "nohup: %s\n", strerror(errno));
+ exit(EXIT_MISC);
+ }
+
+ /* The nohup utility shall take the standard action for all signals
+ except that SIGHUP shall be ignored. */
+ (void)signal(SIGHUP, SIG_IGN);
+
+ execvp(argv[0], &argv[0]);
+ exit_status = (errno == ENOENT) ? EXIT_NOTFOUND : EXIT_NOEXEC;
+ (void)fprintf(stderr, "nohup: %s: %s\n", argv[0], strerror(errno));
+ exit(exit_status);
+}
+
+static void
+dofile(void)
+{
+ int fd;
+ char path[MAXPATHLEN];
+ const char *p;
+
+ /* If the standard output is a terminal, all output written to
+ its standard output shall be appended to the end of the file
+ nohup.out in the current directory. If nohup.out cannot be
+ created or opened for appending, the output shall be appended
+ to the end of the file nohup.out in the directory specified
+ by the HOME environment variable.
+
+ If a file is created, the file's permission bits shall be
+ set to S_IRUSR | S_IWUSR. */
+#define FILENAME "nohup.out"
+ p = FILENAME;
+ if ((fd = open(p, O_RDWR|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR)) >= 0)
+ goto dupit;
+ if ((p = getenv("HOME")) != NULL) {
+ (void)strlcpy(path, p, sizeof(path));
+ (void)strlcat(path, "/", sizeof(path));
+ (void)strlcat(path, FILENAME, sizeof(path));
+ if ((fd = open(p = path, O_RDWR|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR)) >= 0)
+ goto dupit;
+ }
+ (void)fprintf(stderr, "nohup: can't open a nohup.out file.\n");
+ exit(EXIT_MISC);
+
+dupit: (void)lseek(fd, 0L, SEEK_END);
+ if (dup2(fd, STDOUT_FILENO) == -1) {
+ (void)fprintf(stderr, "nohup: %s\n", strerror(errno));
+ exit(EXIT_MISC);
+ }
+ (void)fprintf(stderr, "sending output to %s\n", p);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: nohup utility [argument ...]\n");
+ exit(EXIT_MISC);
+}
diff --git a/usr.bin/paste/paste.1 b/usr.bin/paste/paste.1
new file mode 100644
index 0000000..d76ce5d
--- /dev/null
+++ b/usr.bin/paste/paste.1
@@ -0,0 +1,116 @@
+.\" $NetBSD: paste.1,v 1.9 2013/04/07 17:43:01 wiz Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Adam S. Moskowitz and the Institute of Electrical and Electronics
+.\" Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)paste.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt PASTE 1
+.Os
+.Sh NAME
+.Nm paste
+.Nd merge corresponding or subsequent lines of files
+.Sh SYNOPSIS
+.Nm
+.Op Fl s
+.Op Fl d Ar list
+.Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility concatenates the corresponding lines of the given input files,
+replacing all but the last file's newline characters with a single tab
+character, and writes the resulting lines to standard output.
+If end-of-file is reached on an input file while other input files
+still contain data, the file is treated as if it were an endless source
+of empty lines.
+.Pp
+The options are as follows:
+.Bl -tag -width Fl
+.It Fl d Ar list
+Use one or more of the provided characters to replace the newline
+characters instead of the default tab.
+The characters in
+.Ar list
+are used circularly, i.e., when
+.Ar list
+is exhausted the first character from
+.Ar list
+is reused.
+This continues until a line from the last input file (in default operation)
+or the last line in each file (using the -s option) is displayed, at which
+time
+.Nm
+begins selecting characters from the beginning of
+.Ar list
+again.
+.Pp
+The following special characters can also be used in list:
+.Pp
+.Bl -tag -width flag -compact
+.It Li \en
+newline character
+.It Li \et
+tab character
+.It Li \e\e
+backslash character
+.It Li \e0
+Empty string (not a null character).
+.El
+.Pp
+Any other character preceded by a backslash is equivalent to the
+character itself.
+.It Fl s
+Concatenate all of the lines of each separate input file in command line
+order.
+The newline character of every line except the last line in each input
+file is replaced with the tab character, unless otherwise specified by
+the -d option.
+.El
+.Pp
+If
+.Ql Fl
+is specified for one or more of the input files, the standard
+input is used; standard input is read one line at a time, circularly,
+for each instance of
+.Ql Fl .
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr cut 1 ,
+.Xr join 1
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
diff --git a/usr.bin/paste/paste.c b/usr.bin/paste/paste.c
new file mode 100644
index 0000000..5698131
--- /dev/null
+++ b/usr.bin/paste/paste.c
@@ -0,0 +1,233 @@
+/* $NetBSD: paste.c,v 1.16 2011/09/06 18:24:43 joerg Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam S. Moskowitz of Menlo Consulting.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+/*static char sccsid[] = "from: @(#)paste.c 8.1 (Berkeley) 6/6/93";*/
+__RCSID("$NetBSD: paste.c,v 1.16 2011/09/06 18:24:43 joerg Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void parallel(int, char **);
+static void sequential(char **);
+static int tr(char *);
+__dead static void usage(void);
+
+static char dflt_delim[] = "\t";
+static char *delim = dflt_delim;
+static int delimcnt = 1;
+
+int
+main(int argc, char **argv)
+{
+ int ch, seq;
+
+ seq = 0;
+ while ((ch = getopt(argc, argv, "d:s")) != -1) {
+ switch (ch) {
+ case 'd':
+ delim = strdup(optarg);
+ delimcnt = tr(delim);
+ break;
+ case 's':
+ seq = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (seq)
+ sequential(argv);
+ else
+ parallel(argc, argv);
+ exit(0);
+}
+
+static void
+parallel(int argc, char **argv)
+{
+ char ch, *dp, *line;
+ FILE **fpp, *fp;
+ size_t line_len;
+ int cnt, output;
+
+ fpp = calloc(argc, sizeof *fpp);
+ if (fpp == NULL)
+ err(1, "calloc");
+
+ for (cnt = 0; cnt < argc; cnt++) {
+ if (strcmp(argv[cnt], "-") == 0)
+ fpp[cnt] = stdin;
+ else if (!(fpp[cnt] = fopen(argv[cnt], "r")))
+ err(1, "%s", argv[cnt]);
+ }
+
+ for (;;) {
+ /* Start with the NUL at the end of 'delim' ... */
+ dp = delim + delimcnt;
+ output = 0;
+ for (cnt = 0; cnt < argc; cnt++) {
+ fp = fpp[cnt];
+ if (fp == NULL)
+ continue;
+ line = fgetln(fp, &line_len);
+ if (line == NULL) {
+ /* Assume EOF */
+ if (fp != stdin)
+ fclose(fp);
+ fpp[cnt] = NULL;
+ continue;
+ }
+ /* Output enough separators to catch up */
+ do {
+ ch = *dp++;
+ if (ch)
+ putchar(ch);
+ if (dp >= delim + delimcnt)
+ dp = delim;
+ } while (++output <= cnt);
+ /* Remove any trailing newline - check for last line */
+ if (line[line_len - 1] == '\n')
+ line_len--;
+ printf("%.*s", (int)line_len, line);
+ }
+
+ if (!output)
+ break;
+
+ /* Add separators to end of line */
+ while (++output <= cnt) {
+ ch = *dp++;
+ if (ch)
+ putchar(ch);
+ if (dp >= delim + delimcnt)
+ dp = delim;
+ }
+ putchar('\n');
+ }
+
+ free(fpp);
+}
+
+static void
+sequential(char **argv)
+{
+ FILE *fp;
+ int cnt;
+ char ch, *p, *dp;
+ char buf[_POSIX2_LINE_MAX + 1];
+
+ for (; (p = *argv) != NULL; ++argv) {
+ if (p[0] == '-' && !p[1])
+ fp = stdin;
+ else if (!(fp = fopen(p, "r"))) {
+ warn("%s", p);
+ continue;
+ }
+ if (fgets(buf, sizeof(buf), fp)) {
+ for (cnt = 0, dp = delim;;) {
+ if (!(p = strchr(buf, '\n')))
+ err(1, "%s: input line too long.",
+ *argv);
+ *p = '\0';
+ (void)printf("%s", buf);
+ if (!fgets(buf, sizeof(buf), fp))
+ break;
+ if ((ch = *dp++) != 0)
+ putchar(ch);
+ if (++cnt == delimcnt) {
+ dp = delim;
+ cnt = 0;
+ }
+ }
+ putchar('\n');
+ }
+ if (fp != stdin)
+ (void)fclose(fp);
+ }
+}
+
+static int
+tr(char *arg)
+{
+ int cnt;
+ char ch, *p;
+
+ for (p = arg, cnt = 0; (ch = *p++); ++arg, ++cnt)
+ if (ch == '\\')
+ switch(ch = *p++) {
+ case 'n':
+ *arg = '\n';
+ break;
+ case 't':
+ *arg = '\t';
+ break;
+ case '0':
+ *arg = '\0';
+ break;
+ default:
+ *arg = ch;
+ break;
+ } else
+ *arg = ch;
+
+ if (!cnt)
+ errx(1, "no delimiters specified.");
+ *arg = '\0';
+ return(cnt);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "paste: [-s] [-d delimiters] file ...\n");
+ exit(1);
+}
diff --git a/usr.bin/patch/backupfile.c b/usr.bin/patch/backupfile.c
new file mode 100644
index 0000000..25d6076
--- /dev/null
+++ b/usr.bin/patch/backupfile.c
@@ -0,0 +1,254 @@
+/*
+ * $OpenBSD: backupfile.c,v 1.19 2006/03/11 19:41:30 otto Exp $
+ * $DragonFly: src/usr.bin/patch/backupfile.c,v 1.5 2008/08/11 00:05:06 joerg Exp $
+ * $NetBSD: backupfile.c,v 1.15 2014/04/11 17:30:03 christos Exp $
+ */
+
+/*
+ * backupfile.c -- make Emacs style backup file names
+ *
+ * Copyright (C) 1990 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * without restriction.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+/*
+ * David MacKenzie <djm@ai.mit.edu>. Some algorithms adapted from GNU Emacs.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: backupfile.c,v 1.15 2014/04/11 17:30:03 christos Exp $");
+
+#include <ctype.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "backupfile.h"
+
+
+#define ISDIGIT(c) (isascii ((unsigned char)c) && isdigit ((unsigned char)c))
+
+/* Which type of backup file names are generated. */
+enum backup_type backup_type = none;
+
+/*
+ * The extension added to file names to produce a simple (as opposed to
+ * numbered) backup file name.
+ */
+const char *simple_backup_suffix = "~";
+
+static char *concat(const char *, const char *);
+static char *make_version_name(const char *, int);
+static int max_backup_version(const char *, const char *);
+static int version_number(const char *, const char *, size_t);
+static int argmatch(const char *, const char **);
+static void invalid_arg(const char *, const char *, int);
+
+/*
+ * Return the name of the new backup file for file FILE, allocated with
+ * malloc. Return 0 if out of memory. FILE must not end with a '/' unless it
+ * is the root directory. Do not call this function if backup_type == none.
+ */
+char *
+find_backup_file_name(const char *file)
+{
+ char *dir, *base_versions, *tmp_file;
+ int highest_backup;
+
+ if (backup_type == simple)
+ return concat(file, simple_backup_suffix);
+ tmp_file = strdup(file);
+ if (tmp_file == NULL)
+ return NULL;
+ base_versions = concat(basename(tmp_file), ".~");
+ free(tmp_file);
+ if (base_versions == NULL)
+ return NULL;
+ tmp_file = strdup(file);
+ if (tmp_file == NULL) {
+ free(base_versions);
+ return NULL;
+ }
+ dir = dirname(tmp_file);
+ if (dir == NULL) {
+ free(base_versions);
+ free(tmp_file);
+ return NULL;
+ }
+ highest_backup = max_backup_version(base_versions, dir);
+ free(base_versions);
+ free(tmp_file);
+ if (backup_type == numbered_existing && highest_backup == 0)
+ return concat(file, simple_backup_suffix);
+ return make_version_name(file, highest_backup + 1);
+}
+
+/*
+ * Return the number of the highest-numbered backup file for file FILE in
+ * directory DIR. If there are no numbered backups of FILE in DIR, or an
+ * error occurs reading DIR, return 0. FILE should already have ".~" appended
+ * to it.
+ */
+static int
+max_backup_version(const char *file, const char *dir)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ int highest_version, this_version;
+ size_t file_name_length;
+
+ dirp = opendir(dir);
+ if (dirp == NULL)
+ return 0;
+
+ highest_version = 0;
+ file_name_length = strlen(file);
+
+ while ((dp = readdir(dirp)) != NULL) {
+ if (dp->d_namlen <= file_name_length)
+ continue;
+
+ this_version = version_number(file, dp->d_name, file_name_length);
+ if (this_version > highest_version)
+ highest_version = this_version;
+ }
+ closedir(dirp);
+ return highest_version;
+}
+
+/*
+ * Return a string, allocated with malloc, containing "FILE.~VERSION~".
+ * Return 0 if out of memory.
+ */
+static char *
+make_version_name(const char *file, int version)
+{
+ char *backup_name;
+
+ if (asprintf(&backup_name, "%s.~%d~", file, version) == -1)
+ return NULL;
+ return backup_name;
+}
+
+/*
+ * If BACKUP is a numbered backup of BASE, return its version number;
+ * otherwise return 0. BASE_LENGTH is the length of BASE. BASE should
+ * already have ".~" appended to it.
+ */
+static int
+version_number(const char *base, const char *backup, size_t base_length)
+{
+ int version;
+ const char *p;
+
+ version = 0;
+ if (!strncmp(base, backup, base_length) && ISDIGIT(backup[base_length])) {
+ for (p = &backup[base_length]; ISDIGIT(*p); ++p)
+ version = version * 10 + *p - '0';
+ if (p[0] != '~' || p[1])
+ version = 0;
+ }
+ return version;
+}
+
+/*
+ * Return the newly-allocated concatenation of STR1 and STR2. If out of
+ * memory, return 0.
+ */
+static char *
+concat(const char *str1, const char *str2)
+{
+ char *newstr;
+
+ if (asprintf(&newstr, "%s%s", str1, str2) == -1)
+ return NULL;
+ return newstr;
+}
+
+/*
+ * If ARG is an unambiguous match for an element of the null-terminated array
+ * OPTLIST, return the index in OPTLIST of the matched element, else -1 if it
+ * does not match any element or -2 if it is ambiguous (is a prefix of more
+ * than one element).
+ */
+static int
+argmatch(const char *arg, const char **optlist)
+{
+ int i; /* Temporary index in OPTLIST. */
+ size_t arglen; /* Length of ARG. */
+ int matchind = -1; /* Index of first nonexact match. */
+ int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */
+
+ arglen = strlen(arg);
+
+ /* Test all elements for either exact match or abbreviated matches. */
+ for (i = 0; optlist[i]; i++) {
+ if (!strncmp(optlist[i], arg, arglen)) {
+ if (strlen(optlist[i]) == arglen)
+ /* Exact match found. */
+ return i;
+ else if (matchind == -1)
+ /* First nonexact match found. */
+ matchind = i;
+ else
+ /* Second nonexact match found. */
+ ambiguous = 1;
+ }
+ }
+ if (ambiguous)
+ return -2;
+ else
+ return matchind;
+}
+
+/*
+ * Error reporting for argmatch. KIND is a description of the type of entity
+ * that was being matched. VALUE is the invalid value that was given. PROBLEM
+ * is the return value from argmatch.
+ */
+static void
+invalid_arg(const char *kind, const char *value, int problem)
+{
+ fprintf(stderr, "patch: ");
+ if (problem == -1)
+ fprintf(stderr, "invalid");
+ else /* Assume -2. */
+ fprintf(stderr, "ambiguous");
+ fprintf(stderr, " %s `%s'\n", kind, value);
+}
+
+static const char *backup_args[] = {
+ "none", "never", "simple", "nil", "existing", "t", "numbered", 0
+};
+
+static enum backup_type backup_types[] = {
+ none, simple, simple, numbered_existing,
+ numbered_existing, numbered, numbered
+};
+
+/*
+ * Return the type of backup indicated by VERSION. Unique abbreviations are
+ * accepted.
+ */
+enum backup_type
+get_version(const char *version)
+{
+ int i;
+
+ if (version == NULL || *version == '\0')
+ return numbered_existing;
+ i = argmatch(version, backup_args);
+ if (i >= 0)
+ return backup_types[i];
+ invalid_arg("version control type", version, i);
+ exit(2);
+}
diff --git a/usr.bin/patch/backupfile.h b/usr.bin/patch/backupfile.h
new file mode 100644
index 0000000..d146c3a
--- /dev/null
+++ b/usr.bin/patch/backupfile.h
@@ -0,0 +1,42 @@
+/*
+ * $OpenBSD: backupfile.h,v 1.6 2003/07/28 18:35:36 otto Exp $
+ * $DragonFly: src/usr.bin/patch/backupfile.h,v 1.3 2007/09/29 23:11:10 swildner Exp $
+ * $NetBSD: backupfile.h,v 1.6 2008/09/19 18:33:34 joerg Exp $
+ */
+
+/*
+ * backupfile.h -- declarations for making Emacs style backup file names
+ * Copyright (C) 1990 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * without restriction.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+/* When to make backup files. */
+enum backup_type {
+ /* Never make backups. */
+ none,
+
+ /* Make simple backups of every file. */
+ simple,
+
+ /*
+ * Make numbered backups of files that already have numbered backups,
+ * and simple backups of the others.
+ */
+ numbered_existing,
+
+ /* Make numbered backups of every file. */
+ numbered
+};
+
+extern enum backup_type backup_type;
+extern const char *simple_backup_suffix;
+
+char *find_backup_file_name(const char *file);
+enum backup_type get_version(const char *version);
diff --git a/usr.bin/patch/common.h b/usr.bin/patch/common.h
new file mode 100644
index 0000000..c19d43b
--- /dev/null
+++ b/usr.bin/patch/common.h
@@ -0,0 +1,123 @@
+/*
+ * $OpenBSD: common.h,v 1.26 2006/03/11 19:41:30 otto Exp $
+ * $DragonFly: src/usr.bin/patch/common.h,v 1.5 2008/08/10 23:50:12 joerg Exp $
+ * $NetBSD: common.h,v 1.21 2015/07/24 18:56:44 christos Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+#include <sys/types.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#define DEBUGGING
+
+/* constants */
+
+#define MAXHUNKSIZE 100000 /* is this enough lines? */
+#define INITHUNKMAX 125 /* initial dynamic allocation size */
+#define MAXLINELEN 8192
+#define BUFFERSIZE 1024
+#define LINENUM_MAX LONG_MAX
+
+#define SCCSPREFIX "s."
+#define GET "get -e %s"
+#define SCCSDIFF "get -p %s | diff - %s >/dev/null"
+
+#define RCSSUFFIX ",v"
+#define CHECKOUT "/usr/bin/co"
+#define RCSDIFF "/usr/bin/rcsdiff"
+
+#define ORIGEXT ".orig"
+#define REJEXT ".rej"
+
+/* handy definitions */
+
+#define strNE(s1,s2) (strcmp(s1, s2))
+#define strEQ(s1,s2) (!strcmp(s1, s2))
+#define strnNE(s1,s2,l) (strncmp(s1, s2, l))
+#define strnEQ(s1,s2,l) (!strncmp(s1, s2, l))
+
+/* typedefs */
+
+typedef long LINENUM; /* must be signed */
+
+/* globals */
+
+extern mode_t filemode;
+
+extern char buf[MAXLINELEN];/* general purpose buffer */
+extern size_t buf_len;
+
+extern bool using_plan_a; /* try to keep everything in memory */
+extern bool out_of_mem; /* ran out of memory in plan a */
+
+#define MAXFILEC 2
+
+extern char *filearg[MAXFILEC];
+extern bool ok_to_create_file;
+extern char *outname;
+extern char *origprae;
+
+extern char *TMPOUTNAME;
+extern char *TMPINNAME;
+extern char *TMPREJNAME;
+extern char *TMPPATNAME;
+extern bool toutkeep;
+extern bool trejkeep;
+
+#ifdef DEBUGGING
+extern int debug;
+#endif
+
+extern bool force;
+extern bool batch;
+extern bool verbose;
+extern bool reverse;
+extern bool noreverse;
+extern bool skip_rest_of_patch;
+extern int strippath;
+extern bool canonicalize;
+/* TRUE if -C was specified on command line. */
+extern bool check_only;
+extern bool warn_on_invalid_line;
+extern bool last_line_missing_eol;
+
+
+#define CONTEXT_DIFF 1
+#define NORMAL_DIFF 2
+#define ED_DIFF 3
+#define NEW_CONTEXT_DIFF 4
+#define UNI_DIFF 5
+
+extern int diff_type;
+extern char *revision; /* prerequisite revision, if any */
+extern LINENUM input_lines; /* how long is input file in lines */
+
+extern int posix;
+
diff --git a/usr.bin/patch/inp.c b/usr.bin/patch/inp.c
new file mode 100644
index 0000000..b434a88
--- /dev/null
+++ b/usr.bin/patch/inp.c
@@ -0,0 +1,516 @@
+/*
+ * $OpenBSD: inp.c,v 1.34 2006/03/11 19:41:30 otto Exp $
+ * $DragonFly: src/usr.bin/patch/inp.c,v 1.6 2007/09/29 23:11:10 swildner Exp $
+ * $NetBSD: inp.c,v 1.26 2018/06/18 18:33:31 christos Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: inp.c,v 1.26 2018/06/18 18:33:31 christos Exp $");
+
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "util.h"
+#include "pch.h"
+#include "inp.h"
+
+
+/* Input-file-with-indexable-lines abstract type */
+
+static off_t i_size; /* size of the input file */
+static char *i_womp; /* plan a buffer for entire file */
+static char **i_ptr; /* pointers to lines in i_womp */
+static char empty_line[] = { '\0' };
+
+static int tifd = -1; /* plan b virtual string array */
+static char *tibuf[2]; /* plan b buffers */
+static LINENUM tiline[2] = {-1, -1}; /* 1st line in each buffer */
+static LINENUM lines_per_buf; /* how many lines per buffer */
+static int tireclen; /* length of records in tmp file */
+
+static bool rev_in_string(const char *);
+static bool reallocate_lines(size_t *);
+
+/* returns false if insufficient memory */
+static bool plan_a(const char *);
+
+static void plan_b(const char *);
+
+/* New patch--prepare to edit another file. */
+
+void
+re_input(void)
+{
+ if (using_plan_a) {
+ i_size = 0;
+ free(i_ptr);
+ i_ptr = NULL;
+ if (i_womp != NULL) {
+ munmap(i_womp, i_size);
+ i_womp = NULL;
+ }
+ } else {
+ using_plan_a = true; /* maybe the next one is smaller */
+ close(tifd);
+ tifd = -1;
+ free(tibuf[0]);
+ free(tibuf[1]);
+ tibuf[0] = tibuf[1] = NULL;
+ tiline[0] = tiline[1] = -1;
+ tireclen = 0;
+ }
+}
+
+/* Construct the line index, somehow or other. */
+
+void
+scan_input(const char *filename)
+{
+ if (!plan_a(filename))
+ plan_b(filename);
+ if (verbose) {
+ say("Patching file %s using Plan %s...\n", filename,
+ (using_plan_a ? "A" : "B"));
+ }
+}
+
+static bool
+reallocate_lines(size_t *lines_allocated)
+{
+ char **p;
+ size_t new_size;
+
+ new_size = *lines_allocated * 3 / 2;
+ p = pch_realloc(i_ptr, new_size + 2, sizeof(char *));
+ if (p == NULL) { /* shucks, it was a near thing */
+ munmap(i_womp, i_size);
+ i_womp = NULL;
+ free(i_ptr);
+ i_ptr = NULL;
+ *lines_allocated = 0;
+ return false;
+ }
+ *lines_allocated = new_size;
+ i_ptr = p;
+ return true;
+}
+
+/* Try keeping everything in memory. */
+
+static bool
+plan_a(const char *filename)
+{
+ int ifd, statfailed, devnull, pstat;
+ char *p, *s, lbuf[MAXLINELEN];
+ struct stat filestat;
+ off_t i;
+ ptrdiff_t sz;
+ size_t iline, lines_allocated;
+ pid_t pid;
+ char *argp[4] = {NULL};
+
+#ifdef DEBUGGING
+ if (debug & 8)
+ return false;
+#endif
+
+ if (filename == NULL || *filename == '\0')
+ return false;
+
+ statfailed = stat(filename, &filestat);
+ if (statfailed && ok_to_create_file) {
+ if (verbose)
+ say("(Creating file %s...)\n", filename);
+
+ /*
+ * in check_patch case, we still display `Creating file' even
+ * though we're not. The rule is that -C should be as similar
+ * to normal patch behavior as possible
+ */
+ if (check_only)
+ return true;
+ makedirs(filename, true);
+ close(creat(filename, 0666));
+ statfailed = stat(filename, &filestat);
+ }
+ if (statfailed && check_only)
+ fatal("%s not found, -C mode, can't probe further\n", filename);
+ /* For nonexistent or read-only files, look for RCS versions. */
+ if (statfailed ||
+ /* No one can write to it. */
+ (filestat.st_mode & 0222) == 0 ||
+ /* I can't write to it. */
+ ((filestat.st_mode & 0022) == 0 && filestat.st_uid != getuid())) {
+ char *filebase, *filedir;
+ struct stat cstat;
+ char *tmp_filename1, *tmp_filename2;
+
+ tmp_filename1 = strdup(filename);
+ tmp_filename2 = strdup(filename);
+ if (tmp_filename1 == NULL || tmp_filename2 == NULL)
+ fatal("strdupping filename");
+
+ filebase = basename(tmp_filename1);
+ filedir = dirname(tmp_filename2);
+
+#define try(f, a1, a2, a3) \
+ (snprintf(lbuf, sizeof lbuf, f, a1, a2, a3), stat(lbuf, &cstat) == 0)
+
+ /*
+ * else we can't write to it but it's not under a version
+ * control system, so just proceed.
+ */
+ if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) ||
+ try("%s/RCS/%s%s", filedir, filebase, "") ||
+ try("%s/%s%s", filedir, filebase, RCSSUFFIX)) {
+ if (!statfailed) {
+ if ((filestat.st_mode & 0222) != 0)
+ /* The owner can write to it. */
+ fatal("file %s seems to be locked "
+ "by somebody else under RCS\n",
+ filename);
+ /*
+ * It might be checked out unlocked. See if
+ * it's safe to check out the default version
+ * locked.
+ */
+ if (verbose)
+ say("Comparing file %s to default "
+ "RCS version...\n", filename);
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("can't fork: %s\n",
+ strerror(errno));
+ case 0:
+ devnull = open("/dev/null", O_RDONLY);
+ if (devnull == -1) {
+ fatal("can't open /dev/null: %s",
+ strerror(errno));
+ }
+ (void)dup2(devnull, STDOUT_FILENO);
+ argp[0] = __UNCONST(RCSDIFF);
+ argp[1] = __UNCONST(filename);
+ execv(RCSDIFF, argp);
+ exit(127);
+ }
+ pid = waitpid(pid, &pstat, 0);
+ if (pid == -1 || WEXITSTATUS(pstat) != 0) {
+ fatal("can't check out file %s: "
+ "differs from default RCS version\n",
+ filename);
+ }
+ }
+
+ if (verbose)
+ say("Checking out file %s from RCS...\n",
+ filename);
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("can't fork: %s\n", strerror(errno));
+ case 0:
+ argp[0] = __UNCONST(CHECKOUT);
+ argp[1] = __UNCONST("-l");
+ argp[2] = __UNCONST(filename);
+ execv(CHECKOUT, argp);
+ exit(127);
+ }
+ pid = waitpid(pid, &pstat, 0);
+ if (pid == -1 || WEXITSTATUS(pstat) != 0 ||
+ stat(filename, &filestat)) {
+ fatal("can't check out file %s from RCS\n",
+ filename);
+ }
+ } else if (statfailed) {
+ fatal("can't find %s\n", filename);
+ }
+ free(tmp_filename1);
+ free(tmp_filename2);
+ }
+
+ filemode = filestat.st_mode;
+ if (!S_ISREG(filemode))
+ fatal("%s is not a normal file--can't patch\n", filename);
+ i_size = filestat.st_size;
+ if (out_of_mem) {
+ set_hunkmax(); /* make sure dynamic arrays are allocated */
+ out_of_mem = false;
+ return false; /* force plan b because plan a bombed */
+ }
+ if ((uintmax_t)i_size > (uintmax_t)SIZE_MAX) {
+ say("block too large to mmap\n");
+ return false;
+ }
+ if ((ifd = open(filename, O_RDONLY)) < 0)
+ pfatal("can't open file %s", filename);
+
+ if (i_size) {
+ i_womp = mmap(NULL, i_size, PROT_READ, MAP_PRIVATE, ifd, 0);
+ if (i_womp == MAP_FAILED) {
+ perror("mmap failed");
+ i_womp = NULL;
+ close(ifd);
+ return false;
+ }
+ } else {
+ i_womp = NULL;
+ }
+
+ close(ifd);
+ if (i_size)
+ madvise(i_womp, i_size, MADV_SEQUENTIAL);
+
+ /* estimate the number of lines */
+ lines_allocated = i_size / 25;
+ if (lines_allocated < 100)
+ lines_allocated = 100;
+
+ if (!reallocate_lines(&lines_allocated))
+ return false;
+
+ /* now scan the buffer and build pointer array */
+ iline = 1;
+ i_ptr[iline] = i_womp;
+ /* test for NUL too, to maintain the behavior of the original code */
+ for (s = i_womp, i = 0; i < i_size && *s != '\0'; s++, i++) {
+ if (*s == '\n') {
+ if (iline == lines_allocated) {
+ if (!reallocate_lines(&lines_allocated))
+ return false;
+ }
+ /* these are NOT NUL terminated */
+ i_ptr[++iline] = s + 1;
+ }
+ }
+ /* if the last line contains no EOL, append one */
+ if (i_size > 0 && i_womp[i_size - 1] != '\n') {
+ last_line_missing_eol = true;
+ /* fix last line */
+ sz = s - i_ptr[iline];
+ p = malloc(sz + 1);
+ if (p == NULL) {
+ free(i_ptr);
+ i_ptr = NULL;
+ munmap(i_womp, i_size);
+ i_womp = NULL;
+ return false;
+ }
+
+ memcpy(p, i_ptr[iline], sz);
+ p[sz] = '\n';
+ i_ptr[iline] = p;
+ /* count the extra line and make it point to some valid mem */
+ i_ptr[++iline] = empty_line;
+ } else
+ last_line_missing_eol = false;
+
+ input_lines = iline - 1;
+
+ /* now check for revision, if any */
+
+ if (revision != NULL) {
+ if (!rev_in_string(i_womp)) {
+ if (force) {
+ if (verbose)
+ say("Warning: this file doesn't appear "
+ "to be the %s version--patching anyway.\n",
+ revision);
+ } else if (batch) {
+ fatal("this file doesn't appear to be the "
+ "%s version--aborting.\n",
+ revision);
+ } else {
+ ask("This file doesn't appear to be the "
+ "%s version--patch anyway? [n] ",
+ revision);
+ if (*buf != 'y')
+ fatal("aborted\n");
+ }
+ } else if (verbose)
+ say("Good. This file appears to be the %s version.\n",
+ revision);
+ }
+ return true; /* plan a will work */
+}
+
+/* Keep (virtually) nothing in memory. */
+
+static void
+plan_b(const char *filename)
+{
+ FILE *ifp;
+ size_t i = 0, j, maxlen = 1;
+ char *p;
+ bool found_revision = (revision == NULL);
+
+ using_plan_a = false;
+ if ((ifp = fopen(filename, "r")) == NULL)
+ pfatal("can't open file %s", filename);
+ unlink(TMPINNAME);
+ if ((tifd = open(TMPINNAME, O_EXCL | O_CREAT | O_WRONLY, 0666)) < 0)
+ pfatal("can't open file %s", TMPINNAME);
+ while (fgets(buf, buf_len, ifp) != NULL) {
+ if (revision != NULL && !found_revision && rev_in_string(buf))
+ found_revision = true;
+ if ((i = strlen(buf)) > maxlen)
+ maxlen = i; /* find longest line */
+ }
+ last_line_missing_eol = i > 0 && buf[i - 1] != '\n';
+ if (last_line_missing_eol && maxlen == i)
+ maxlen++;
+
+ if (revision != NULL) {
+ if (!found_revision) {
+ if (force) {
+ if (verbose)
+ say("Warning: this file doesn't appear "
+ "to be the %s version--patching anyway.\n",
+ revision);
+ } else if (batch) {
+ fatal("this file doesn't appear to be the "
+ "%s version--aborting.\n",
+ revision);
+ } else {
+ ask("This file doesn't appear to be the %s "
+ "version--patch anyway? [n] ",
+ revision);
+ if (*buf != 'y')
+ fatal("aborted\n");
+ }
+ } else if (verbose)
+ say("Good. This file appears to be the %s version.\n",
+ revision);
+ }
+ fseek(ifp, 0L, SEEK_SET); /* rewind file */
+ lines_per_buf = BUFFERSIZE / maxlen;
+ tireclen = maxlen;
+ tibuf[0] = malloc(BUFFERSIZE + 1);
+ if (tibuf[0] == NULL)
+ fatal("out of memory\n");
+ tibuf[1] = malloc(BUFFERSIZE + 1);
+ if (tibuf[1] == NULL)
+ fatal("out of memory\n");
+ for (i = 1;; i++) {
+ p = tibuf[0] + maxlen * (i % lines_per_buf);
+ if (i % lines_per_buf == 0) /* new block */
+ if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE)
+ pfatal("can't write temp file");
+ if (fgets(p, maxlen + 1, ifp) == NULL) {
+ input_lines = i - 1;
+ if (i % lines_per_buf != 0)
+ if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE)
+ pfatal("can't write temp file");
+ break;
+ }
+ j = strlen(p);
+ /* These are '\n' terminated strings, so no need to add a NUL */
+ if (j == 0 || p[j - 1] != '\n')
+ p[j] = '\n';
+ }
+ fclose(ifp);
+ close(tifd);
+ if ((tifd = open(TMPINNAME, O_RDONLY)) < 0)
+ pfatal("can't reopen file %s", TMPINNAME);
+}
+
+/*
+ * Fetch a line from the input file, \n terminated, not necessarily \0.
+ */
+char *
+ifetch(LINENUM line, int whichbuf)
+{
+ if (line < 1 || line > input_lines) {
+ if (warn_on_invalid_line) {
+ say("No such line %ld in input file, ignoring\n", line);
+ warn_on_invalid_line = false;
+ }
+ return NULL;
+ }
+ if (using_plan_a)
+ return i_ptr[line];
+ else {
+ LINENUM offline = line % lines_per_buf;
+ LINENUM baseline = line - offline;
+
+ if (tiline[0] == baseline)
+ whichbuf = 0;
+ else if (tiline[1] == baseline)
+ whichbuf = 1;
+ else {
+ tiline[whichbuf] = baseline;
+
+ if (lseek(tifd, (off_t) (baseline / lines_per_buf *
+ BUFFERSIZE), SEEK_SET) < 0)
+ pfatal("cannot seek in the temporary input file");
+
+ if (read(tifd, tibuf[whichbuf], BUFFERSIZE) < 0)
+ pfatal("error reading tmp file %s", TMPINNAME);
+ }
+ return tibuf[whichbuf] + (tireclen * offline);
+ }
+}
+
+/*
+ * True if the string argument contains the revision number we want.
+ */
+static bool
+rev_in_string(const char *string)
+{
+ const char *s;
+ size_t patlen;
+
+ if (revision == NULL)
+ return true;
+ patlen = strlen(revision);
+ if (strnEQ(string, revision, patlen) && isspace((unsigned char)string[patlen]))
+ return true;
+ for (s = string; *s; s++) {
+ if (isspace((unsigned char)*s) && strnEQ(s + 1, revision, patlen) &&
+ isspace((unsigned char)s[patlen + 1])) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/usr.bin/patch/inp.h b/usr.bin/patch/inp.h
new file mode 100644
index 0000000..d010526
--- /dev/null
+++ b/usr.bin/patch/inp.h
@@ -0,0 +1,35 @@
+/*
+ * $OpenBSD: inp.h,v 1.8 2003/08/15 08:00:51 otto Exp $
+ * $DragonFly: src/usr.bin/patch/inp.h,v 1.1 2004/09/24 18:44:28 joerg Exp $
+ * $NetBSD: inp.h,v 1.10 2008/09/19 18:33:34 joerg Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+void re_input(void);
+void scan_input(const char *);
+char *ifetch(LINENUM, int);
diff --git a/usr.bin/patch/mkpath.c b/usr.bin/patch/mkpath.c
new file mode 100644
index 0000000..3d18036
--- /dev/null
+++ b/usr.bin/patch/mkpath.c
@@ -0,0 +1,84 @@
+/*
+ * $OpenBSD: mkpath.c,v 1.2 2005/06/20 07:14:06 otto Exp $
+ * $DragonFly: src/usr.bin/patch/mkpath.c,v 1.1 2007/09/29 23:11:10 swildner Exp $
+ * $NetBSD: mkpath.c,v 1.1 2008/09/19 18:33:34 joerg Exp $
+ */
+
+/*
+ * Copyright (c) 1983, 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: mkpath.c,v 1.1 2008/09/19 18:33:34 joerg Exp $");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <errno.h>
+#include <string.h>
+
+int mkpath(char *);
+
+/* Code taken directly from mkdir(1).
+
+ * mkpath -- create directories.
+ * path - path
+ */
+int
+mkpath(char *path)
+{
+ struct stat sb;
+ char *slash;
+ int done = 0;
+
+ slash = path;
+
+ while (!done) {
+ slash += strspn(slash, "/");
+ slash += strcspn(slash, "/");
+
+ done = (*slash == '\0');
+ *slash = '\0';
+
+ if (stat(path, &sb)) {
+ if (errno != ENOENT || (mkdir(path, 0777) &&
+ errno != EEXIST)) {
+ warn("%s", path);
+ return (-1);
+ }
+ } else if (!S_ISDIR(sb.st_mode)) {
+ warnx("%s: %s", path, strerror(ENOTDIR));
+ return (-1);
+ }
+
+ *slash = '/';
+ }
+
+ return (0);
+}
+
diff --git a/usr.bin/patch/patch.1 b/usr.bin/patch/patch.1
new file mode 100644
index 0000000..1f434c7
--- /dev/null
+++ b/usr.bin/patch/patch.1
@@ -0,0 +1,663 @@
+.\" $OpenBSD: patch.1,v 1.22 2008/06/06 20:44:00 jmc Exp $
+.\" $DragonFly: src/usr.bin/patch/patch.1,v 1.10 2008/08/18 19:15:55 joerg Exp $
+.\" $NetBSD: patch.1,v 1.21 2017/07/03 21:34:20 wiz Exp $
+.\" Copyright 1986, Larry Wall
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following condition
+.\" is met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this condition and the following disclaimer.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd November 7, 2015
+.Dt PATCH 1
+.Os
+.Sh NAME
+.Nm patch
+.Nd apply a diff file to an original
+.Sh SYNOPSIS
+.Nm
+.Op Fl bCcEeflNnRstuv
+.Op Fl B Ar backup-prefix
+.Op Fl D Ar symbol
+.Op Fl d Ar directory
+.Op Fl F Ar max-fuzz
+.Op Fl i Ar patchfile
+.Op Fl o Ar out-file
+.Op Fl p Ar strip-count
+.Op Fl r Ar rej-name
+.Op Fl V Cm t | nil | never | none
+.Op Fl x Ar number
+.Op Fl z Ar backup-ext
+.Op Fl Fl posix
+.Op Ar origfile Op Ar patchfile
+.Nm
+.Pf \*(Lt Ar patchfile
+.Sh DESCRIPTION
+.Nm
+will take a patch file containing any of the four forms of difference
+listing produced by the
+.Xr diff 1
+program and apply those differences to an original file,
+producing a patched version.
+If
+.Ar patchfile
+is omitted, or is a hyphen, the patch will be read from the standard input.
+.Pp
+.Nm
+will attempt to determine the type of the diff listing, unless over-ruled by a
+.Fl c ,
+.Fl e ,
+.Fl n ,
+or
+.Fl u
+option.
+Context diffs (old-style, new-style, and unified) and
+normal diffs are applied directly by the
+.Nm
+program itself, whereas ed diffs are simply fed to the
+.Xr ed 1
+editor via a pipe.
+.Pp
+If the
+.Ar patchfile
+contains more than one patch,
+.Nm
+will try to apply each of them as if they came from separate patch files.
+This means, among other things, that it is assumed that the name of the file
+to patch must be determined for each diff listing, and that the garbage before
+each diff listing will be examined for interesting things such as file names
+and revision level (see the section on
+.Sx Filename Determination
+below).
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl B Ar backup-prefix , Fl Fl prefix Ar backup-prefix
+Causes the next argument to be interpreted as a prefix to the backup file
+name.
+If this argument is specified, any argument to
+.Fl z
+will be ignored.
+.It Fl b , Fl Fl backup
+Save a backup copy of the file before it is modified.
+By default the original file is saved with a backup extension of
+.Qq .orig
+unless the file already has a numbered backup, in which case a numbered
+backup is made.
+This is equivalent to specifying
+.Qo Fl V Cm existing Qc .
+This option is currently the default, unless
+.Fl -posix
+is specified.
+.It Fl C , Fl Fl check
+Checks that the patch would apply cleanly, but does not modify anything.
+.It Fl c , Fl Fl context
+Forces
+.Nm
+to interpret the patch file as a context diff.
+.It Fl D Ar symbol , Fl Fl ifdef Ar symbol
+Causes
+.Nm
+to use the
+.Qq #ifdef...#endif
+construct to mark changes.
+The argument following will be used as the differentiating symbol.
+Note that, unlike the C compiler, there must be a space between the
+.Fl D
+and the argument.
+.It Fl d Ar directory , Fl Fl directory Ar directory
+Causes
+.Nm
+to interpret the next argument as a directory,
+and change the working directory to it before doing anything else.
+.It Fl E , Fl Fl remove-empty-files
+Causes
+.Nm
+to remove output files that are empty after the patches have been applied.
+This option is useful when applying patches that create or remove files.
+.It Fl e , Fl Fl ed
+Forces
+.Nm
+to interpret the patch file as an
+.Xr ed 1
+script.
+.It Fl F Ar max-fuzz , Fl Fl fuzz Ar max-fuzz
+Sets the maximum fuzz factor.
+This option only applies to context diffs, and causes
+.Nm
+to ignore up to that many lines in looking for places to install a hunk.
+Note that a larger fuzz factor increases the odds of a faulty patch.
+The default fuzz factor is 2, and it may not be set to more than
+the number of lines of context in the context diff, ordinarily 3.
+.It Fl f , Fl Fl force
+Forces
+.Nm
+to assume that the user knows exactly what he or she is doing, and to not
+ask any questions.
+It assumes the following:
+skip patches for which a file to patch can't be found;
+patch files even though they have the wrong version for the
+.Qq Prereq:
+line in the patch;
+and assume that patches are not reversed even if they look like they are.
+This option does not suppress commentary; use
+.Fl s
+for that.
+.It Fl i Ar patchfile , Fl Fl input Ar patchfile
+Causes the next argument to be interpreted as the input file name
+(i.e., a patchfile).
+This option may be specified multiple times.
+.It Fl l , Fl Fl ignore-whitespace
+Causes the pattern matching to be done loosely, in case the tabs and
+spaces have been munged in your input file.
+Any sequence of whitespace in the pattern line will match any sequence
+in the input file.
+Normal characters must still match exactly.
+Each line of the context must still match a line in the input file.
+.It Fl N , Fl Fl forward
+Causes
+.Nm
+to ignore patches that it thinks are reversed or already applied.
+See also
+.Fl R .
+.It Fl n , Fl Fl normal
+Forces
+.Nm
+to interpret the patch file as a normal diff.
+.It Fl o Ar out-file , Fl Fl output Ar out-file
+Causes the next argument to be interpreted as the output file name.
+.It Fl p Ar strip-count , Fl Fl strip Ar strip-count
+Sets the pathname strip count,
+which controls how pathnames found in the patch file are treated,
+in case you keep your files in a different directory than the person who sent
+out the patch.
+The strip count specifies how many slashes are to be stripped from
+the front of the pathname.
+(Any intervening directory names also go away.)
+For example, supposing the file name in the patch file was
+.Pa /u/howard/src/blurfl/blurfl.c :
+.Pp
+Setting
+.Fl p Ns Ar 0
+gives the entire pathname unmodified.
+.Pp
+.Fl p Ns Ar 1
+gives
+.Pp
+.D1 Pa u/howard/src/blurfl/blurfl.c
+.Pp
+without the leading slash.
+.Pp
+.Fl p Ns Ar 4
+gives
+.Pp
+.D1 Pa blurfl/blurfl.c
+.Pp
+Not specifying
+.Fl p
+at all just gives you
+.Pa blurfl.c ,
+unless all of the directories in the leading path
+.Pq Pa u/howard/src/blurfl
+exist and that path is relative,
+in which case you get the entire pathname unmodified.
+Whatever you end up with is looked for either in the current directory,
+or the directory specified by the
+.Fl d
+option.
+.It Fl R , Fl Fl reverse
+Tells
+.Nm
+that this patch was created with the old and new files swapped.
+(Yes, I'm afraid that does happen occasionally, human nature being what it
+is.)
+.Nm
+will attempt to swap each hunk around before applying it.
+Rejects will come out in the swapped format.
+The
+.Fl R
+option will not work with ed diff scripts because there is too little
+information to reconstruct the reverse operation.
+.Pp
+If the first hunk of a patch fails,
+.Nm
+will reverse the hunk to see if it can be applied that way.
+If it can, you will be asked if you want to have the
+.Fl R
+option set.
+If it can't, the patch will continue to be applied normally.
+(Note: this method cannot detect a reversed patch if it is a normal diff
+and if the first command is an append (i.e., it should have been a delete)
+since appends always succeed, due to the fact that a null context will match
+anywhere.
+Luckily, most patches add or change lines rather than delete them, so most
+reversed normal diffs will begin with a delete, which will fail, triggering
+the heuristic.)
+.It Fl r Ar rej-name , Fl Fl reject-file Ar rej-name
+Causes the next argument to be interpreted as the reject file name.
+.It Fl s , Fl Fl quiet , Fl Fl silent
+Makes
+.Nm
+do its work silently, unless an error occurs.
+.It Fl t , Fl Fl batch
+Similar to
+.Fl f ,
+in that it suppresses questions, but makes some different assumptions:
+skip patches for which a file to patch can't be found (the same as
+.Fl f ) ;
+skip patches for which the file has the wrong version for the
+.Qq Prereq:
+line in the patch;
+and assume that patches are reversed if they look like they are.
+.It Fl u , Fl Fl unified
+Forces
+.Nm
+to interpret the patch file as a unified context diff (a unidiff).
+.It Fl V Cm t | nil | never | none , Fl Fl version-control Cm t | nil | never | none
+Causes the next argument to be interpreted as a method for creating
+backup file names.
+The type of backups made can also be given in the
+.Ev PATCH_VERSION_CONTROL
+or
+.Ev VERSION_CONTROL
+environment variables, which are overridden by this option.
+The
+.Fl B
+option overrides this option, causing the prefix to always be used for
+making backup file names.
+The values of the
+.Ev PATCH_VERSION_CONTROL
+and
+.Ev VERSION_CONTROL
+environment variables and the argument to the
+.Fl V
+option are like the GNU Emacs
+.Dq version-control
+variable; they also recognize synonyms that are more descriptive.
+The valid values are (unique abbreviations are accepted):
+.Bl -tag -width Ds -offset indent
+.It Cm t , numbered
+Always make numbered backups.
+.It Cm nil , existing
+Make numbered backups of files that already have them,
+simple backups of the others.
+.It Cm never , simple
+Always make simple backups.
+.It Cm none
+No backups are created.
+.El
+.It Fl v , Fl Fl version
+Causes
+.Nm
+to print out its revision header and patch level.
+.It Fl x Ar number , Fl Fl debug Ar number
+Sets internal debugging flags, and is of interest only to
+.Nm
+patchers.
+.It Fl z Ar backup-ext , Fl Fl suffix Ar backup-ext
+Causes the next argument to be interpreted as the backup extension, to be
+used in place of
+.Qq .orig .
+.It Fl Fl posix
+Enables strict
+.St -p1003.1-2004
+conformance, specifically:
+.Bl -enum
+.It
+Backup files are not created unless the
+.Fl b
+option is specified.
+.It
+If unspecified, the file name used is the first of the old, new and
+index files that exists.
+.El
+.El
+.Ss Patch Application
+.Nm
+will try to skip any leading garbage, apply the diff,
+and then skip any trailing garbage.
+Thus you could feed an article or message containing a
+diff listing to
+.Nm ,
+and it should work.
+If the entire diff is indented by a consistent amount,
+this will be taken into account.
+.Pp
+With context diffs, and to a lesser extent with normal diffs,
+.Nm
+can detect when the line numbers mentioned in the patch are incorrect,
+and will attempt to find the correct place to apply each hunk of the patch.
+As a first guess, it takes the line number mentioned for the hunk, plus or
+minus any offset used in applying the previous hunk.
+If that is not the correct place,
+.Nm
+will scan both forwards and backwards for a set of lines matching the context
+given in the hunk.
+First
+.Nm
+looks for a place where all lines of the context match.
+If no such place is found, and it's a context diff, and the maximum fuzz factor
+is set to 1 or more, then another scan takes place ignoring the first and last
+line of context.
+If that fails, and the maximum fuzz factor is set to 2 or more,
+the first two and last two lines of context are ignored,
+and another scan is made.
+.Pq The default maximum fuzz factor is 2.
+.Pp
+If
+.Nm
+cannot find a place to install that hunk of the patch, it will put the hunk
+out to a reject file, which normally is the name of the output file plus
+.Qq .rej .
+(Note that the rejected hunk will come out in context diff form whether the
+input patch was a context diff or a normal diff.
+If the input was a normal diff, many of the contexts will simply be null.)
+The line numbers on the hunks in the reject file may be different than
+in the patch file: they reflect the approximate location patch thinks the
+failed hunks belong in the new file rather than the old one.
+.Pp
+As each hunk is completed, you will be told whether the hunk succeeded or
+failed, and which line (in the new file)
+.Nm
+thought the hunk should go on.
+If this is different from the line number specified in the diff,
+you will be told the offset.
+A single large offset MAY be an indication that a hunk was installed in the
+wrong place.
+You will also be told if a fuzz factor was used to make the match, in which
+case you should also be slightly suspicious.
+.Ss Filename Determination
+If no original file is specified on the command line,
+.Nm
+will try to figure out from the leading garbage what the name of the file
+to edit is.
+When checking a prospective file name, pathname components are stripped
+as specified by the
+.Fl p
+option and the file's existence and writability are checked relative
+to the current working directory (or the directory specified by the
+.Fl d
+option).
+.Pp
+If the diff is a context or unified diff,
+.Nm
+is able to determine the old and new file names from the diff header.
+For context diffs, the
+.Dq old
+file is specified in the line beginning with
+.Qq ***
+and the
+.Dq new
+file is specified in the line beginning with
+.Qq --- .
+For a unified diff, the
+.Dq old
+file is specified in the line beginning with
+.Qq ---
+and the
+.Dq new
+file is specified in the line beginning with
+.Qq +++ .
+If there is an
+.Qq Index:
+line in the leading garbage (regardless of the diff type),
+.Nm
+will use the file name from that line as the
+.Dq index
+file.
+.Pp
+.Nm
+will choose the file name by performing the following steps, with the first
+match used:
+.Bl -enum
+.It
+If
+.Nm
+is operating in strict
+.St -p1003.1-2004
+mode, the first of the
+.Dq old ,
+.Dq new
+and
+.Dq index
+file names that exist is used.
+Otherwise,
+.Nm
+will examine either the
+.Dq old
+and
+.Dq new
+file names or, for a non-context diff, the
+.Dq index
+file name, and choose the file name with the fewest path components,
+the shortest basename, and the shortest total file name length (in that order).
+.It
+If no file exists,
+.Nm
+checks for the existence of the files in an RCS directory using the criteria
+specified above.
+If found,
+.Nm
+will attempt to get or check out the file.
+.It
+If no suitable file was found to patch, the patch file is a context or
+unified diff, and the old file was zero length, the new file name is
+created and used.
+.It
+If the file name still cannot be determined,
+.Nm
+will prompt the user for the file name to use.
+.El
+.Pp
+Additionally, if the leading garbage contains a
+.Qq Prereq:\ \&
+line,
+.Nm
+will take the first word from the prerequisites line (normally a version
+number) and check the input file to see if that word can be found.
+If not,
+.Nm
+will ask for confirmation before proceeding.
+.Pp
+The upshot of all this is that you should be able to say, while in a news
+interface, the following:
+.Pp
+.Dl | patch -d /usr/src/local/blurfl
+.Pp
+and patch a file in the blurfl directory directly from the article containing
+the patch.
+.Ss Backup Files
+By default, the patched version is put in place of the original, with
+the original file backed up to the same name with the extension
+.Qq .orig ,
+or as specified by the
+.Fl B ,
+.Fl V ,
+or
+.Fl z
+options.
+The extension used for making backup files may also be specified in the
+.Ev SIMPLE_BACKUP_SUFFIX
+environment variable, which is overridden by the options above.
+.Pp
+If the backup file is a symbolic or hard link to the original file,
+.Nm
+creates a new backup file name by changing the first lowercase letter
+in the last component of the file's name into uppercase.
+If there are no more lowercase letters in the name,
+it removes the first character from the name.
+It repeats this process until it comes up with a
+backup file that does not already exist or is not linked to the original file.
+.Pp
+You may also specify where you want the output to go with the
+.Fl o
+option; if that file already exists, it is backed up first.
+.Ss Notes For Patch Senders
+There are several things you should bear in mind if you are going to
+be sending out patches:
+.Pp
+First, you can save people a lot of grief by keeping a
+.Pa patchlevel.h
+file which is patched to increment the patch level as the first diff in the
+patch file you send out.
+If you put a
+.Qq Prereq:
+line in with the patch, it won't let them apply
+patches out of order without some warning.
+.Pp
+Second, make sure you've specified the file names right, either in a
+context diff header, or with an
+.Qq Index:
+line.
+If you are patching something in a subdirectory, be sure to tell the patch
+user to specify a
+.Fl p
+option as needed.
+.Pp
+Third, you can create a file by sending out a diff that compares a
+null file to the file you want to create.
+This will only work if the file you want to create doesn't exist already in
+the target directory.
+.Pp
+Fourth, take care not to send out reversed patches, since it makes people wonder
+whether they already applied the patch.
+.Pp
+Fifth, while you may be able to get away with putting 582 diff listings into
+one file, it is probably wiser to group related patches into separate files in
+case something goes haywire.
+.Sh ENVIRONMENT
+.Bl -tag -width "PATCH_VERSION_CONTROL" -compact
+.It Ev POSIXLY_CORRECT
+When set,
+.Nm
+behaves as if the
+.Fl Fl posix
+option has been specified.
+.It Ev SIMPLE_BACKUP_SUFFIX
+Extension to use for backup file names instead of
+.Qq .orig .
+.It Ev TMPDIR
+Directory to put temporary files in; default is
+.Pa /tmp .
+.It Ev PATCH_VERSION_CONTROL
+Selects when numbered backup files are made.
+.It Ev VERSION_CONTROL
+Same as
+.Ev PATCH_VERSION_CONTROL .
+.El
+.Sh FILES
+.Bl -tag -width "$TMPDIR/patch*" -compact
+.It Pa $TMPDIR/patch*
+.Nm
+temporary files
+.It Pa /dev/tty
+used to read input when
+.Nm
+prompts the user
+.El
+.Sh DIAGNOSTICS
+Too many to list here, but generally indicative that
+.Nm
+couldn't parse your patch file.
+.Pp
+The message
+.Qq Hmm...
+indicates that there is unprocessed text in the patch file and that
+.Nm
+is attempting to intuit whether there is a patch in that text and, if so,
+what kind of patch it is.
+.Pp
+The
+.Nm
+utility exits with one of the following values:
+.Pp
+.Bl -tag -width Ds -compact -offset indent
+.It \&0
+Successful completion.
+.It \&1
+One or more lines were written to a reject file.
+.It >\&1
+An error occurred.
+.El
+.Pp
+When applying a set of patches in a loop it behooves you to check this
+exit status so you don't apply a later patch to a partially patched file.
+.Sh SEE ALSO
+.Xr diff 1
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2004
+specification
+(except as detailed above for the
+.Fl -posix
+option),
+though the presence of
+.Nm
+itself is optional.
+.Pp
+The flags
+.Op Fl CEfstuvBFVxz
+and
+.Op Fl -posix
+are extensions to that specification.
+.Sh AUTHORS
+.An Larry Wall
+with many other contributors.
+.Sh CAVEATS
+.Nm
+cannot tell if the line numbers are off in an ed script, and can only detect
+bad line numbers in a normal diff when it finds a
+.Qq change
+or a
+.Qq delete
+command.
+A context diff using fuzz factor 3 may have the same problem.
+Until a suitable interactive interface is added, you should probably do
+a context diff in these cases to see if the changes made sense.
+Of course, compiling without errors is a pretty good indication that the patch
+worked, but not always.
+.Pp
+.Nm
+usually produces the correct results, even when it has to do a lot of
+guessing.
+However, the results are guaranteed to be correct only when the patch is
+applied to exactly the same version of the file that the patch was
+generated from.
+.Sh BUGS
+Could be smarter about partial matches, excessively deviant offsets and
+swapped code, but that would take an extra pass.
+.Pp
+Check patch mode
+.Pq Fl C
+will fail if you try to check several patches in succession that build on
+each other.
+The entire
+.Nm
+code would have to be restructured to keep temporary files around so that it
+can handle this situation.
+.Pp
+If code has been duplicated (for instance with #ifdef OLDCODE ... #else ...
+#endif),
+.Nm
+is incapable of patching both versions, and, if it works at all, will likely
+patch the wrong one, and tell you that it succeeded to boot.
+.Pp
+If you apply a patch you've already applied,
+.Nm
+will think it is a reversed patch, and offer to un-apply the patch.
+This could be construed as a feature.
diff --git a/usr.bin/patch/patch.c b/usr.bin/patch/patch.c
new file mode 100644
index 0000000..9f49e7f
--- /dev/null
+++ b/usr.bin/patch/patch.c
@@ -0,0 +1,1064 @@
+/*
+ * $OpenBSD: patch.c,v 1.45 2007/04/18 21:52:24 sobrado Exp $
+ * $DragonFly: src/usr.bin/patch/patch.c,v 1.10 2008/08/10 23:39:56 joerg Exp $
+ * $NetBSD: patch.c,v 1.29 2011/09/06 18:25:14 joerg Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: patch.c,v 1.29 2011/09/06 18:25:14 joerg Exp $");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "util.h"
+#include "pch.h"
+#include "inp.h"
+#include "backupfile.h"
+#include "pathnames.h"
+
+mode_t filemode = 0644;
+
+char buf[MAXLINELEN]; /* general purpose buffer */
+size_t buf_len = sizeof(buf);
+
+bool using_plan_a = true; /* try to keep everything in memory */
+bool out_of_mem = false; /* ran out of memory in plan a */
+
+#define MAXFILEC 2
+
+char *filearg[MAXFILEC];
+bool ok_to_create_file = false;
+char *outname = NULL;
+char *origprae = NULL;
+char *TMPOUTNAME;
+char *TMPINNAME;
+char *TMPREJNAME;
+char *TMPPATNAME;
+bool toutkeep = false;
+bool trejkeep = false;
+bool warn_on_invalid_line;
+bool last_line_missing_eol;
+
+#ifdef DEBUGGING
+int debug = 0;
+#endif
+
+bool force = false;
+bool batch = false;
+bool verbose = true;
+bool reverse = false;
+bool noreverse = false;
+bool skip_rest_of_patch = false;
+int strippath = 957;
+bool canonicalize = false;
+bool check_only = false;
+int diff_type = 0;
+char *revision = NULL; /* prerequisite revision, if any */
+LINENUM input_lines = 0; /* how long is input file in lines */
+int posix = 0; /* strict POSIX mode? */
+
+static void reinitialize_almost_everything(void);
+static void get_some_switches(void);
+static LINENUM locate_hunk(LINENUM);
+static void abort_context_hunk(void);
+static void rej_line(int, LINENUM);
+static void abort_hunk(void);
+static void apply_hunk(LINENUM);
+static void init_output(const char *);
+static void init_reject(const char *);
+static void copy_till(LINENUM, bool);
+static bool spew_output(void);
+static void dump_line(LINENUM, bool);
+static bool patch_match(LINENUM, LINENUM, LINENUM);
+static bool similar(const char *, const char *, int);
+__dead static void usage(void);
+
+/* true if -E was specified on command line. */
+static bool remove_empty_files = false;
+
+/* true if -R was specified on command line. */
+static bool reverse_flag_specified = false;
+
+/* buffer holding the name of the rejected patch file. */
+static char rejname[NAME_MAX + 1];
+
+/* buffer for stderr */
+static char serrbuf[BUFSIZ];
+
+/* how many input lines have been irretractibly output */
+static LINENUM last_frozen_line = 0;
+
+static int Argc; /* guess */
+static char **Argv;
+static int Argc_last; /* for restarting plan_b */
+static char **Argv_last;
+
+static FILE *ofp = NULL; /* output file pointer */
+static FILE *rejfp = NULL; /* reject file pointer */
+
+static int filec = 0; /* how many file arguments? */
+static LINENUM last_offset = 0;
+static LINENUM maxfuzz = 2;
+
+/* patch using ifdef, ifndef, etc. */
+static bool do_defines = false;
+/* #ifdef xyzzy */
+static char if_defined[128];
+/* #ifndef xyzzy */
+static char not_defined[128];
+/* #else */
+static const char else_defined[] = "#else\n";
+/* #endif xyzzy */
+static char end_defined[128];
+
+
+/* Apply a set of diffs as appropriate. */
+
+int
+main(int argc, char *argv[])
+{
+ int error = 0, hunk, failed, i, fd;
+ LINENUM where = 0, newwhere, fuzz, mymaxfuzz;
+ const char *tmpdir;
+ char *v;
+
+ setbuf(stderr, serrbuf);
+ for (i = 0; i < MAXFILEC; i++)
+ filearg[i] = NULL;
+
+ /* Cons up the names of the temporary files. */
+ if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0')
+ tmpdir = _PATH_TMP;
+ for (i = strlen(tmpdir) - 1; i > 0 && tmpdir[i] == '/'; i--)
+ ;
+ i++;
+ if (asprintf(&TMPOUTNAME, "%.*s/patchoXXXXXXXXXX", i, tmpdir) == -1)
+ fatal("cannot allocate memory");
+ if ((fd = mkstemp(TMPOUTNAME)) < 0)
+ pfatal("can't create %s", TMPOUTNAME);
+ close(fd);
+
+ if (asprintf(&TMPINNAME, "%.*s/patchiXXXXXXXXXX", i, tmpdir) == -1)
+ fatal("cannot allocate memory");
+ if ((fd = mkstemp(TMPINNAME)) < 0)
+ pfatal("can't create %s", TMPINNAME);
+ close(fd);
+
+ if (asprintf(&TMPREJNAME, "%.*s/patchrXXXXXXXXXX", i, tmpdir) == -1)
+ fatal("cannot allocate memory");
+ if ((fd = mkstemp(TMPREJNAME)) < 0)
+ pfatal("can't create %s", TMPREJNAME);
+ close(fd);
+
+ if (asprintf(&TMPPATNAME, "%.*s/patchpXXXXXXXXXX", i, tmpdir) == -1)
+ fatal("cannot allocate memory");
+ if ((fd = mkstemp(TMPPATNAME)) < 0)
+ pfatal("can't create %s", TMPPATNAME);
+ close(fd);
+
+ v = getenv("SIMPLE_BACKUP_SUFFIX");
+ if (v)
+ simple_backup_suffix = v;
+ else
+ simple_backup_suffix = ORIGEXT;
+
+ /* parse switches */
+ Argc = argc;
+ Argv = argv;
+ get_some_switches();
+
+ if (backup_type == none) {
+ if ((v = getenv("PATCH_VERSION_CONTROL")) == NULL)
+ v = getenv("VERSION_CONTROL");
+ if (v != NULL || !posix)
+ backup_type = get_version(v); /* OK to pass NULL. */
+ }
+
+ /* make sure we clean up /tmp in case of disaster */
+ set_signals(0);
+
+ for (open_patch_file(filearg[1]); there_is_another_patch();
+ reinitialize_almost_everything()) {
+ /* for each patch in patch file */
+
+ warn_on_invalid_line = true;
+
+ if (outname == NULL)
+ outname = savestr(filearg[0]);
+
+ /* for ed script just up and do it and exit */
+ if (diff_type == ED_DIFF) {
+ do_ed_script();
+ continue;
+ }
+ /* initialize the patched file */
+ if (!skip_rest_of_patch)
+ init_output(TMPOUTNAME);
+
+ /* initialize reject file */
+ init_reject(TMPREJNAME);
+
+ /* find out where all the lines are */
+ if (!skip_rest_of_patch)
+ scan_input(filearg[0]);
+
+ /* from here on, open no standard i/o files, because malloc */
+ /* might misfire and we can't catch it easily */
+
+ /* apply each hunk of patch */
+ hunk = 0;
+ failed = 0;
+ out_of_mem = false;
+ while (another_hunk()) {
+ hunk++;
+ fuzz = 0;
+ mymaxfuzz = pch_context();
+ if (maxfuzz < mymaxfuzz)
+ mymaxfuzz = maxfuzz;
+ if (!skip_rest_of_patch) {
+ do {
+ where = locate_hunk(fuzz);
+ if (hunk == 1 && where == 0 && !force) {
+ /* dwim for reversed patch? */
+ if (!pch_swap()) {
+ if (fuzz == 0)
+ say("Not enough memory to try swapped hunk! Assuming unswapped.\n");
+ continue;
+ }
+ reverse = !reverse;
+ /* try again */
+ where = locate_hunk(fuzz);
+ if (where == 0) {
+ /* didn't find it swapped */
+ if (!pch_swap())
+ /* put it back to normal */
+ fatal("lost hunk on alloc error!\n");
+ reverse = !reverse;
+ } else if (noreverse) {
+ if (!pch_swap())
+ /* put it back to normal */
+ fatal("lost hunk on alloc error!\n");
+ reverse = !reverse;
+ say("Ignoring previously applied (or reversed) patch.\n");
+ skip_rest_of_patch = true;
+ } else if (batch) {
+ if (verbose)
+ say("%seversed (or previously applied) patch detected! %s -R.",
+ reverse ? "R" : "Unr",
+ reverse ? "Assuming" : "Ignoring");
+ } else {
+ ask("%seversed (or previously applied) patch detected! %s -R? [y] ",
+ reverse ? "R" : "Unr",
+ reverse ? "Assume" : "Ignore");
+ if (*buf == 'n') {
+ ask("Apply anyway? [n] ");
+ if (*buf != 'y')
+ skip_rest_of_patch = true;
+ where = 0;
+ reverse = !reverse;
+ if (!pch_swap())
+ /* put it back to normal */
+ fatal("lost hunk on alloc error!\n");
+ }
+ }
+ }
+ } while (!skip_rest_of_patch && where == 0 &&
+ ++fuzz <= mymaxfuzz);
+
+ if (skip_rest_of_patch) { /* just got decided */
+ if (ferror(ofp) || fclose(ofp)) {
+ say("Error writing %s\n",
+ TMPOUTNAME);
+ error = 1;
+ }
+ ofp = NULL;
+ }
+ }
+ newwhere = pch_newfirst() + last_offset;
+ if (skip_rest_of_patch) {
+ abort_hunk();
+ failed++;
+ if (verbose)
+ say("Hunk #%d ignored at %ld.\n",
+ hunk, newwhere);
+ } else if (where == 0) {
+ abort_hunk();
+ failed++;
+ if (verbose)
+ say("Hunk #%d failed at %ld.\n",
+ hunk, newwhere);
+ } else {
+ apply_hunk(where);
+ if (verbose) {
+ say("Hunk #%d succeeded at %ld",
+ hunk, newwhere);
+ if (fuzz != 0)
+ say(" with fuzz %ld", fuzz);
+ if (last_offset)
+ say(" (offset %ld line%s)",
+ last_offset,
+ last_offset == 1L ? "" : "s");
+ say(".\n");
+ }
+ }
+ }
+
+ if (out_of_mem && using_plan_a) {
+ Argc = Argc_last;
+ Argv = Argv_last;
+ say("\n\nRan out of memory using Plan A--trying again...\n\n");
+ if (ofp)
+ fclose(ofp);
+ ofp = NULL;
+ if (rejfp)
+ fclose(rejfp);
+ rejfp = NULL;
+ continue;
+ }
+ if (hunk == 0)
+ fatal("Internal error: hunk should not be 0\n");
+
+ /* finish spewing out the new file */
+ if (!skip_rest_of_patch && !spew_output()) {
+ say("Can't write %s\n", TMPOUTNAME);
+ error = 1;
+ }
+
+ /* and put the output where desired */
+ ignore_signals();
+ if (!skip_rest_of_patch) {
+ struct stat statbuf;
+ char *realout = outname;
+
+ if (!check_only) {
+ if (move_file(TMPOUTNAME, outname) < 0) {
+ toutkeep = true;
+ realout = TMPOUTNAME;
+ chmod(TMPOUTNAME, filemode);
+ } else
+ chmod(outname, filemode);
+
+ if (remove_empty_files &&
+ stat(realout, &statbuf) == 0 &&
+ statbuf.st_size == 0) {
+ if (verbose)
+ say("Removing %s (empty after patching).\n",
+ realout);
+ unlink(realout);
+ }
+ }
+ }
+ if (ferror(rejfp) || fclose(rejfp)) {
+ say("Error writing %s\n", rejname);
+ error = 1;
+ }
+ rejfp = NULL;
+ if (failed) {
+ error = 1;
+ if (*rejname == '\0') {
+ if (strlcpy(rejname, outname,
+ sizeof(rejname)) >= sizeof(rejname))
+ fatal("filename %s is too long\n", outname);
+ if (strlcat(rejname, REJEXT,
+ sizeof(rejname)) >= sizeof(rejname))
+ fatal("filename %s is too long\n", outname);
+ }
+ if (skip_rest_of_patch) {
+ say("%d out of %d hunks ignored--saving rejects to %s\n",
+ failed, hunk, rejname);
+ } else {
+ say("%d out of %d hunks failed--saving rejects to %s\n",
+ failed, hunk, rejname);
+ }
+ if (!check_only && move_file(TMPREJNAME, rejname) < 0)
+ trejkeep = true;
+ }
+ set_signals(1);
+ }
+ my_exit(error);
+ /* NOTREACHED */
+}
+
+/* Prepare to find the next patch to do in the patch file. */
+
+static void
+reinitialize_almost_everything(void)
+{
+ re_patch();
+ re_input();
+
+ input_lines = 0;
+ last_frozen_line = 0;
+
+ filec = 0;
+ if (!out_of_mem) {
+ free(filearg[0]);
+ filearg[0] = NULL;
+ }
+
+ free(outname);
+ outname = NULL;
+
+ last_offset = 0;
+ diff_type = 0;
+
+ free(revision);
+ revision = NULL;
+
+ reverse = reverse_flag_specified;
+ skip_rest_of_patch = false;
+
+ get_some_switches();
+}
+
+/* Process switches and filenames. */
+
+static void
+get_some_switches(void)
+{
+ const char *options = "b::B:cCd:D:eEfF:i:lnNo:p:r:RstuvV:x:z:";
+ static struct option longopts[] = {
+ {"backup", no_argument, 0, 'b'},
+ {"batch", no_argument, 0, 't'},
+ {"check", no_argument, 0, 'C'},
+ {"context", no_argument, 0, 'c'},
+ {"debug", required_argument, 0, 'x'},
+ {"directory", required_argument, 0, 'd'},
+ {"ed", no_argument, 0, 'e'},
+ {"force", no_argument, 0, 'f'},
+ {"forward", no_argument, 0, 'N'},
+ {"fuzz", required_argument, 0, 'F'},
+ {"ifdef", required_argument, 0, 'D'},
+ {"input", required_argument, 0, 'i'},
+ {"ignore-whitespace", no_argument, 0, 'l'},
+ {"normal", no_argument, 0, 'n'},
+ {"output", required_argument, 0, 'o'},
+ {"prefix", required_argument, 0, 'B'},
+ {"quiet", no_argument, 0, 's'},
+ {"reject-file", required_argument, 0, 'r'},
+ {"remove-empty-files", no_argument, 0, 'E'},
+ {"reverse", no_argument, 0, 'R'},
+ {"silent", no_argument, 0, 's'},
+ {"strip", required_argument, 0, 'p'},
+ {"suffix", required_argument, 0, 'z'},
+ {"unified", no_argument, 0, 'u'},
+ {"version", no_argument, 0, 'v'},
+ {"version-control", required_argument, 0, 'V'},
+ {"posix", no_argument, &posix, 1},
+ {NULL, 0, 0, 0}
+ };
+ int ch;
+
+ rejname[0] = '\0';
+ Argc_last = Argc;
+ Argv_last = Argv;
+ if (!Argc)
+ return;
+ optreset = optind = 1;
+ while ((ch = getopt_long(Argc, Argv, options, longopts, NULL)) != -1) {
+ switch (ch) {
+ case 'b':
+ if (backup_type == none)
+ backup_type = numbered_existing;
+ if (optarg == NULL)
+ break;
+ if (verbose)
+ say("Warning, the ``-b suffix'' option has been"
+ " obsoleted by the -z option.\n");
+ /* FALLTHROUGH */
+ case 'z':
+ /* must directly follow 'b' case for backwards compat */
+ simple_backup_suffix = savestr(optarg);
+ break;
+ case 'B':
+ origprae = savestr(optarg);
+ break;
+ case 'c':
+ diff_type = CONTEXT_DIFF;
+ break;
+ case 'C':
+ check_only = true;
+ break;
+ case 'd':
+ if (chdir(optarg) < 0)
+ pfatal("can't cd to %s", optarg);
+ break;
+ case 'D':
+ do_defines = true;
+ if (!isalpha((unsigned char)*optarg) && *optarg != '_')
+ fatal("argument to -D is not an identifier\n");
+ snprintf(if_defined, sizeof if_defined,
+ "#ifdef %s\n", optarg);
+ snprintf(not_defined, sizeof not_defined,
+ "#ifndef %s\n", optarg);
+ snprintf(end_defined, sizeof end_defined,
+ "#endif /* %s */\n", optarg);
+ break;
+ case 'e':
+ diff_type = ED_DIFF;
+ break;
+ case 'E':
+ remove_empty_files = true;
+ break;
+ case 'f':
+ force = true;
+ break;
+ case 'F':
+ maxfuzz = atoi(optarg);
+ break;
+ case 'i':
+ if (++filec == MAXFILEC)
+ fatal("too many file arguments\n");
+ filearg[filec] = savestr(optarg);
+ break;
+ case 'l':
+ canonicalize = true;
+ break;
+ case 'n':
+ diff_type = NORMAL_DIFF;
+ break;
+ case 'N':
+ noreverse = true;
+ break;
+ case 'o':
+ outname = savestr(optarg);
+ break;
+ case 'p':
+ strippath = atoi(optarg);
+ break;
+ case 'r':
+ if (strlcpy(rejname, optarg,
+ sizeof(rejname)) >= sizeof(rejname))
+ fatal("argument for -r is too long\n");
+ break;
+ case 'R':
+ reverse = true;
+ reverse_flag_specified = true;
+ break;
+ case 's':
+ verbose = false;
+ break;
+ case 't':
+ batch = true;
+ break;
+ case 'u':
+ diff_type = UNI_DIFF;
+ break;
+ case 'v':
+ version();
+ break;
+ case 'V':
+ backup_type = get_version(optarg);
+ break;
+#ifdef DEBUGGING
+ case 'x':
+ debug = atoi(optarg);
+ break;
+#endif
+ default:
+ if (ch != '\0')
+ usage();
+ break;
+ }
+ }
+ Argc -= optind;
+ Argv += optind;
+
+ if (Argc > 0) {
+ filearg[0] = savestr(*Argv++);
+ Argc--;
+ while (Argc > 0) {
+ if (++filec == MAXFILEC)
+ fatal("too many file arguments\n");
+ filearg[filec] = savestr(*Argv++);
+ Argc--;
+ }
+ }
+
+ if (getenv("POSIXLY_CORRECT") != NULL)
+ posix = 1;
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+"usage: patch [-bCcEeflNnRstuv] [-B backup-prefix] [-D symbol] [-d directory]\n"
+" [-F max-fuzz] [-i patchfile] [-o out-file] [-p strip-count]\n"
+" [-r rej-name] [-V t | nil | never] [-x number] [-z backup-ext]\n"
+" [--posix] [origfile [patchfile]]\n"
+" patch <patchfile\n");
+ my_exit(EXIT_FAILURE);
+}
+
+/*
+ * Attempt to find the right place to apply this hunk of patch.
+ */
+static LINENUM
+locate_hunk(LINENUM fuzz)
+{
+ LINENUM first_guess = pch_first() + last_offset;
+ LINENUM offset;
+ LINENUM pat_lines = pch_ptrn_lines();
+ LINENUM max_pos_offset = input_lines - first_guess - pat_lines + 1;
+ LINENUM max_neg_offset = first_guess - last_frozen_line - 1 + pch_context();
+
+ if (pat_lines == 0) { /* null range matches always */
+ if (verbose && fuzz == 0 && (diff_type == CONTEXT_DIFF
+ || diff_type == NEW_CONTEXT_DIFF
+ || diff_type == UNI_DIFF)) {
+ say("Empty context always matches.\n");
+ }
+ return (first_guess);
+ }
+ if (max_neg_offset >= first_guess) /* do not try lines < 0 */
+ max_neg_offset = first_guess - 1;
+ if (first_guess <= input_lines && patch_match(first_guess, 0, fuzz))
+ return first_guess;
+ for (offset = 1; ; offset++) {
+ bool check_after = (offset <= max_pos_offset);
+ bool check_before = (offset <= max_neg_offset);
+
+ if (check_after && patch_match(first_guess, offset, fuzz)) {
+#ifdef DEBUGGING
+ if (debug & 1)
+ say("Offset changing from %ld to %ld\n",
+ last_offset, offset);
+#endif
+ last_offset = offset;
+ return first_guess + offset;
+ } else if (check_before && patch_match(first_guess, -offset, fuzz)) {
+#ifdef DEBUGGING
+ if (debug & 1)
+ say("Offset changing from %ld to %ld\n",
+ last_offset, -offset);
+#endif
+ last_offset = -offset;
+ return first_guess - offset;
+ } else if (!check_before && !check_after)
+ return 0;
+ }
+}
+
+/* We did not find the pattern, dump out the hunk so they can handle it. */
+
+static void
+abort_context_hunk(void)
+{
+ LINENUM i;
+ const LINENUM pat_end = pch_end();
+ /*
+ * add in last_offset to guess the same as the previous successful
+ * hunk
+ */
+ const LINENUM oldfirst = pch_first() + last_offset;
+ const LINENUM newfirst = pch_newfirst() + last_offset;
+ const LINENUM oldlast = oldfirst + pch_ptrn_lines() - 1;
+ const LINENUM newlast = newfirst + pch_repl_lines() - 1;
+ const char *stars = (diff_type >= NEW_CONTEXT_DIFF ? " ****" : "");
+ const char *minuses = (diff_type >= NEW_CONTEXT_DIFF ? " ----" : " -----");
+
+ fprintf(rejfp, "***************\n");
+ for (i = 0; i <= pat_end; i++) {
+ switch (pch_char(i)) {
+ case '*':
+ if (oldlast < oldfirst)
+ fprintf(rejfp, "*** 0%s\n", stars);
+ else if (oldlast == oldfirst)
+ fprintf(rejfp, "*** %ld%s\n", oldfirst, stars);
+ else
+ fprintf(rejfp, "*** %ld,%ld%s\n", oldfirst,
+ oldlast, stars);
+ break;
+ case '=':
+ if (newlast < newfirst)
+ fprintf(rejfp, "--- 0%s\n", minuses);
+ else if (newlast == newfirst)
+ fprintf(rejfp, "--- %ld%s\n", newfirst, minuses);
+ else
+ fprintf(rejfp, "--- %ld,%ld%s\n", newfirst,
+ newlast, minuses);
+ break;
+ case '\n':
+ fprintf(rejfp, "%s", pfetch(i));
+ break;
+ case ' ':
+ case '-':
+ case '+':
+ case '!':
+ fprintf(rejfp, "%c %s", pch_char(i), pfetch(i));
+ break;
+ default:
+ fatal("fatal internal error in abort_context_hunk\n");
+ }
+ }
+}
+
+static void
+rej_line(int ch, LINENUM i)
+{
+ size_t len;
+ const char *line = pfetch(i);
+
+ len = strlen(line);
+
+ fprintf(rejfp, "%c%s", ch, line);
+ if (len == 0 || line[len-1] != '\n')
+ fprintf(rejfp, "\n\\ No newline at end of file\n");
+}
+
+static void
+abort_hunk(void)
+{
+ LINENUM i, j, split;
+ int ch1, ch2;
+ const LINENUM pat_end = pch_end();
+ const LINENUM oldfirst = pch_first() + last_offset;
+ const LINENUM newfirst = pch_newfirst() + last_offset;
+
+ if (diff_type != UNI_DIFF) {
+ abort_context_hunk();
+ return;
+ }
+ split = -1;
+ for (i = 0; i <= pat_end; i++) {
+ if (pch_char(i) == '=') {
+ split = i;
+ break;
+ }
+ }
+ if (split == -1) {
+ fprintf(rejfp, "malformed hunk: no split found\n");
+ return;
+ }
+ i = 0;
+ j = split + 1;
+ fprintf(rejfp, "@@ -%ld,%ld +%ld,%ld @@\n",
+ pch_ptrn_lines() ? oldfirst : 0,
+ pch_ptrn_lines(), newfirst, pch_repl_lines());
+ while (i < split || j <= pat_end) {
+ ch1 = i < split ? pch_char(i) : -1;
+ ch2 = j <= pat_end ? pch_char(j) : -1;
+ if (ch1 == '-') {
+ rej_line('-', i);
+ i++;
+ } else if (ch1 == ' ' && ch2 == ' ') {
+ rej_line(' ', i);
+ i++;
+ j++;
+ } else if (ch1 == '!' && ch2 == '!') {
+ while (i < split && ch1 == '!') {
+ rej_line('-', i);
+ i++;
+ ch1 = i < split ? pch_char(i) : -1;
+ }
+ while (j <= pat_end && ch2 == '!') {
+ rej_line('+', j);
+ j++;
+ ch2 = j <= pat_end ? pch_char(j) : -1;
+ }
+ } else if (ch1 == '*') {
+ i++;
+ } else if (ch2 == '+' || ch2 == ' ') {
+ rej_line(ch2, j);
+ j++;
+ } else {
+ fprintf(rejfp, "internal error on (%ld %ld %ld)\n",
+ i, split, j);
+ rej_line(ch1, i);
+ rej_line(ch2, j);
+ return;
+ }
+ }
+}
+
+/* We found where to apply it (we hope), so do it. */
+
+static void
+apply_hunk(LINENUM where)
+{
+ LINENUM old = 1;
+ const LINENUM lastline = pch_ptrn_lines();
+ LINENUM new = lastline + 1;
+#define OUTSIDE 0
+#define IN_IFNDEF 1
+#define IN_IFDEF 2
+#define IN_ELSE 3
+ int def_state = OUTSIDE;
+ const LINENUM pat_end = pch_end();
+
+ where--;
+ while (pch_char(new) == '=' || pch_char(new) == '\n')
+ new++;
+
+ while (old <= lastline) {
+ if (pch_char(old) == '-') {
+ copy_till(where + old - 1, false);
+ if (do_defines) {
+ if (def_state == OUTSIDE) {
+ fputs(not_defined, ofp);
+ def_state = IN_IFNDEF;
+ } else if (def_state == IN_IFDEF) {
+ fputs(else_defined, ofp);
+ def_state = IN_ELSE;
+ }
+ fputs(pfetch(old), ofp);
+ }
+ last_frozen_line++;
+ old++;
+ } else if (new > pat_end) {
+ break;
+ } else if (pch_char(new) == '+') {
+ copy_till(where + old - 1, false);
+ if (do_defines) {
+ if (def_state == IN_IFNDEF) {
+ fputs(else_defined, ofp);
+ def_state = IN_ELSE;
+ } else if (def_state == OUTSIDE) {
+ fputs(if_defined, ofp);
+ def_state = IN_IFDEF;
+ }
+ }
+ fputs(pfetch(new), ofp);
+ new++;
+ } else if (pch_char(new) != pch_char(old)) {
+ say("Out-of-sync patch, lines %ld,%ld--mangled text or line numbers, maybe?\n",
+ pch_hunk_beg() + old,
+ pch_hunk_beg() + new);
+#ifdef DEBUGGING
+ say("oldchar = '%c', newchar = '%c'\n",
+ pch_char(old), pch_char(new));
+#endif
+ my_exit(2);
+ } else if (pch_char(new) == '!') {
+ copy_till(where + old - 1, false);
+ if (do_defines) {
+ fputs(not_defined, ofp);
+ def_state = IN_IFNDEF;
+ }
+ while (pch_char(old) == '!') {
+ if (do_defines) {
+ fputs(pfetch(old), ofp);
+ }
+ last_frozen_line++;
+ old++;
+ }
+ if (do_defines) {
+ fputs(else_defined, ofp);
+ def_state = IN_ELSE;
+ }
+ while (pch_char(new) == '!') {
+ fputs(pfetch(new), ofp);
+ new++;
+ }
+ } else {
+ if (pch_char(new) != ' ')
+ fatal("Internal error: expected ' '\n");
+ old++;
+ new++;
+ if (do_defines && def_state != OUTSIDE) {
+ fputs(end_defined, ofp);
+ def_state = OUTSIDE;
+ }
+ }
+ }
+ if (new <= pat_end && pch_char(new) == '+') {
+ copy_till(where + old - 1, false);
+ if (do_defines) {
+ if (def_state == OUTSIDE) {
+ fputs(if_defined, ofp);
+ def_state = IN_IFDEF;
+ } else if (def_state == IN_IFNDEF) {
+ fputs(else_defined, ofp);
+ def_state = IN_ELSE;
+ }
+ }
+ while (new <= pat_end && pch_char(new) == '+') {
+ fputs(pfetch(new), ofp);
+ new++;
+ }
+ }
+ if (do_defines && def_state != OUTSIDE) {
+ fputs(end_defined, ofp);
+ }
+}
+
+/*
+ * Open the new file.
+ */
+static void
+init_output(const char *name)
+{
+ ofp = fopen(name, "w");
+ if (ofp == NULL)
+ pfatal("can't create %s", name);
+}
+
+/*
+ * Open a file to put hunks we can't locate.
+ */
+static void
+init_reject(const char *name)
+{
+ rejfp = fopen(name, "w");
+ if (rejfp == NULL)
+ pfatal("can't create %s", name);
+}
+
+/*
+ * Copy input file to output, up to wherever hunk is to be applied.
+ * If endoffile is true, treat the last line specially since it may
+ * lack a newline.
+ */
+static void
+copy_till(LINENUM lastline, bool endoffile)
+{
+ if (last_frozen_line > lastline)
+ fatal("misordered hunks! output would be garbled\n");
+ while (last_frozen_line < lastline) {
+ if (++last_frozen_line == lastline && endoffile)
+ dump_line(last_frozen_line, !last_line_missing_eol);
+ else
+ dump_line(last_frozen_line, true);
+ }
+}
+
+/*
+ * Finish copying the input file to the output file.
+ */
+static bool
+spew_output(void)
+{
+ int rv;
+
+#ifdef DEBUGGING
+ if (debug & 256)
+ say("il=%ld lfl=%ld\n", input_lines, last_frozen_line);
+#endif
+ if (input_lines)
+ copy_till(input_lines, true); /* dump remainder of file */
+ rv = ferror(ofp) == 0 && fclose(ofp) == 0;
+ ofp = NULL;
+ return rv;
+}
+
+/*
+ * Copy one line from input to output.
+ */
+static void
+dump_line(LINENUM line, bool write_newline)
+{
+ char *s;
+
+ s = ifetch(line, 0);
+ if (s == NULL)
+ return;
+ /* Note: string is not NUL terminated. */
+ for (; *s != '\n'; s++)
+ putc(*s, ofp);
+ if (write_newline)
+ putc('\n', ofp);
+}
+
+/*
+ * Does the patch pattern match at line base+offset?
+ */
+static bool
+patch_match(LINENUM base, LINENUM offset, LINENUM fuzz)
+{
+ LINENUM pline = 1 + fuzz;
+ LINENUM iline;
+ LINENUM pat_lines = pch_ptrn_lines() - fuzz;
+ const char *ilineptr;
+ const char *plineptr;
+ short plinelen;
+
+ for (iline = base + offset + fuzz; pline <= pat_lines; pline++, iline++) {
+ ilineptr = ifetch(iline, offset >= 0);
+ if (ilineptr == NULL)
+ return false;
+ plineptr = pfetch(pline);
+ plinelen = pch_line_len(pline);
+ if (canonicalize) {
+ if (!similar(ilineptr, plineptr, plinelen))
+ return false;
+ } else if (strnNE(ilineptr, plineptr, plinelen))
+ return false;
+ if (iline == input_lines) {
+ /*
+ * We are looking at the last line of the file.
+ * If the file has no eol, the patch line should
+ * not have one either and vice-versa. Note that
+ * plinelen > 0.
+ */
+ if (last_line_missing_eol) {
+ if (plineptr[plinelen - 1] == '\n')
+ return false;
+ } else {
+ if (plineptr[plinelen - 1] != '\n')
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+/*
+ * Do two lines match with canonicalized white space?
+ */
+static bool
+similar(const char *a, const char *b, int len)
+{
+ while (len) {
+ if (isspace((unsigned char)*b)) { /* whitespace (or \n) to match? */
+ if (!isspace((unsigned char)*a)) /* no corresponding whitespace? */
+ return false;
+ while (len && isspace((unsigned char)*b) && *b != '\n')
+ b++, len--; /* skip pattern whitespace */
+ while (isspace((unsigned char)*a) && *a != '\n')
+ a++; /* skip target whitespace */
+ if (*a == '\n' || *b == '\n')
+ return (*a == *b); /* should end in sync */
+ } else if (*a++ != *b++) /* match non-whitespace chars */
+ return false;
+ else
+ len--; /* probably not necessary */
+ }
+ return true; /* actually, this is not reached */
+ /* since there is always a \n */
+}
diff --git a/usr.bin/patch/pathnames.h b/usr.bin/patch/pathnames.h
new file mode 100644
index 0000000..e9d3ccb
--- /dev/null
+++ b/usr.bin/patch/pathnames.h
@@ -0,0 +1,14 @@
+/*
+ * $OpenBSD: pathnames.h,v 1.1 2003/07/29 20:10:17 millert Exp $
+ * $DragonFly: src/usr.bin/patch/pathnames.h,v 1.2 2008/08/11 00:04:12 joerg Exp $
+ * $NetBSD: pathnames.h,v 1.1 2008/09/19 18:33:34 joerg Exp $
+ */
+
+/*
+ * Placed in the public domain by Todd C. Miller <Todd.Miller@courtesan.com>
+ * on July 29, 2003.
+ */
+
+#include <paths.h>
+
+#define _PATH_ED "/bin/ed"
diff --git a/usr.bin/patch/pch.c b/usr.bin/patch/pch.c
new file mode 100644
index 0000000..8a635ad
--- /dev/null
+++ b/usr.bin/patch/pch.c
@@ -0,0 +1,1600 @@
+/*
+ * $OpenBSD: pch.c,v 1.37 2007/09/02 15:19:33 deraadt Exp $
+ * $DragonFly: src/usr.bin/patch/pch.c,v 1.6 2008/08/10 23:35:40 joerg Exp $
+ * $NetBSD: pch.c,v 1.30 2018/06/18 18:33:31 christos Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: pch.c,v 1.30 2018/06/18 18:33:31 christos Exp $");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "util.h"
+#include "pch.h"
+#include "pathnames.h"
+
+/* Patch (diff listing) abstract type. */
+
+static long p_filesize; /* size of the patch file */
+static LINENUM p_first; /* 1st line number */
+static LINENUM p_newfirst; /* 1st line number of replacement */
+static LINENUM p_ptrn_lines; /* # lines in pattern */
+static LINENUM p_repl_lines; /* # lines in replacement text */
+static LINENUM p_end = -1; /* last line in hunk */
+static LINENUM p_max; /* max allowed value of p_end */
+static LINENUM p_context = 3; /* # of context lines */
+static LINENUM p_input_line = 0; /* current line # from patch file */
+static char **p_line = NULL;/* the text of the hunk */
+static short *p_len = NULL; /* length of each line */
+static char *p_char = NULL; /* +, -, and ! */
+static int hunkmax = INITHUNKMAX; /* size of above arrays to begin with */
+static int p_indent; /* indent to patch */
+static LINENUM p_base; /* where to intuit this time */
+static LINENUM p_bline; /* line # of p_base */
+static LINENUM p_start; /* where intuit found a patch */
+static LINENUM p_sline; /* and the line number for it */
+static LINENUM p_hunk_beg; /* line number of current hunk */
+static LINENUM p_efake = -1; /* end of faked up lines--don't free */
+static LINENUM p_bfake = -1; /* beg of faked up lines */
+static FILE *pfp = NULL; /* patch file pointer */
+static char *bestguess = NULL; /* guess at correct filename */
+
+static void grow_hunkmax(void);
+static int intuit_diff_type(void);
+static void next_intuit_at(LINENUM, LINENUM);
+static void skip_to(LINENUM, LINENUM);
+static char *pgets(char *, int, FILE *);
+static char *best_name(const struct file_name *, bool);
+static char *posix_name(const struct file_name *, bool);
+static size_t num_components(const char *);
+
+/*
+ * Prepare to look for the next patch in the patch file.
+ */
+void
+re_patch(void)
+{
+ p_first = 0;
+ p_newfirst = 0;
+ p_ptrn_lines = 0;
+ p_repl_lines = 0;
+ p_end = (LINENUM) - 1;
+ p_max = 0;
+ p_indent = 0;
+}
+
+/*
+ * Open the patch file at the beginning of time.
+ */
+void
+open_patch_file(const char *filename)
+{
+ struct stat filestat;
+
+ if (filename == NULL || *filename == '\0' || strEQ(filename, "-")) {
+ pfp = fopen(TMPPATNAME, "w");
+ if (pfp == NULL)
+ pfatal("can't create %s", TMPPATNAME);
+ while (fgets(buf, buf_len, stdin) != NULL)
+ fputs(buf, pfp);
+ if (ferror(pfp) || fclose(pfp))
+ pfatal("can't write %s", TMPPATNAME);
+ filename = TMPPATNAME;
+ }
+ pfp = fopen(filename, "r");
+ if (pfp == NULL)
+ pfatal("patch file %s not found", filename);
+ fstat(fileno(pfp), &filestat);
+ p_filesize = filestat.st_size;
+ next_intuit_at(0L, 1L); /* start at the beginning */
+ set_hunkmax();
+}
+
+/*
+ * Make sure our dynamically realloced tables are malloced to begin with.
+ */
+void
+set_hunkmax(void)
+{
+ if (p_line == NULL)
+ p_line = calloc((size_t) hunkmax, sizeof(char *));
+ if (p_len == NULL)
+ p_len = calloc((size_t) hunkmax, sizeof(short));
+ if (p_char == NULL)
+ p_char = calloc((size_t) hunkmax, sizeof(char));
+}
+
+/*
+ * Enlarge the arrays containing the current hunk of patch.
+ */
+static void
+grow_hunkmax(void)
+{
+ int new_hunkmax;
+ char **new_p_line;
+ short *new_p_len;
+ char *new_p_char;
+
+ new_hunkmax = hunkmax * 2;
+
+ if (p_line == NULL || p_len == NULL || p_char == NULL)
+ fatal("Internal memory allocation error\n");
+
+ new_p_line = pch_realloc(p_line, new_hunkmax, sizeof(char *));
+ if (new_p_line == NULL)
+ free(p_line);
+
+ new_p_len = pch_realloc(p_len, new_hunkmax, sizeof(short));
+ if (new_p_len == NULL)
+ free(p_len);
+
+ new_p_char = pch_realloc(p_char, new_hunkmax, sizeof(char));
+ if (new_p_char == NULL)
+ free(p_char);
+
+ p_char = new_p_char;
+ p_len = new_p_len;
+ p_line = new_p_line;
+
+ if (p_line != NULL && p_len != NULL && p_char != NULL) {
+ hunkmax = new_hunkmax;
+ return;
+ }
+
+ if (!using_plan_a)
+ fatal("out of memory\n");
+ out_of_mem = true; /* whatever is null will be allocated again */
+ /* from within plan_a(), of all places */
+}
+
+/* True if the remainder of the patch file contains a diff of some sort. */
+
+bool
+there_is_another_patch(void)
+{
+ bool exists = false;
+
+ if (p_base != 0L && p_base >= p_filesize) {
+ if (verbose)
+ say("done\n");
+ return false;
+ }
+ if (verbose)
+ say("Hmm...");
+ diff_type = intuit_diff_type();
+ if (!diff_type) {
+ if (p_base != 0L) {
+ if (verbose)
+ say(" Ignoring the trailing garbage.\ndone\n");
+ } else
+ say(" I can't seem to find a patch in there anywhere.\n");
+ return false;
+ }
+ if (verbose)
+ say(" %sooks like %s to me...\n",
+ (p_base == 0L ? "L" : "The next patch l"),
+ diff_type == UNI_DIFF ? "a unified diff" :
+ diff_type == CONTEXT_DIFF ? "a context diff" :
+ diff_type == NEW_CONTEXT_DIFF ? "a new-style context diff" :
+ diff_type == NORMAL_DIFF ? "a normal diff" :
+ "an ed script");
+ if (p_indent && verbose)
+ say("(Patch is indented %d space%s.)\n", p_indent,
+ p_indent == 1 ? "" : "s");
+ skip_to(p_start, p_sline);
+ while (filearg[0] == NULL) {
+ if (force || batch) {
+ say("No file to patch. Skipping...\n");
+ filearg[0] = savestr(bestguess);
+ skip_rest_of_patch = true;
+ return true;
+ }
+ ask("File to patch: ");
+ if (*buf != '\n') {
+ free(bestguess);
+ bestguess = savestr(buf);
+ filearg[0] = fetchname(buf, &exists, 0);
+ }
+ if (!exists) {
+ ask("No file found--skip this patch? [n] ");
+ if (*buf != 'y')
+ continue;
+ if (verbose)
+ say("Skipping patch...\n");
+ free(filearg[0]);
+ filearg[0] = fetchname(bestguess, &exists, 0);
+ skip_rest_of_patch = true;
+ return true;
+ }
+ }
+ return true;
+}
+
+/* Determine what kind of diff is in the remaining part of the patch file. */
+
+static int
+intuit_diff_type(void)
+{
+ long this_line = 0, previous_line;
+ long first_command_line = -1;
+ LINENUM fcl_line = -1;
+ bool last_line_was_command = false, this_is_a_command = false;
+ bool stars_last_line = false, stars_this_line = false;
+ char *s, *t;
+ int indent, retval;
+ struct file_name names[MAX_FILE];
+
+ memset(names, 0, sizeof(names));
+ ok_to_create_file = false;
+ fseek(pfp, p_base, SEEK_SET);
+ p_input_line = p_bline - 1;
+ for (;;) {
+ previous_line = this_line;
+ last_line_was_command = this_is_a_command;
+ stars_last_line = stars_this_line;
+ this_line = ftell(pfp);
+ indent = 0;
+ p_input_line++;
+ if (fgets(buf, buf_len, pfp) == NULL) {
+ if (first_command_line >= 0L) {
+ /* nothing but deletes!? */
+ p_start = first_command_line;
+ p_sline = fcl_line;
+ retval = ED_DIFF;
+ goto scan_exit;
+ } else {
+ p_start = this_line;
+ p_sline = p_input_line;
+ retval = 0;
+ goto scan_exit;
+ }
+ }
+ for (s = buf; *s == ' ' || *s == '\t' || *s == 'X'; s++) {
+ if (*s == '\t')
+ indent += 8 - (indent % 8);
+ else
+ indent++;
+ }
+ for (t = s; isdigit((unsigned char)*t) || *t == ','; t++)
+ ;
+ this_is_a_command = (isdigit((unsigned char)*s) &&
+ (*t == 'd' || *t == 'c' || *t == 'a'));
+ if (first_command_line < 0L && this_is_a_command) {
+ first_command_line = this_line;
+ fcl_line = p_input_line;
+ p_indent = indent; /* assume this for now */
+ }
+ if (!stars_last_line && strnEQ(s, "*** ", 4))
+ names[OLD_FILE].path = fetchname(s + 4,
+ &names[OLD_FILE].exists, strippath);
+ else if (strnEQ(s, "--- ", 4))
+ names[NEW_FILE].path = fetchname(s + 4,
+ &names[NEW_FILE].exists, strippath);
+ else if (strnEQ(s, "+++ ", 4))
+ /* pretend it is the old name */
+ names[OLD_FILE].path = fetchname(s + 4,
+ &names[OLD_FILE].exists, strippath);
+ else if (strnEQ(s, "Index:", 6))
+ names[INDEX_FILE].path = fetchname(s + 6,
+ &names[INDEX_FILE].exists, strippath);
+ else if (strnEQ(s, "Prereq:", 7)) {
+ for (t = s + 7; isspace((unsigned char)*t); t++)
+ ;
+ revision = savestr(t);
+ for (t = revision; *t && !isspace((unsigned char)*t); t++)
+ ;
+ *t = '\0';
+ if (*revision == '\0') {
+ free(revision);
+ revision = NULL;
+ }
+ }
+ if ((!diff_type || diff_type == ED_DIFF) &&
+ first_command_line >= 0L &&
+ strEQ(s, ".\n")) {
+ p_indent = indent;
+ p_start = first_command_line;
+ p_sline = fcl_line;
+ retval = ED_DIFF;
+ goto scan_exit;
+ }
+ if ((!diff_type || diff_type == UNI_DIFF) && strnEQ(s, "@@ -", 4)) {
+ if (strnEQ(s + 4, "0,0", 3))
+ ok_to_create_file = true;
+ p_indent = indent;
+ p_start = this_line;
+ p_sline = p_input_line;
+ retval = UNI_DIFF;
+ goto scan_exit;
+ }
+ stars_this_line = strnEQ(s, "********", 8);
+ if ((!diff_type || diff_type == CONTEXT_DIFF) && stars_last_line &&
+ strnEQ(s, "*** ", 4)) {
+ if (atol(s + 4) == 0)
+ ok_to_create_file = true;
+ /*
+ * If this is a new context diff the character just
+ * before the newline is a '*'.
+ */
+ while (*s != '\n')
+ s++;
+ p_indent = indent;
+ p_start = previous_line;
+ p_sline = p_input_line - 1;
+ retval = (*(s - 1) == '*' ? NEW_CONTEXT_DIFF : CONTEXT_DIFF);
+ goto scan_exit;
+ }
+ if ((!diff_type || diff_type == NORMAL_DIFF) &&
+ last_line_was_command &&
+ (strnEQ(s, "< ", 2) || strnEQ(s, "> ", 2))) {
+ p_start = previous_line;
+ p_sline = p_input_line - 1;
+ p_indent = indent;
+ retval = NORMAL_DIFF;
+ goto scan_exit;
+ }
+ }
+scan_exit:
+ if (retval == UNI_DIFF) {
+ /* unswap old and new */
+ struct file_name tmp = names[OLD_FILE];
+ names[OLD_FILE] = names[NEW_FILE];
+ names[NEW_FILE] = tmp;
+ }
+ if (filearg[0] == NULL) {
+ if (posix)
+ filearg[0] = posix_name(names, ok_to_create_file);
+ else {
+ /* Ignore the Index: name for context diffs, like GNU */
+ if (names[OLD_FILE].path != NULL ||
+ names[NEW_FILE].path != NULL) {
+ free(names[INDEX_FILE].path);
+ names[INDEX_FILE].path = NULL;
+ }
+ filearg[0] = best_name(names, ok_to_create_file);
+ }
+ }
+
+ free(bestguess);
+ bestguess = NULL;
+ if (filearg[0] != NULL)
+ bestguess = savestr(filearg[0]);
+ else if (!ok_to_create_file) {
+ /*
+ * We don't want to create a new file but we need a
+ * filename to set bestguess. Avoid setting filearg[0]
+ * so the file is not created automatically.
+ */
+ if (posix)
+ bestguess = posix_name(names, true);
+ else
+ bestguess = best_name(names, true);
+ }
+ free(names[OLD_FILE].path);
+ free(names[NEW_FILE].path);
+ free(names[INDEX_FILE].path);
+ return retval;
+}
+
+/*
+ * Remember where this patch ends so we know where to start up again.
+ */
+static void
+next_intuit_at(LINENUM file_pos, LINENUM file_line)
+{
+ p_base = file_pos;
+ p_bline = file_line;
+}
+
+/*
+ * Basically a verbose fseek() to the actual diff listing.
+ */
+static void
+skip_to(LINENUM file_pos, LINENUM file_line)
+{
+ char *ret;
+
+ if (p_base > file_pos)
+ fatal("Internal error: seek %ld>%ld\n", p_base, file_pos);
+ if (verbose && p_base < file_pos) {
+ fseek(pfp, p_base, SEEK_SET);
+ say("The text leading up to this was:\n--------------------------\n");
+ while (ftell(pfp) < file_pos) {
+ ret = fgets(buf, buf_len, pfp);
+ if (ret == NULL)
+ fatal("Unexpected end of file\n");
+ say("|%s", buf);
+ }
+ say("--------------------------\n");
+ } else
+ fseek(pfp, file_pos, SEEK_SET);
+ p_input_line = file_line - 1;
+}
+
+/* Make this a function for better debugging. */
+__dead static void
+malformed(void)
+{
+ fatal("malformed patch at line %ld: %s", p_input_line, buf);
+ /* about as informative as "Syntax error" in C */
+}
+
+static LINENUM
+getlinenum(const char *s)
+{
+ LINENUM l = (LINENUM)atol(s);
+ if (l < 0) {
+ l = 0;
+ malformed();
+ }
+ return l;
+}
+
+static LINENUM
+getskiplinenum(char **p)
+{
+ char *s = *p;
+ LINENUM l = getlinenum(s);
+ while (isdigit((unsigned char)*s))
+ s++;
+ *p = s;
+ return l;
+}
+
+/*
+ * True if the line has been discarded (i.e., it is a line saying
+ * "\ No newline at end of file".)
+ */
+static bool
+remove_special_line(void)
+{
+ int c;
+
+ c = fgetc(pfp);
+ if (c == '\\') {
+ do {
+ c = fgetc(pfp);
+ } while (c != EOF && c != '\n');
+
+ return true;
+ }
+ if (c != EOF)
+ fseek(pfp, -1L, SEEK_CUR);
+
+ return false;
+}
+
+/*
+ * True if there is more of the current diff listing to process.
+ */
+bool
+another_hunk(void)
+{
+ long line_beginning; /* file pos of the current line */
+ LINENUM repl_beginning; /* index of --- line */
+ LINENUM fillcnt; /* #lines of missing ptrn or repl */
+ LINENUM fillsrc; /* index of first line to copy */
+ LINENUM filldst; /* index of first missing line */
+ bool ptrn_spaces_eaten; /* ptrn was slightly misformed */
+ bool repl_could_be_missing; /* no + or ! lines in this hunk */
+ bool repl_missing; /* we are now backtracking */
+ long repl_backtrack_position; /* file pos of first repl line */
+ LINENUM repl_patch_line; /* input line number for same */
+ LINENUM ptrn_copiable; /* # of copiable lines in ptrn */
+ char *s, *ret;
+ int context = 0;
+
+ while (p_end >= 0) {
+ if (p_end == p_efake)
+ p_end = p_bfake; /* don't free twice */
+ else
+ free(p_line[p_end]);
+ p_end--;
+ }
+ p_efake = -1;
+
+ p_max = hunkmax; /* gets reduced when --- found */
+ if (diff_type == CONTEXT_DIFF || diff_type == NEW_CONTEXT_DIFF) {
+ line_beginning = ftell(pfp);
+ repl_beginning = 0;
+ fillcnt = 0;
+ fillsrc = 0;
+ filldst = 0;
+ ptrn_spaces_eaten = false;
+ repl_could_be_missing = true;
+ repl_missing = false;
+ repl_backtrack_position = 0;
+ repl_patch_line = 0;
+ ptrn_copiable = 0;
+
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL || strnNE(buf, "********", 8)) {
+ next_intuit_at(line_beginning, p_input_line);
+ return false;
+ }
+ p_context = 100;
+ p_hunk_beg = p_input_line + 1;
+ while (p_end < p_max) {
+ line_beginning = ftell(pfp);
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL) {
+ if (p_max - p_end < 4) {
+ /* assume blank lines got chopped */
+ strlcpy(buf, " \n", buf_len);
+ } else {
+ if (repl_beginning && repl_could_be_missing) {
+ repl_missing = true;
+ goto hunk_done;
+ }
+ fatal("unexpected end of file in patch\n");
+ }
+ }
+ p_end++;
+ if (p_end >= hunkmax)
+ fatal("Internal error: hunk larger than hunk "
+ "buffer size");
+ p_char[p_end] = *buf;
+ p_line[p_end] = NULL;
+ switch (*buf) {
+ case '*':
+ if (strnEQ(buf, "********", 8)) {
+ if (repl_beginning && repl_could_be_missing) {
+ repl_missing = true;
+ goto hunk_done;
+ } else
+ fatal("unexpected end of hunk "
+ "at line %ld\n",
+ p_input_line);
+ }
+ if (p_end != 0) {
+ if (repl_beginning && repl_could_be_missing) {
+ repl_missing = true;
+ goto hunk_done;
+ }
+ fatal("unexpected *** at line %ld: %s",
+ p_input_line, buf);
+ }
+ context = 0;
+ p_line[p_end] = savestr(buf);
+ if (out_of_mem) {
+ p_end--;
+ return false;
+ }
+ for (s = buf; *s && !isdigit((unsigned char)*s); s++)
+ ;
+ if (!*s)
+ malformed();
+ if (strnEQ(s, "0,0", 3))
+ memmove(s, s + 2, strlen(s + 2) + 1);
+ p_first = getskiplinenum(&s);
+ if (*s == ',') {
+ for (; *s && !isdigit((unsigned char)*s); s++)
+ ;
+ if (!*s)
+ malformed();
+ p_ptrn_lines = (getlinenum(s)) - p_first + 1;
+ if (p_ptrn_lines < 0)
+ malformed();
+ } else if (p_first)
+ p_ptrn_lines = 1;
+ else {
+ p_ptrn_lines = 0;
+ p_first = 1;
+ }
+ if (p_first >= LINENUM_MAX - p_ptrn_lines ||
+ p_ptrn_lines >= LINENUM_MAX - 6)
+ malformed();
+
+ /* we need this much at least */
+ p_max = p_ptrn_lines + 6;
+ while (p_max >= hunkmax)
+ grow_hunkmax();
+ p_max = hunkmax;
+ break;
+ case '-':
+ if (buf[1] == '-') {
+ if (repl_beginning ||
+ (p_end != p_ptrn_lines + 1 +
+ (p_char[p_end - 1] == '\n'))) {
+ if (p_end == 1) {
+ /*
+ * `old' lines were omitted;
+ * set up to fill them in
+ * from 'new' context lines.
+ */
+ p_end = p_ptrn_lines + 1;
+ fillsrc = p_end + 1;
+ filldst = 1;
+ fillcnt = p_ptrn_lines;
+ } else {
+ if (repl_beginning) {
+ if (repl_could_be_missing) {
+ repl_missing = true;
+ goto hunk_done;
+ }
+ fatal("duplicate \"---\" at line %ld--check line numbers at line %ld\n",
+ p_input_line, p_hunk_beg + repl_beginning);
+ } else {
+ fatal("%s \"---\" at line %ld--check line numbers at line %ld\n",
+ (p_end <= p_ptrn_lines
+ ? "Premature"
+ : "Overdue"),
+ p_input_line, p_hunk_beg);
+ }
+ }
+ }
+ repl_beginning = p_end;
+ repl_backtrack_position = ftell(pfp);
+ repl_patch_line = p_input_line;
+ p_line[p_end] = savestr(buf);
+ if (out_of_mem) {
+ p_end--;
+ return false;
+ }
+ p_char[p_end] = '=';
+ for (s = buf; *s && !isdigit((unsigned char)*s); s++)
+ ;
+ if (!*s)
+ malformed();
+ p_newfirst = getskiplinenum(&s);
+ if (*s == ',') {
+ for (; *s && !isdigit((unsigned char)*s); s++)
+ ;
+ if (!*s)
+ malformed();
+ p_repl_lines = (getlinenum(s)) -
+ p_newfirst + 1;
+ if (p_repl_lines < 0)
+ malformed();
+ } else if (p_newfirst)
+ p_repl_lines = 1;
+ else {
+ p_repl_lines = 0;
+ p_newfirst = 1;
+ }
+ if (p_newfirst >= LINENUM_MAX - p_repl_lines ||
+ p_repl_lines >= LINENUM_MAX - p_end)
+ malformed();
+ p_max = p_repl_lines + p_end;
+ if (p_max > MAXHUNKSIZE)
+ fatal("hunk too large (%ld lines) at line %ld: %s",
+ p_max, p_input_line, buf);
+ while (p_max >= hunkmax)
+ grow_hunkmax();
+ if (p_repl_lines != ptrn_copiable &&
+ (p_context != 0 || p_repl_lines != 1))
+ repl_could_be_missing = false;
+ break;
+ }
+ goto change_line;
+ case '+':
+ case '!':
+ repl_could_be_missing = false;
+ change_line:
+ if (buf[1] == '\n' && canonicalize)
+ strlcpy(buf + 1, " \n", buf_len - 1);
+ if (!isspace((unsigned char)buf[1]) && buf[1] != '>' &&
+ buf[1] != '<' &&
+ repl_beginning && repl_could_be_missing) {
+ repl_missing = true;
+ goto hunk_done;
+ }
+ if (context >= 0) {
+ if (context < p_context)
+ p_context = context;
+ context = -1000;
+ }
+ p_line[p_end] = savestr(buf + 2);
+ if (out_of_mem) {
+ p_end--;
+ return false;
+ }
+ if (p_end == p_ptrn_lines) {
+ if (remove_special_line()) {
+ int len;
+
+ len = strlen(p_line[p_end]) - 1;
+ (p_line[p_end])[len] = 0;
+ }
+ }
+ break;
+ case '\t':
+ case '\n': /* assume the 2 spaces got eaten */
+ if (repl_beginning && repl_could_be_missing &&
+ (!ptrn_spaces_eaten ||
+ diff_type == NEW_CONTEXT_DIFF)) {
+ repl_missing = true;
+ goto hunk_done;
+ }
+ p_line[p_end] = savestr(buf);
+ if (out_of_mem) {
+ p_end--;
+ return false;
+ }
+ if (p_end != p_ptrn_lines + 1) {
+ ptrn_spaces_eaten |= (repl_beginning != 0);
+ context++;
+ if (!repl_beginning)
+ ptrn_copiable++;
+ p_char[p_end] = ' ';
+ }
+ break;
+ case ' ':
+ if (!isspace((unsigned char)buf[1]) &&
+ repl_beginning && repl_could_be_missing) {
+ repl_missing = true;
+ goto hunk_done;
+ }
+ context++;
+ if (!repl_beginning)
+ ptrn_copiable++;
+ p_line[p_end] = savestr(buf + 2);
+ if (out_of_mem) {
+ p_end--;
+ return false;
+ }
+ break;
+ default:
+ if (repl_beginning && repl_could_be_missing) {
+ repl_missing = true;
+ goto hunk_done;
+ }
+ malformed();
+ }
+ /* set up p_len for strncmp() so we don't have to */
+ /* assume null termination */
+ if (p_line[p_end])
+ p_len[p_end] = strlen(p_line[p_end]);
+ else
+ p_len[p_end] = 0;
+ }
+
+hunk_done:
+ if (p_end >= 0 && !repl_beginning)
+ fatal("no --- found in patch at line %ld\n", pch_hunk_beg());
+
+ if (repl_missing) {
+
+ /* reset state back to just after --- */
+ p_input_line = repl_patch_line;
+ for (p_end--; p_end > repl_beginning; p_end--)
+ free(p_line[p_end]);
+ fseek(pfp, repl_backtrack_position, SEEK_SET);
+
+ /* redundant 'new' context lines were omitted - set */
+ /* up to fill them in from the old file context */
+ if (!p_context && p_repl_lines == 1) {
+ p_repl_lines = 0;
+ p_max--;
+ }
+ fillsrc = 1;
+ filldst = repl_beginning + 1;
+ fillcnt = p_repl_lines;
+ p_end = p_max;
+ } else if (!p_context && fillcnt == 1) {
+ /* the first hunk was a null hunk with no context */
+ /* and we were expecting one line -- fix it up. */
+ while (filldst < p_end) {
+ p_line[filldst] = p_line[filldst + 1];
+ p_char[filldst] = p_char[filldst + 1];
+ p_len[filldst] = p_len[filldst + 1];
+ filldst++;
+ }
+#if 0
+ repl_beginning--; /* this doesn't need to be fixed */
+#endif
+ p_end--;
+ p_first++; /* do append rather than insert */
+ fillcnt = 0;
+ p_ptrn_lines = 0;
+ }
+ if (diff_type == CONTEXT_DIFF &&
+ (fillcnt || (p_first > 1 && ptrn_copiable > 2 * p_context))) {
+ if (verbose)
+ say("%s\n%s\n%s\n",
+ "(Fascinating--this is really a new-style context diff but without",
+ "the telltale extra asterisks on the *** line that usually indicate",
+ "the new style...)");
+ diff_type = NEW_CONTEXT_DIFF;
+ }
+ /* if there were omitted context lines, fill them in now */
+ if (fillcnt) {
+ p_bfake = filldst; /* remember where not to free() */
+ p_efake = filldst + fillcnt - 1;
+ while (fillcnt-- > 0) {
+ while (fillsrc <= p_end && p_char[fillsrc] != ' ')
+ fillsrc++;
+ if (fillsrc > p_end)
+ fatal("replacement text or line numbers mangled in hunk at line %ld\n",
+ p_hunk_beg);
+ p_line[filldst] = p_line[fillsrc];
+ p_char[filldst] = p_char[fillsrc];
+ p_len[filldst] = p_len[fillsrc];
+ fillsrc++;
+ filldst++;
+ }
+ while (fillsrc <= p_end && fillsrc != repl_beginning &&
+ p_char[fillsrc] != ' ')
+ fillsrc++;
+#ifdef DEBUGGING
+ if (debug & 64)
+ printf("fillsrc %ld, filldst %ld, rb %ld, e+1 %ld\n",
+ fillsrc, filldst, repl_beginning, p_end + 1);
+#endif
+ if (fillsrc != p_end + 1 && fillsrc != repl_beginning)
+ malformed();
+ if (filldst != p_end + 1 && filldst != repl_beginning)
+ malformed();
+ }
+ if (p_line[p_end] != NULL) {
+ if (remove_special_line()) {
+ p_len[p_end] -= 1;
+ (p_line[p_end])[p_len[p_end]] = 0;
+ }
+ }
+ } else if (diff_type == UNI_DIFF) {
+ LINENUM fillold; /* index of old lines */
+ LINENUM fillnew; /* index of new lines */
+ char ch;
+
+ line_beginning = ftell(pfp); /* file pos of the current line */
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL || strnNE(buf, "@@ -", 4)) {
+ next_intuit_at(line_beginning, p_input_line);
+ return false;
+ }
+ s = buf + 4;
+ if (!*s)
+ malformed();
+ p_first = getskiplinenum(&s);
+ if (*s == ',') {
+ s++;
+ p_ptrn_lines = getskiplinenum(&s);
+ } else
+ p_ptrn_lines = 1;
+ if (p_first >= LINENUM_MAX - p_ptrn_lines)
+ malformed();
+ if (*s == ' ')
+ s++;
+ if (*s != '+' || !*++s)
+ malformed();
+ p_newfirst = getskiplinenum(&s);
+ if (*s == ',') {
+ s++;
+ p_repl_lines = getskiplinenum(&s);
+ } else
+ p_repl_lines = 1;
+ if (*s == ' ')
+ s++;
+ if (*s != '@')
+ malformed();
+ if (p_first >= LINENUM_MAX - p_ptrn_lines ||
+ p_newfirst > LINENUM_MAX - p_repl_lines ||
+ p_ptrn_lines >= LINENUM_MAX - p_repl_lines - 1)
+ malformed();
+ if (!p_ptrn_lines)
+ p_first++; /* do append rather than insert */
+ p_max = p_ptrn_lines + p_repl_lines + 1;
+ while (p_max >= hunkmax)
+ grow_hunkmax();
+ fillold = 1;
+ fillnew = fillold + p_ptrn_lines;
+ p_end = fillnew + p_repl_lines;
+ snprintf(buf, buf_len, "*** %ld,%ld ****\n", p_first,
+ p_first + p_ptrn_lines - 1);
+ p_line[0] = savestr(buf);
+ if (out_of_mem) {
+ p_end = -1;
+ return false;
+ }
+ p_char[0] = '*';
+ snprintf(buf, buf_len, "--- %ld,%ld ----\n", p_newfirst,
+ p_newfirst + p_repl_lines - 1);
+ p_line[fillnew] = savestr(buf);
+ if (out_of_mem) {
+ p_end = 0;
+ return false;
+ }
+ p_char[fillnew++] = '=';
+ p_context = 100;
+ context = 0;
+ p_hunk_beg = p_input_line + 1;
+ while (fillold <= p_ptrn_lines || fillnew <= p_end) {
+ line_beginning = ftell(pfp);
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL) {
+ if (p_max - fillnew < 3) {
+ /* assume blank lines got chopped */
+ strlcpy(buf, " \n", buf_len);
+ } else {
+ fatal("unexpected end of file in patch\n");
+ }
+ }
+ if (*buf == '\t' || *buf == '\n') {
+ ch = ' '; /* assume the space got eaten */
+ s = savestr(buf);
+ } else {
+ ch = *buf;
+ s = savestr(buf + 1);
+ }
+ if (out_of_mem) {
+ while (--fillnew > p_ptrn_lines)
+ free(p_line[fillnew]);
+ p_end = fillold - 1;
+ return false;
+ }
+ switch (ch) {
+ case '-':
+ if (fillold > p_ptrn_lines) {
+ free(s);
+ p_end = fillnew - 1;
+ malformed();
+ }
+ p_char[fillold] = ch;
+ p_line[fillold] = s;
+ p_len[fillold++] = strlen(s);
+ if (fillold > p_ptrn_lines) {
+ if (remove_special_line()) {
+ p_len[fillold - 1] -= 1;
+ s[p_len[fillold - 1]] = 0;
+ }
+ }
+ break;
+ case '=':
+ ch = ' ';
+ /* FALL THROUGH */
+ case ' ':
+ if (fillold > p_ptrn_lines) {
+ free(s);
+ while (--fillnew > p_ptrn_lines)
+ free(p_line[fillnew]);
+ p_end = fillold - 1;
+ malformed();
+ }
+ context++;
+ p_char[fillold] = ch;
+ p_line[fillold] = s;
+ p_len[fillold++] = strlen(s);
+ s = savestr(s);
+ if (out_of_mem) {
+ while (--fillnew > p_ptrn_lines)
+ free(p_line[fillnew]);
+ p_end = fillold - 1;
+ return false;
+ }
+ if (fillold > p_ptrn_lines) {
+ if (remove_special_line()) {
+ p_len[fillold - 1] -= 1;
+ s[p_len[fillold - 1]] = 0;
+ }
+ }
+ /* FALL THROUGH */
+ case '+':
+ if (fillnew > p_end) {
+ free(s);
+ while (--fillnew > p_ptrn_lines)
+ free(p_line[fillnew]);
+ p_end = fillold - 1;
+ malformed();
+ }
+ p_char[fillnew] = ch;
+ p_line[fillnew] = s;
+ p_len[fillnew++] = strlen(s);
+ if (fillold > p_ptrn_lines) {
+ if (remove_special_line()) {
+ p_len[fillnew - 1] -= 1;
+ s[p_len[fillnew - 1]] = 0;
+ }
+ }
+ break;
+ default:
+ p_end = fillnew;
+ malformed();
+ }
+ if (ch != ' ' && context > 0) {
+ if (context < p_context)
+ p_context = context;
+ context = -1000;
+ }
+ } /* while */
+ } else { /* normal diff--fake it up */
+ char hunk_type;
+ int i;
+ LINENUM min, max;
+
+ line_beginning = ftell(pfp);
+ p_context = 0;
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL || !isdigit((unsigned char)*buf)) {
+ next_intuit_at(line_beginning, p_input_line);
+ return false;
+ }
+ s = buf;
+ p_first = getskiplinenum(&s);
+ if (*s == ',') {
+ s++;
+ p_ptrn_lines = getskiplinenum(&s) - p_first + 1;
+ } else
+ p_ptrn_lines = (*s != 'a');
+ if (p_first >= LINENUM_MAX - p_ptrn_lines)
+ malformed();
+ hunk_type = *s++;
+ if (hunk_type == 'a')
+ p_first++; /* do append rather than insert */
+ min = getskiplinenum(&s);
+ if (*s == ',')
+ max = getlinenum(++s);
+ else
+ max = min;
+ if (min < 0 || min > max || max - min == LINENUM_MAX)
+ malformed();
+ if (hunk_type == 'd')
+ min++;
+ p_end = p_ptrn_lines + 1 + max - min + 1;
+ p_newfirst = min;
+ p_repl_lines = max - min + 1;
+ if (p_newfirst > LINENUM_MAX - p_repl_lines ||
+ p_ptrn_lines >= LINENUM_MAX - p_repl_lines - 1)
+ malformed();
+ p_end = p_ptrn_lines + p_repl_lines + 1;
+ if (p_end > MAXHUNKSIZE)
+ fatal("hunk too large (%ld lines) at line %ld: %s",
+ p_end, p_input_line, buf);
+ while (p_end >= hunkmax)
+ grow_hunkmax();
+ snprintf(buf, buf_len, "*** %ld,%ld\n", p_first,
+ p_first + p_ptrn_lines - 1);
+ p_line[0] = savestr(buf);
+ if (out_of_mem) {
+ p_end = -1;
+ return false;
+ }
+ p_char[0] = '*';
+ for (i = 1; i <= p_ptrn_lines; i++) {
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL)
+ fatal("unexpected end of file in patch at line %ld\n",
+ p_input_line);
+ if (*buf != '<')
+ fatal("< expected at line %ld of patch\n",
+ p_input_line);
+ p_line[i] = savestr(buf + 2);
+ if (out_of_mem) {
+ p_end = i - 1;
+ return false;
+ }
+ p_len[i] = strlen(p_line[i]);
+ p_char[i] = '-';
+ }
+
+ if (remove_special_line()) {
+ p_len[i - 1] -= 1;
+ (p_line[i - 1])[p_len[i - 1]] = 0;
+ }
+ if (hunk_type == 'c') {
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL)
+ fatal("unexpected end of file in patch at line %ld\n",
+ p_input_line);
+ if (*buf != '-')
+ fatal("--- expected at line %ld of patch\n",
+ p_input_line);
+ }
+ snprintf(buf, buf_len, "--- %ld,%ld\n", min, max);
+ p_line[i] = savestr(buf);
+ if (out_of_mem) {
+ p_end = i - 1;
+ return false;
+ }
+ p_char[i] = '=';
+ for (i++; i <= p_end; i++) {
+ ret = pgets(buf, buf_len, pfp);
+ p_input_line++;
+ if (ret == NULL)
+ fatal("unexpected end of file in patch at line %ld\n",
+ p_input_line);
+ if (*buf != '>')
+ fatal("> expected at line %ld of patch\n",
+ p_input_line);
+ p_line[i] = savestr(buf + 2);
+ if (out_of_mem) {
+ p_end = i - 1;
+ return false;
+ }
+ p_len[i] = strlen(p_line[i]);
+ p_char[i] = '+';
+ }
+
+ if (remove_special_line()) {
+ p_len[i - 1] -= 1;
+ (p_line[i - 1])[p_len[i - 1]] = 0;
+ }
+ }
+ if (reverse) /* backwards patch? */
+ if (!pch_swap())
+ say("Not enough memory to swap next hunk!\n");
+#ifdef DEBUGGING
+ if (debug & 2) {
+ int i;
+ char special;
+
+ for (i = 0; i <= p_end; i++) {
+ if (i == p_ptrn_lines)
+ special = '^';
+ else
+ special = ' ';
+ fprintf(stderr, "%3d %c %c %s", i, p_char[i],
+ special, p_line[i]);
+ fflush(stderr);
+ }
+ }
+#endif
+ if (p_end + 1 < hunkmax)/* paranoia reigns supreme... */
+ p_char[p_end + 1] = '^'; /* add a stopper for apply_hunk */
+ return true;
+}
+
+/*
+ * Input a line from the patch file, worrying about indentation.
+ */
+static char *
+pgets(char *bf, int sz, FILE *fp)
+{
+ char *s, *ret = fgets(bf, sz, fp);
+ int indent = 0;
+
+ if (p_indent && ret != NULL) {
+ for (s = buf;
+ indent < p_indent && (*s == ' ' || *s == '\t' || *s == 'X');
+ s++) {
+ if (*s == '\t')
+ indent += 8 - (indent % 7);
+ else
+ indent++;
+ }
+ if (buf != s && strlcpy(buf, s, buf_len) >= buf_len)
+ fatal("buffer too small in pgets()\n");
+ }
+ return ret;
+}
+
+/*
+ * Reverse the old and new portions of the current hunk.
+ */
+bool
+pch_swap(void)
+{
+ char **tp_line; /* the text of the hunk */
+ short *tp_len; /* length of each line */
+ char *tp_char; /* +, -, and ! */
+ LINENUM i;
+ LINENUM n;
+ bool blankline = false;
+ char *s;
+
+ i = p_first;
+ p_first = p_newfirst;
+ p_newfirst = i;
+
+ /* make a scratch copy */
+
+ tp_line = p_line;
+ tp_len = p_len;
+ tp_char = p_char;
+ p_line = NULL; /* force set_hunkmax to allocate again */
+ p_len = NULL;
+ p_char = NULL;
+ set_hunkmax();
+ if (p_line == NULL || p_len == NULL || p_char == NULL) {
+
+ free(p_line);
+ p_line = tp_line;
+ free(p_len);
+ p_len = tp_len;
+ free(p_char);
+ p_char = tp_char;
+ return false; /* not enough memory to swap hunk! */
+ }
+ /* now turn the new into the old */
+
+ i = p_ptrn_lines + 1;
+ if (tp_char[i] == '\n') { /* account for possible blank line */
+ blankline = true;
+ i++;
+ }
+ if (p_efake >= 0) { /* fix non-freeable ptr range */
+ if (p_efake <= i)
+ n = p_end - i + 1;
+ else
+ n = -i;
+ p_efake += n;
+ p_bfake += n;
+ }
+ for (n = 0; i <= p_end; i++, n++) {
+ p_line[n] = tp_line[i];
+ p_char[n] = tp_char[i];
+ if (p_char[n] == '+')
+ p_char[n] = '-';
+ p_len[n] = tp_len[i];
+ }
+ if (blankline) {
+ i = p_ptrn_lines + 1;
+ p_line[n] = tp_line[i];
+ p_char[n] = tp_char[i];
+ p_len[n] = tp_len[i];
+ n++;
+ }
+ if (p_char[0] != '=')
+ fatal("Malformed patch at line %ld: expected '=' found '%c'\n",
+ p_input_line, p_char[0]);
+ p_char[0] = '*';
+ for (s = p_line[0]; *s; s++)
+ if (*s == '-')
+ *s = '*';
+
+ /* now turn the old into the new */
+
+ if (p_char[0] != '*')
+ fatal("Malformed patch at line %ld: expected '*' found '%c'\n",
+ p_input_line, p_char[0]);
+ tp_char[0] = '=';
+ for (s = tp_line[0]; *s; s++)
+ if (*s == '*')
+ *s = '-';
+ for (i = 0; n <= p_end; i++, n++) {
+ p_line[n] = tp_line[i];
+ p_char[n] = tp_char[i];
+ if (p_char[n] == '-')
+ p_char[n] = '+';
+ p_len[n] = tp_len[i];
+ }
+
+ if (i != p_ptrn_lines + 1)
+ fatal("Malformed patch at line %ld: expected %ld lines, "
+ "got %ld\n",
+ p_input_line, p_ptrn_lines + 1, i);
+
+ i = p_ptrn_lines;
+ p_ptrn_lines = p_repl_lines;
+ p_repl_lines = i;
+
+ free(tp_line);
+ free(tp_len);
+ free(tp_char);
+
+ return true;
+}
+
+/*
+ * Return the specified line position in the old file of the old context.
+ */
+LINENUM
+pch_first(void)
+{
+ return p_first;
+}
+
+/*
+ * Return the number of lines of old context.
+ */
+LINENUM
+pch_ptrn_lines(void)
+{
+ return p_ptrn_lines;
+}
+
+/*
+ * Return the probable line position in the new file of the first line.
+ */
+LINENUM
+pch_newfirst(void)
+{
+ return p_newfirst;
+}
+
+/*
+ * Return the number of lines in the replacement text including context.
+ */
+LINENUM
+pch_repl_lines(void)
+{
+ return p_repl_lines;
+}
+
+/*
+ * Return the number of lines in the whole hunk.
+ */
+LINENUM
+pch_end(void)
+{
+ return p_end;
+}
+
+/*
+ * Return the number of context lines before the first changed line.
+ */
+LINENUM
+pch_context(void)
+{
+ return p_context;
+}
+
+/*
+ * Return the length of a particular patch line.
+ */
+short
+pch_line_len(LINENUM line)
+{
+ return p_len[line];
+}
+
+/*
+ * Return the control character (+, -, *, !, etc) for a patch line.
+ */
+char
+pch_char(LINENUM line)
+{
+ return p_char[line];
+}
+
+/*
+ * Return a pointer to a particular patch line.
+ */
+char *
+pfetch(LINENUM line)
+{
+ return p_line[line];
+}
+
+/*
+ * Return where in the patch file this hunk began, for error messages.
+ */
+LINENUM
+pch_hunk_beg(void)
+{
+ return p_hunk_beg;
+}
+
+/*
+ * Apply an ed script by feeding ed itself.
+ */
+void
+do_ed_script(void)
+{
+ char *t;
+ long beginning_of_this_line;
+ FILE *pipefp = NULL;
+ int continuation;
+
+ if (!skip_rest_of_patch) {
+ if (copy_file(filearg[0], TMPOUTNAME) < 0) {
+ unlink(TMPOUTNAME);
+ fatal("can't create temp file %s", TMPOUTNAME);
+ }
+ snprintf(buf, buf_len, "%s -S%s %s", _PATH_ED,
+ verbose ? "" : "s", TMPOUTNAME);
+ pipefp = popen(buf, "w");
+ }
+ for (;;) {
+ beginning_of_this_line = ftell(pfp);
+ if (pgets(buf, buf_len, pfp) == NULL) {
+ next_intuit_at(beginning_of_this_line, p_input_line);
+ break;
+ }
+ p_input_line++;
+ for (t = buf; isdigit((unsigned char)*t) || *t == ','; t++)
+ ;
+ /* POSIX defines allowed commands as {a,c,d,i,s} */
+ if (isdigit((unsigned char)*buf) && (*t == 'a' || *t == 'c' ||
+ *t == 'd' || *t == 'i' || *t == 's')) {
+ if (pipefp != NULL)
+ fputs(buf, pipefp);
+ if (*t == 's') {
+ for (;;) {
+ continuation = 0;
+ t = strchr(buf, '\0') - 1;
+ while (--t >= buf && *t == '\\')
+ continuation = !continuation;
+ if (!continuation ||
+ pgets(buf, sizeof buf, pfp) == NULL)
+ break;
+ if (pipefp != NULL)
+ fputs(buf, pipefp);
+ }
+ } else if (*t != 'd') {
+ while (pgets(buf, buf_len, pfp) != NULL) {
+ p_input_line++;
+ if (pipefp != NULL)
+ fputs(buf, pipefp);
+ if (strEQ(buf, ".\n"))
+ break;
+ }
+ }
+ } else {
+ next_intuit_at(beginning_of_this_line, p_input_line);
+ break;
+ }
+ }
+ if (pipefp == NULL)
+ return;
+ fprintf(pipefp, "w\n");
+ fprintf(pipefp, "q\n");
+ fflush(pipefp);
+ pclose(pipefp);
+ ignore_signals();
+ if (!check_only) {
+ if (move_file(TMPOUTNAME, outname) < 0) {
+ toutkeep = true;
+ chmod(TMPOUTNAME, filemode);
+ } else
+ chmod(outname, filemode);
+ }
+ set_signals(1);
+}
+
+/*
+ * Choose the name of the file to be patched based on POSIX rules.
+ * NOTE: the POSIX rules are amazingly stupid and we only follow them
+ * if the user specified --posix or set POSIXLY_CORRECT.
+ */
+static char *
+posix_name(const struct file_name *names, bool assume_exists)
+{
+ char *path = NULL;
+ int i;
+
+ /*
+ * POSIX states that the filename will be chosen from one
+ * of the old, new and index names (in that order) if
+ * the file exists relative to CWD after -p stripping.
+ */
+ for (i = 0; i < MAX_FILE; i++) {
+ if (names[i].path != NULL && names[i].exists) {
+ path = names[i].path;
+ break;
+ }
+ }
+ if (path == NULL && !assume_exists) {
+ /*
+ * No files found, look for something we can checkout from
+ * RCS/SCCS dirs. Same order as above.
+ */
+ for (i = 0; i < MAX_FILE; i++) {
+ if (names[i].path != NULL &&
+ (path = checked_in(names[i].path)) != NULL)
+ break;
+ }
+ /*
+ * Still no match? Check to see if the diff could be creating
+ * a new file.
+ */
+ if (path == NULL && ok_to_create_file &&
+ names[NEW_FILE].path != NULL)
+ path = names[NEW_FILE].path;
+ }
+
+ return path ? savestr(path) : NULL;
+}
+
+/*
+ * Choose the name of the file to be patched based the "best" one
+ * available.
+ */
+static char *
+best_name(const struct file_name *names, bool assume_exists)
+{
+ size_t min_components, min_baselen, min_len, tmp;
+ char *best = NULL;
+ int i;
+
+ /*
+ * The "best" name is the one with the fewest number of path
+ * components, the shortest basename length, and the shortest
+ * overall length (in that order). We only use the Index: file
+ * if neither of the old or new files could be intuited from
+ * the diff header.
+ */
+ min_components = min_baselen = min_len = SIZE_MAX;
+ for (i = INDEX_FILE; i >= OLD_FILE; i--) {
+ if (names[i].path == NULL ||
+ (!names[i].exists && !assume_exists))
+ continue;
+ if ((tmp = num_components(names[i].path)) > min_components)
+ continue;
+ min_components = tmp;
+ if ((tmp = strlen(basename(names[i].path))) > min_baselen)
+ continue;
+ min_baselen = tmp;
+ if ((tmp = strlen(names[i].path)) > min_len)
+ continue;
+ min_len = tmp;
+ best = names[i].path;
+ }
+ if (best == NULL) {
+ /*
+ * No files found, look for something we can checkout from
+ * RCS/SCCS dirs. Logic is identical to that above...
+ */
+ min_components = min_baselen = min_len = SIZE_MAX;
+ for (i = INDEX_FILE; i >= OLD_FILE; i--) {
+ if (names[i].path == NULL ||
+ checked_in(names[i].path) == NULL)
+ continue;
+ if ((tmp = num_components(names[i].path)) > min_components)
+ continue;
+ min_components = tmp;
+ if ((tmp = strlen(basename(names[i].path))) > min_baselen)
+ continue;
+ min_baselen = tmp;
+ if ((tmp = strlen(names[i].path)) > min_len)
+ continue;
+ min_len = tmp;
+ best = names[i].path;
+ }
+ /*
+ * Still no match? Check to see if the diff could be creating
+ * a new file.
+ */
+ if (best == NULL && ok_to_create_file &&
+ names[NEW_FILE].path != NULL)
+ best = names[NEW_FILE].path;
+ }
+
+ return best ? savestr(best) : NULL;
+}
+
+static size_t
+num_components(const char *path)
+{
+ size_t n;
+ const char *cp;
+
+ for (n = 0, cp = path; (cp = strchr(cp, '/')) != NULL; n++, cp++) {
+ while (*cp == '/')
+ cp++; /* skip consecutive slashes */
+ }
+ return n;
+}
diff --git a/usr.bin/patch/pch.h b/usr.bin/patch/pch.h
new file mode 100644
index 0000000..a2d45a7
--- /dev/null
+++ b/usr.bin/patch/pch.h
@@ -0,0 +1,59 @@
+/*
+ * $OpenBSD: pch.h,v 1.9 2003/10/31 20:20:45 millert Exp $
+ * $DragonFly: src/usr.bin/patch/pch.h,v 1.1 2004/09/24 18:44:28 joerg Exp $
+ * $NetBSD: pch.h,v 1.10 2008/09/19 18:33:34 joerg Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+#define OLD_FILE 0
+#define NEW_FILE 1
+#define INDEX_FILE 2
+#define MAX_FILE 3
+
+struct file_name {
+ char *path;
+ bool exists;
+};
+
+void re_patch(void);
+void open_patch_file(const char *);
+void set_hunkmax(void);
+bool there_is_another_patch(void);
+bool another_hunk(void);
+bool pch_swap(void);
+char *pfetch(LINENUM);
+short pch_line_len(LINENUM);
+LINENUM pch_first(void);
+LINENUM pch_ptrn_lines(void);
+LINENUM pch_newfirst(void);
+LINENUM pch_repl_lines(void);
+LINENUM pch_end(void);
+LINENUM pch_context(void);
+LINENUM pch_hunk_beg(void);
+char pch_char(LINENUM);
+void do_ed_script(void);
diff --git a/usr.bin/patch/util.c b/usr.bin/patch/util.c
new file mode 100644
index 0000000..30e4f3c
--- /dev/null
+++ b/usr.bin/patch/util.c
@@ -0,0 +1,447 @@
+/*
+ * $OpenBSD: util.c,v 1.32 2006/03/11 19:41:30 otto Exp $
+ * $DragonFly: src/usr.bin/patch/util.c,v 1.9 2007/09/29 23:11:10 swildner Exp $
+ * $NetBSD: util.c,v 1.28 2018/06/18 18:33:31 christos Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: util.c,v 1.28 2018/06/18 18:33:31 christos Exp $");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "util.h"
+#include "backupfile.h"
+#include "pathnames.h"
+
+/* Rename a file, copying it if necessary. */
+
+int
+move_file(const char *from, const char *to)
+{
+ int fromfd;
+ ssize_t i;
+
+ /* to stdout? */
+
+ if (strEQ(to, "-")) {
+#ifdef DEBUGGING
+ if (debug & 4)
+ say("Moving %s to stdout.\n", from);
+#endif
+ fromfd = open(from, O_RDONLY);
+ if (fromfd < 0)
+ pfatal("internal error, can't reopen %s", from);
+ while ((i = read(fromfd, buf, buf_len)) > 0)
+ if (write(STDOUT_FILENO, buf, i) != i)
+ pfatal("write failed");
+ close(fromfd);
+ return 0;
+ }
+ if (backup_file(to) < 0) {
+ say("Can't backup %s, output is in %s: %s\n", to, from,
+ strerror(errno));
+ return -1;
+ }
+#ifdef DEBUGGING
+ if (debug & 4)
+ say("Moving %s to %s.\n", from, to);
+#endif
+ if (rename(from, to) < 0) {
+ if (errno != EXDEV || copy_file(from, to) < 0) {
+ say("Can't create %s, output is in %s: %s\n",
+ to, from, strerror(errno));
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/* Backup the original file. */
+
+int
+backup_file(const char *orig)
+{
+ struct stat filestat;
+ char bakname[MAXPATHLEN], *s, *simplename;
+ dev_t orig_device;
+ ino_t orig_inode;
+
+ if (backup_type == none || stat(orig, &filestat) != 0)
+ return 0; /* nothing to do */
+ /*
+ * If the user used zero prefixes or suffixes, then
+ * he doesn't want backups. Yet we have to remove
+ * orig to break possible hardlinks.
+ */
+ if ((origprae && *origprae == 0) || *simple_backup_suffix == 0) {
+ unlink(orig);
+ return 0;
+ }
+ orig_device = filestat.st_dev;
+ orig_inode = filestat.st_ino;
+
+ if (origprae) {
+ if (strlcpy(bakname, origprae, sizeof(bakname)) >= sizeof(bakname) ||
+ strlcat(bakname, orig, sizeof(bakname)) >= sizeof(bakname))
+ fatal("filename %s too long for buffer\n", origprae);
+ } else {
+ if ((s = find_backup_file_name(orig)) == NULL)
+ fatal("out of memory\n");
+ if (strlcpy(bakname, s, sizeof(bakname)) >= sizeof(bakname))
+ fatal("filename %s too long for buffer\n", s);
+ free(s);
+ }
+
+ if ((simplename = strrchr(bakname, '/')) != NULL)
+ simplename = simplename + 1;
+ else
+ simplename = bakname;
+
+ /*
+ * Find a backup name that is not the same file. Change the
+ * first lowercase char into uppercase; if that isn't
+ * sufficient, chop off the first char and try again.
+ */
+ while (stat(bakname, &filestat) == 0 &&
+ orig_device == filestat.st_dev && orig_inode == filestat.st_ino) {
+ /* Skip initial non-lowercase chars. */
+ for (s = simplename; *s && !islower((unsigned char)*s); s++)
+ ;
+ if (*s)
+ *s = toupper((unsigned char)*s);
+ else
+ memmove(simplename, simplename + 1,
+ strlen(simplename + 1) + 1);
+ }
+#ifdef DEBUGGING
+ if (debug & 4)
+ say("Moving %s to %s.\n", orig, bakname);
+#endif
+ if (rename(orig, bakname) < 0) {
+ if (errno != EXDEV || copy_file(orig, bakname) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Copy a file.
+ */
+int
+copy_file(const char *from, const char *to)
+{
+ int tofd, fromfd;
+ ssize_t i;
+
+ tofd = open(to, O_CREAT|O_TRUNC|O_WRONLY, 0666);
+ if (tofd < 0)
+ return -1;
+ fromfd = open(from, O_RDONLY, 0);
+ if (fromfd < 0)
+ pfatal("internal error, can't reopen %s", from);
+ while ((i = read(fromfd, buf, buf_len)) > 0)
+ if (write(tofd, buf, i) != i)
+ pfatal("write to %s failed", to);
+ close(fromfd);
+ close(tofd);
+ return 0;
+}
+
+/*
+ * Allocate a unique area for a string.
+ */
+char *
+savestr(const char *s)
+{
+ char *rv;
+
+ if (!s)
+ s = "Oops";
+ rv = strdup(s);
+ if (rv == NULL) {
+ if (using_plan_a)
+ out_of_mem = true;
+ else
+ fatal("out of memory\n");
+ }
+ return rv;
+}
+
+/*
+ * Vanilla terminal output (buffered).
+ */
+void
+say(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fflush(stderr);
+}
+
+/*
+ * Terminal output, pun intended.
+ */
+void
+fatal(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ fprintf(stderr, "patch: **** ");
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ my_exit(2);
+}
+
+/*
+ * Say something from patch, something from the system, then silence . . .
+ */
+void
+pfatal(const char *fmt, ...)
+{
+ va_list ap;
+ int errnum = errno;
+
+ fprintf(stderr, "patch: **** ");
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, ": %s\n", strerror(errnum));
+ my_exit(2);
+}
+
+/*
+ * Get a response from the user via /dev/tty
+ */
+void
+ask(const char *fmt, ...)
+{
+ va_list ap;
+ ssize_t nr = 0;
+ static int ttyfd = -1;
+
+ va_start(ap, fmt);
+ vfprintf(stdout, fmt, ap);
+ va_end(ap);
+ fflush(stdout);
+ if (ttyfd < 0)
+ ttyfd = open(_PATH_TTY, O_RDONLY);
+ if (ttyfd >= 0) {
+ if ((nr = read(ttyfd, buf, buf_len)) > 0 &&
+ buf[nr - 1] == '\n')
+ buf[nr - 1] = '\0';
+ }
+ if (ttyfd < 0 || nr <= 0) {
+ /* no tty or error reading, pretend user entered 'return' */
+ putchar('\n');
+ buf[0] = '\0';
+ }
+}
+
+/*
+ * How to handle certain events when not in a critical region.
+ */
+void
+set_signals(int reset)
+{
+ static sig_t hupval, intval;
+
+ if (!reset) {
+ hupval = signal(SIGHUP, SIG_IGN);
+ if (hupval != SIG_IGN)
+ hupval = my_exit;
+ intval = signal(SIGINT, SIG_IGN);
+ if (intval != SIG_IGN)
+ intval = my_exit;
+ }
+ signal(SIGHUP, hupval);
+ signal(SIGINT, intval);
+}
+
+/*
+ * How to handle certain events when in a critical region.
+ */
+void
+ignore_signals(void)
+{
+ signal(SIGHUP, SIG_IGN);
+ signal(SIGINT, SIG_IGN);
+}
+
+/*
+ * Make sure we'll have the directories to create a file. If `striplast' is
+ * true, ignore the last element of `filename'.
+ */
+
+void
+makedirs(const char *filename, bool striplast)
+{
+ char *tmpbuf;
+
+ if ((tmpbuf = strdup(filename)) == NULL)
+ fatal("out of memory\n");
+
+ if (striplast) {
+ char *s = strrchr(tmpbuf, '/');
+ if (s == NULL) {
+ free(tmpbuf);
+ return; /* nothing to be done */
+ }
+ *s = '\0';
+ }
+ if (mkpath(tmpbuf) != 0)
+ pfatal("creation of %s failed", tmpbuf);
+ free(tmpbuf);
+}
+
+/*
+ * Make filenames more reasonable.
+ */
+char *
+fetchname(const char *at, bool *exists, int strip_leading)
+{
+ char *fullname, *name, *t;
+ int sleading, tab;
+ struct stat filestat;
+
+ if (at == NULL || *at == '\0')
+ return NULL;
+ while (isspace((unsigned char)*at))
+ at++;
+#ifdef DEBUGGING
+ if (debug & 128)
+ say("fetchname %s %d\n", at, strip_leading);
+#endif
+ /* So files can be created by diffing against /dev/null. */
+ if (strnEQ(at, _PATH_DEVNULL, sizeof(_PATH_DEVNULL) - 1))
+ return NULL;
+ name = fullname = t = savestr(at);
+
+ tab = strchr(t, '\t') != NULL;
+ /* Strip off up to `strip_leading' path components and NUL terminate. */
+ for (sleading = strip_leading; *t != '\0' && ((tab && *t != '\t') ||
+ !isspace((unsigned char)*t)); t++) {
+ if (t[0] == '/' && t[1] != '/' && t[1] != '\0')
+ if (--sleading >= 0)
+ name = t + 1;
+ }
+ *t = '\0';
+
+ /*
+ * If no -p option was given (957 is the default value!), we were
+ * given a relative pathname, and the leading directories that we
+ * just stripped off all exist, put them back on.
+ */
+ if (strip_leading == 957 && name != fullname && *fullname != '/') {
+ name[-1] = '\0';
+ if (stat(fullname, &filestat) == 0 && S_ISDIR(filestat.st_mode)) {
+ name[-1] = '/';
+ name = fullname;
+ }
+ }
+ name = savestr(name);
+ free(fullname);
+
+ *exists = stat(name, &filestat) == 0;
+ return name;
+}
+
+/*
+ * Takes the name returned by fetchname and looks in RCS/SCCS directories
+ * for a checked in version.
+ */
+char *
+checked_in(char *file)
+{
+ char *filebase, *filedir, tmpbuf[MAXPATHLEN];
+ struct stat filestat;
+
+ filebase = basename(file);
+ filedir = dirname(file);
+
+#define try(f, a1, a2, a3) \
+(snprintf(tmpbuf, sizeof tmpbuf, f, a1, a2, a3), stat(tmpbuf, &filestat) == 0)
+
+ if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) ||
+ try("%s/RCS/%s%s", filedir, filebase, "") ||
+ try("%s/%s%s", filedir, filebase, RCSSUFFIX) ||
+ try("%s/SCCS/%s%s", filedir, SCCSPREFIX, filebase) ||
+ try("%s/%s%s", filedir, SCCSPREFIX, filebase))
+ return file;
+
+ return NULL;
+}
+
+void
+version(void)
+{
+ printf("Patch version 2.0-12u8-NetBSD\n");
+ my_exit(EXIT_SUCCESS);
+}
+
+/*
+ * Exit with cleanup.
+ */
+void
+my_exit(int status)
+{
+ unlink(TMPINNAME);
+ if (!toutkeep)
+ unlink(TMPOUTNAME);
+ if (!trejkeep)
+ unlink(TMPREJNAME);
+ unlink(TMPPATNAME);
+ exit(status);
+}
+
+void *
+pch_realloc(void *ptr, size_t number, size_t size)
+{
+ if (number > SIZE_MAX / size) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+ return realloc(ptr, number * size);
+}
diff --git a/usr.bin/patch/util.h b/usr.bin/patch/util.h
new file mode 100644
index 0000000..d8bde6c
--- /dev/null
+++ b/usr.bin/patch/util.h
@@ -0,0 +1,51 @@
+/*
+ * $OpenBSD: util.h,v 1.15 2005/06/20 07:14:06 otto Exp $
+ * $DragonFly: src/usr.bin/patch/util.h,v 1.2 2007/09/29 23:11:10 swildner Exp $
+ * $NetBSD: util.h,v 1.13 2018/06/18 18:33:31 christos Exp $
+ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+char *fetchname(const char *, bool *, int);
+char *checked_in(char *);
+int backup_file(const char *);
+int move_file(const char *, const char *);
+int copy_file(const char *, const char *);
+void say(const char *, ...) __printflike(1, 2);
+void fatal(const char *, ...) __printflike(1, 2) __dead;
+void pfatal(const char *, ...) __printflike(1, 2) __dead;
+void ask(const char *, ...) __printflike(1, 2);
+char *savestr(const char *);
+void set_signals(int);
+void ignore_signals(void);
+void makedirs(const char *, bool);
+void version(void) __dead;
+void my_exit(int) __dead;
+void *pch_realloc(void *, size_t, size_t);
+
+/* in mkpath.c */
+extern int mkpath(char *);
diff --git a/usr.bin/pathchk/pathchk.1 b/usr.bin/pathchk/pathchk.1
new file mode 100644
index 0000000..d7f5a43
--- /dev/null
+++ b/usr.bin/pathchk/pathchk.1
@@ -0,0 +1,123 @@
+.\" $NetBSD: pathchk.1,v 1.6 2010/11/16 23:59:02 wiz Exp $
+.\"
+.\" Copyright (c) 2001, 2002 Chuck Rouillard
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. The name of the author may not be used to endorse or promote
+.\" products derived from this software without specific prior written
+.\" permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from FreeBSD: pathchk.1,v 1.3 2002/12/12 17:26:01 ru Exp
+.\"
+.Dd November 9, 2010
+.Dt PATHCHK 1
+.Os
+.Sh NAME
+.Nm pathchk
+.Nd check pathnames
+.Sh SYNOPSIS
+.Nm
+.Op Fl p
+.Ar pathname ...
+.Sh DESCRIPTION
+The
+.Nm
+utility checks whether each of the specified
+.Ar pathname
+arguments is valid or portable.
+.Pp
+A diagnostic message is written for each argument that:
+.Bl -bullet
+.It
+Is longer than
+.Dv PATH_MAX
+bytes.
+.It
+Contains any component longer than
+.Dv NAME_MAX
+bytes.
+(The value of
+.Dv NAME_MAX
+depends on the underlying file system.)
+.It
+Contains a directory component that is not searchable.
+.El
+.Pp
+It is not considered an error if a
+.Ar pathname
+argument contains a nonexistent component as long as a component by that
+name could be created.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl p
+Perform portability checks on the specified
+.Ar pathname
+arguments.
+Diagnostic messages will be written for each argument that:
+.Bl -bullet
+.It
+Is longer than
+.Dv _POSIX_PATH_MAX
+.Pq 255
+bytes.
+.It
+Contains a component longer than
+.Dv _POSIX_NAME_MAX
+.Pq 14
+bytes.
+.It
+Contains any character not in the portable filename character set (that is,
+alphanumeric characters,
+.Ql \&. ,
+.Ql \&-
+and
+.Ql _ ) .
+No component may start with the hyphen
+.Pq Ql \&-
+character.
+.El
+.El
+.Sh EXAMPLES
+Check whether the names of files in the current directory are portable to
+other
+.Tn POSIX
+systems:
+.Pp
+.Dl "find . -exec pathchk -p \e{\e} +"
+or the more efficient:
+.Dl "find . -print0 | xargs -0 pathchk -p"
+.Sh SEE ALSO
+.Xr getconf 1 ,
+.Xr pathconf 2 ,
+.Xr stat 2
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.1-2001 .
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.Nx 2.0 .
diff --git a/usr.bin/pathchk/pathchk.c b/usr.bin/pathchk/pathchk.c
new file mode 100644
index 0000000..0f7f5c1
--- /dev/null
+++ b/usr.bin/pathchk/pathchk.c
@@ -0,0 +1,191 @@
+/* $NetBSD: pathchk.c,v 1.2 2011/09/16 15:39:28 joerg Exp $ */
+
+/*-
+ * Copyright (c) 2002 Tim J. Robbins.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from FreeBSD: pathchk.c,v 1.4 2002/12/15 00:40:47 tjr Exp
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: pathchk.c,v 1.2 2011/09/16 15:39:28 joerg Exp $");
+#endif /* !lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int check(const char *);
+static int portable(const char *);
+__dead static void usage(void);
+
+static int pflag; /* Perform portability checks */
+
+int
+main(int argc, char *argv[])
+{
+ int ch, rval;
+ const char *arg;
+
+ while ((ch = getopt(argc, argv, "p")) > 0) {
+ switch (ch) {
+ case 'p':
+ pflag = 1;
+ break;
+ default:
+ usage();
+ /*NOTREACHED*/
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ usage();
+
+ rval = 0;
+ while ((arg = *argv++) != NULL)
+ rval |= check(arg);
+
+ exit(rval);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: pathchk [-p] pathname...\n");
+ exit(1);
+}
+
+static int
+check(const char *path)
+{
+ struct stat sb;
+ long complen, namemax, pathmax, svnamemax;
+ int badch, last;
+ char *end, *p, *pathd;
+
+ if ((pathd = strdup(path)) == NULL)
+ err(1, "strdup");
+
+ p = pathd;
+
+ if (!pflag) {
+ errno = 0;
+ namemax = pathconf(*p == '/' ? "/" : ".", _PC_NAME_MAX);
+ if (namemax == -1 && errno != 0)
+ namemax = NAME_MAX;
+ } else
+ namemax = _POSIX_NAME_MAX;
+
+ for (;;) {
+ p += strspn(p, "/");
+ complen = (long)strcspn(p, "/");
+ end = p + complen;
+ last = *end == '\0';
+ *end = '\0';
+
+ if (namemax != -1 && complen > namemax) {
+ warnx("%s: %s: component too long (limit %ld)", path,
+ p, namemax);
+ goto bad;
+ }
+
+ if (!pflag && stat(pathd, &sb) == -1 && errno != ENOENT) {
+ warn("%s: %.*s", path, (int)(strlen(pathd) -
+ complen - 1), pathd);
+ goto bad;
+ }
+
+ if (pflag && (badch = portable(p)) >= 0) {
+ warnx("%s: %s: component contains non-portable "
+ "character `%c'", path, p, badch);
+ goto bad;
+ }
+
+ if (last)
+ break;
+
+ if (!pflag) {
+ errno = 0;
+ svnamemax = namemax;
+ namemax = pathconf(pathd, _PC_NAME_MAX);
+ if (namemax == -1 && errno != 0)
+ namemax = svnamemax;
+ }
+
+ *end = '/';
+ p = end + 1;
+ }
+
+ if (!pflag) {
+ errno = 0;
+ pathmax = pathconf(path, _PC_PATH_MAX);
+ if (pathmax == -1 && errno != 0)
+ pathmax = PATH_MAX;
+ } else
+ pathmax = _POSIX_PATH_MAX;
+ if (pathmax != -1 && strlen(path) >= (size_t)pathmax) {
+ warnx("%s: path too long (limit %ld)", path, pathmax - 1);
+ goto bad;
+ }
+
+ free(pathd);
+ return (0);
+
+bad: free(pathd);
+ return (1);
+}
+
+/*
+ * Check whether a path component contains only portable characters. Return
+ * the first non-portable character found.
+ */
+static int
+portable(const char *path)
+{
+ static const char charset[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789._-";
+ long s;
+
+ if (*path == '-')
+ return (*path);
+
+ s = strspn(path, charset);
+ if (path[s] != '\0')
+ return (path[s]);
+
+ return (-1);
+}
diff --git a/usr.bin/pr/egetopt.c b/usr.bin/pr/egetopt.c
new file mode 100644
index 0000000..5c43476
--- /dev/null
+++ b/usr.bin/pr/egetopt.c
@@ -0,0 +1,215 @@
+/* $NetBSD: egetopt.c,v 1.9 2011/09/06 18:26:06 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1991 Keith Muller.
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+from: static char sccsid[] = "@(#)egetopt.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: egetopt.c,v 1.9 2011/09/06 18:26:06 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "extern.h"
+
+/*
+ * egetopt: get option letter from argument vector (an extended
+ * version of getopt).
+ *
+ * Non standard additions to the ostr specs are:
+ * 1) '?': immediate value following arg is optional (no white space
+ * between the arg and the value)
+ * 2) '#': +/- followed by a number (with an optional sign but
+ * no white space between the arg and the number). The - may be
+ * combined with other options, but the + cannot.
+ */
+
+int eopterr = 1; /* if error message should be printed */
+int eoptind = 1; /* index into parent argv vector */
+int eoptopt; /* character checked for validity */
+char *eoptarg; /* argument associated with option */
+
+#define BADCH (int)'?'
+static char EMSG[1] = { '\0' };
+
+int
+egetopt(int nargc, char * const *nargv, const char *ostr)
+{
+ static char *place = EMSG; /* option letter processing */
+ char *oli; /* option letter list index */
+ static int delim; /* which option delimiter */
+ char *p;
+ static char savec = '\0';
+
+ if (savec != '\0') {
+ *place = savec;
+ savec = '\0';
+ }
+
+ if (!*place) {
+ /*
+ * update scanning pointer
+ */
+ if ((eoptind >= nargc) ||
+ ((*(place = nargv[eoptind]) != '-') && (*place != '+'))) {
+ place = EMSG;
+ return (-1);
+ }
+
+ delim = (int)*place;
+ if (place[1] && *++place == '-' && !place[1]) {
+ /*
+ * found "--"
+ */
+ ++eoptind;
+ place = EMSG;
+ return (-1);
+ }
+ }
+
+ /*
+ * check option letter
+ */
+ if ((eoptopt = (int)*place++) == (int)':' || (eoptopt == (int)'?') ||
+ !(oli = strchr(ostr, eoptopt))) {
+ /*
+ * if the user didn't specify '-' as an option,
+ * assume it means -1 when by itself.
+ */
+ if ((eoptopt == (int)'-') && !*place)
+ return (-1);
+ if (strchr(ostr, '#') && (isdigit(eoptopt) ||
+ (((eoptopt == (int)'-') || (eoptopt == (int)'+')) &&
+ isdigit((unsigned char)*place)))) {
+ /*
+ * # option: +/- with a number is ok
+ */
+ for (p = place; *p != '\0'; ++p) {
+ if (!isdigit((unsigned char)*p))
+ break;
+ }
+ eoptarg = place-1;
+
+ if (*p == '\0') {
+ place = EMSG;
+ ++eoptind;
+ } else {
+ place = p;
+ savec = *p;
+ *place = '\0';
+ }
+ return (delim);
+ }
+
+ if (!*place)
+ ++eoptind;
+ if (eopterr) {
+ if (!(p = strrchr(*nargv, '/')))
+ p = *nargv;
+ else
+ ++p;
+ (void)fprintf(stderr, "%s: illegal option -- %c\n",
+ p, eoptopt);
+ }
+ return (BADCH);
+ }
+ if (delim == (int)'+') {
+ /*
+ * '+' is only allowed with numbers
+ */
+ if (!*place)
+ ++eoptind;
+ if (eopterr) {
+ if (!(p = strrchr(*nargv, '/')))
+ p = *nargv;
+ else
+ ++p;
+ (void)fprintf(stderr,
+ "%s: illegal '+' delimiter with option -- %c\n",
+ p, eoptopt);
+ }
+ return (BADCH);
+ }
+ ++oli;
+ if ((*oli != ':') && (*oli != '?')) {
+ /*
+ * don't need argument
+ */
+ eoptarg = NULL;
+ if (!*place)
+ ++eoptind;
+ return (eoptopt);
+ }
+
+ if (*place) {
+ /*
+ * no white space
+ */
+ eoptarg = place;
+ } else if (*oli == '?') {
+ /*
+ * no arg, but NOT required
+ */
+ eoptarg = NULL;
+ } else if (nargc <= ++eoptind) {
+ /*
+ * no arg, but IS required
+ */
+ place = EMSG;
+ if (eopterr) {
+ if (!(p = strrchr(*nargv, '/')))
+ p = *nargv;
+ else
+ ++p;
+ (void)fprintf(stderr,
+ "%s: option requires an argument -- %c\n", p,
+ eoptopt);
+ }
+ return (BADCH);
+ } else {
+ /*
+ * arg has white space
+ */
+ eoptarg = nargv[eoptind];
+ }
+ place = EMSG;
+ ++eoptind;
+ return (eoptopt);
+}
diff --git a/usr.bin/pr/extern.h b/usr.bin/pr/extern.h
new file mode 100644
index 0000000..6aeb61c
--- /dev/null
+++ b/usr.bin/pr/extern.h
@@ -0,0 +1,42 @@
+/* $NetBSD: extern.h,v 1.6 2011/09/06 18:26:06 joerg Exp $ */
+
+/*-
+ * Copyright (c) 1991 Keith Muller.
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)extern.h 8.1 (Berkeley) 6/6/93
+ * $NetBSD: extern.h,v 1.6 2011/09/06 18:26:06 joerg Exp $
+ */
+
+extern int eoptind;
+extern char *eoptarg;
+
+int egetopt(int, char * const *, const char *);
diff --git a/usr.bin/pr/pr.1 b/usr.bin/pr/pr.1
new file mode 100644
index 0000000..f1cc1aa
--- /dev/null
+++ b/usr.bin/pr/pr.1
@@ -0,0 +1,361 @@
+.\" $NetBSD: pr.1,v 1.23 2017/07/03 21:34:21 wiz Exp $
+.\"
+.\" Copyright (c) 1991 Keith Muller.
+.\" Copyright (c) 1993
+.\" The Regents of the University of California. All rights reserved.
+.\" Copyright (c) 1994-1995, 1997, 1999-2003, 2009, 2012
+.\" The NetBSD Foundation, Inc.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Keith Muller of the University of California, San Diego.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)pr.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd July 31, 2012
+.Dt PR 1
+.Os
+.Sh NAME
+.Nm pr
+.Nd print files
+.Sh SYNOPSIS
+.Nm
+.Op Ar \&+page
+.Op Fl Ar column
+.Op Fl adFfmprt
+.Oo
+.Fl e Ns Oo Ar char Oc Ns Op Ar gap
+.Oc
+.Op Fl h Ar header
+.Oo
+.Fl i Ns Oo Ar char Oc Ns Op Ar gap
+.Oc
+.Op Fl l Ar lines
+.Oo
+.Fl n Ns Oo Ar char Oc Ns Op Ar width
+.Oc
+.Op Fl o Ar offset
+.Oo
+.Fl s Ns Op Ar char
+.Oc
+.Op Fl T Ar timefmt
+.Op Fl w Ar width
+.Op -
+.Op Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is a printing and pagination filter for text files.
+When multiple input files are specified, each is read, formatted,
+and written to standard output.
+By default, the input is separated into 66-line pages, each with
+.Bl -bullet
+.It
+A 5-line header with the page number, date, time, and
+the pathname of the file.
+.It
+A 5-line trailer consisting of blank lines.
+.El
+.Pp
+If standard output is associated with a terminal,
+diagnostic messages are suppressed until the
+.Nm
+utility has completed processing.
+.Pp
+When multiple column output is specified,
+text columns are of equal width.
+By default text columns are separated by at least one
+.Aq Em blank .
+Input lines that do not fit into a text column are truncated.
+Lines are not truncated under single column output.
+.Sh OPTIONS
+In the following option descriptions, column, lines, offset, page, and
+width are positive decimal integers and gap is a nonnegative decimal integer.
+.Bl -tag -width 4n
+.It Ar \&+page
+Begin output at page number
+.Ar page
+of the formatted input.
+.It Fl Ar column
+Produce output that is
+.Ar columns
+wide (default is 1) that is written vertically
+down each column in the order in which the text
+is received from the input file.
+The options
+.Fl e
+and
+.Fl i
+are assumed.
+This option should not be used with
+.Fl m .
+When used with
+.Fl t ,
+the minimum number of lines is used to display the output.
+.It Fl a
+Modify the effect of the
+.Fl column
+option so that the columns are filled across the page in a round-robin order
+(e.g., when column is 2, the first input line heads column
+1, the second heads column 2, the third is the second line
+in column 1, etc.).
+This option requires the use of the
+.Fl column
+option.
+.It Fl d
+Produce output that is double spaced.
+An extra
+.Aq Em newline
+character is output following every
+.Aq newline
+found in the input.
+.It Fl e Ns Oo Ar char Oc Ns Op Ar gap
+Expand each input
+.Aq tab
+to the next greater column
+position specified by the formula
+.Ar n*gap+1 ,
+where
+.Em n
+is an integer > 0.
+If
+.Ar gap
+is zero or is omitted the default is 8.
+All
+.Aq Em tab
+characters in the input are expanded into the appropriate
+number of
+.Ao Em space Ac Ns s .
+If any nondigit character,
+.Ar char ,
+is specified, it is used as the input tab character.
+If the first character of
+.Ar char
+is a digit then
+.Ar char
+is treated as
+.Ar gap .
+.It Fl F
+Use a
+.Aq Em form-feed
+character for new pages,
+instead of the default behavior that uses a
+sequence of
+.Aq Em newline
+characters.
+.It Fl f
+Same as
+.Fl F .
+Additionally pause before beginning the first page
+if the standard output is associated with a terminal.
+.It Fl h Ar header
+Use the string
+.Ar header
+to replace the
+.Ar file name
+in the header line.
+.It Fl i Ns Oo Ar char Oc Ns Op Ar gap
+In output, replace multiple
+.Ao space Ac Ns s
+with
+.Ao tab Ac Ns s
+whenever two or more
+adjacent
+.Ao space Ac Ns s
+reach column positions
+.Ar gap+1 ,
+.Ar 2*gap+1 ,
+etc.
+If
+.Ar gap
+is zero or omitted, default
+.Aq Em tab
+settings at every eighth column position
+is used.
+If any nondigit character,
+.Ar char ,
+is specified, it is used as the output
+.Aq Em tab
+character.
+If the first character of
+.Ar char
+is a digit then
+.Ar char
+is treated as
+.Ar gap .
+.It Fl l Ar lines
+Override the 66 line default and reset the page length to
+.Ar lines .
+If
+.Ar lines
+is not greater than the sum of both the header and trailer
+depths (in lines), the
+.Nm
+utility suppresses output of both the header and trailer, as if the
+.Fl t
+option were in effect.
+.It Fl m
+Merge the contents of multiple files.
+One line from each file specified by a file operand is
+written side by side into text columns of equal fixed widths, in
+terms of the number of column positions.
+The number of text columns depends on the number of
+file operands successfully opened.
+The maximum number of files merged depends on page width and the
+per process open file limit.
+The options
+.Fl e
+and
+.Fl i
+are assumed.
+.It Fl n Ns Oo Ar char Oc Ns Op Ar width
+Provide
+.Ar width
+digit line numbering.
+The default for
+.Ar width ,
+if not specified, is 5.
+The number occupies the first
+.Ar width
+column positions of each text column or each line of
+.Fl m
+output.
+If
+.Ar char
+(any nondigit character) is given, it is appended to the line number to
+separate it from whatever follows.
+The default for
+.Ar char
+is a
+.Aq Em tab .
+Line numbers longer than
+.Ar width
+columns are truncated.
+.It Fl o Ar offset
+Each line of output is preceded by
+.Ar offset
+.Ao Em space Ac Ns s .
+If the
+.Fl o
+option is not specified, the default is zero.
+The space taken is in addition to the output line width.
+.It Fl p
+Pause before beginning each page if the
+standard output is associated with a terminal.
+.Nm
+will write an
+.Aq Em alert
+to standard error and wait for a
+.Aq Em carriage-return
+to be read on
+.Pa /dev/tty .
+.It Fl r
+Write no diagnostic reports on failure to open a file.
+.It Fl s Ns Op Ar char
+Separate text columns by the single character
+.Ar char
+instead of by the appropriate number of
+.Ao Em space Ac Ns s
+(default for
+.Ar char
+is the
+.Aq Em tab
+character).
+.It Fl T
+Specify an
+.Xr strftime 3
+format string to be used to format the date and time information in the page
+header.
+.It Fl t
+Print neither the five-line identifying
+header nor the five-line trailer usually supplied for each page.
+Quit printing after the last line of each file without spacing to the
+end of the page.
+.It Fl w Ar width
+Set the width of the line to
+.Ar width
+column positions for multiple text-column output only.
+If the
+.Fl w
+option is not specified and the
+.Fl s
+option is not specified, the default width is 72.
+If the
+.Fl w
+option is not specified and the
+.Fl s
+option is specified, the default width is 512.
+.It Ar file
+A pathname of a file to be printed.
+If no
+.Ar file
+operands are specified, or if a
+.Ar file
+operand is
+.Sq Fl ,
+the standard input is used.
+The standard input is used only if no
+.Ar file
+operands are specified, or if a
+.Ar file
+operand is
+.Sq Fl .
+.El
+.Pp
+The
+.Fl s
+option does not allow the option letter to be separated from its
+argument, and the options
+.Fl e ,
+.Fl i ,
+and
+.Fl n
+require that both arguments, if present, not be separated from the option
+letter.
+.Sh ERRORS
+If
+.Nm
+receives an interrupt while printing to a terminal, it
+flushes all accumulated error messages to the screen before
+terminating.
+.Pp
+The
+.Nm
+utility exits 0 on success, and 1 if an error occurs.
+.Pp
+Error messages are written to standard error during the printing
+process (if output is redirected) or after all successful
+file printing is complete (when printing to a terminal).
+.Sh SEE ALSO
+.Xr cat 1 ,
+.Xr more 1 ,
+.Xr strftime 3
+.Sh STANDARDS
+The
+.Nm
+utility is
+.St -p1003.1-2008
+compatible.
diff --git a/usr.bin/pr/pr.c b/usr.bin/pr/pr.c
new file mode 100644
index 0000000..91e8075
--- /dev/null
+++ b/usr.bin/pr/pr.c
@@ -0,0 +1,1901 @@
+/* $NetBSD: pr.c,v 1.24 2012/08/01 02:27:48 ginsbach Exp $ */
+
+/*-
+ * Copyright (c) 1991 Keith Muller.
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2012
+ * The NetBSD Foundation, Inc.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+from: static char sccsid[] = "@(#)pr.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: pr.c,v 1.24 2012/08/01 02:27:48 ginsbach Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "pr.h"
+#include "extern.h"
+
+/*
+ * pr: a printing and pagination filter. If multiple input files
+ * are specified, each is read, formatted, and written to standard
+ * output. By default, input is separated into 66-line pages, each
+ * with a header that includes the page number, date, time and the
+ * files pathname.
+ *
+ * Complies with posix P1003.2/D11
+ */
+
+/*
+ * parameter variables
+ */
+static int pgnm; /* starting page number */
+static int clcnt; /* number of columns */
+static int colwd; /* column data width - multiple columns */
+static int across; /* mult col flag; write across page */
+static int dspace; /* double space flag */
+static char inchar; /* expand input char */
+static int ingap; /* expand input gap */
+static int formfeed; /* use formfeed as trailer */
+static char *header; /* header name instead of file name */
+static char ochar; /* contract output char */
+static int ogap; /* contract output gap */
+static int lines; /* number of lines per page */
+static int merge; /* merge multiple files in output */
+static char nmchar; /* line numbering append char */
+static int nmwd; /* width of line number field */
+static int offst; /* number of page offset spaces */
+static int nodiag; /* do not report file open errors */
+static char schar; /* text column separation character */
+static int sflag; /* -s option for multiple columns */
+static int ttyout; /* output is a tty */
+static int nohead; /* do not write head and trailer */
+static int pgpause; /* pause before each page */
+static int pgwd; /* page width with multiple col output */
+static const char *timefrmt = TIMEFMT; /* time conversion string */
+static FILE *ttyinf; /* input terminal for page pauses */
+
+/*
+ * misc globals
+ */
+static FILE *errf; /* error message file pointer */
+static int addone; /* page length is odd with double space */
+static int errcnt; /* error count on file processing */
+static const char digs[] = "0123456789"; /* page number translation map */
+
+static void addnum(char *, int, int);
+static void flsh_errs(void);
+static int horzcol(int, char **);
+static int inln(FILE *, char *, int, int *, int, int *);
+static int inskip(FILE *, int, int);
+static void mfail(void);
+static int mulfile(int, char **);
+static FILE *nxtfile(int, char **, const char **, char *, int);
+static int onecol(int, char **);
+static int otln(char *, int, int *, int *, int);
+static void pfail(void);
+static int prhead(char *, const char *, int);
+static void prpause(int);
+static int prtail(int, int);
+static int setup(int, char **);
+__dead static void terminate(int);
+static void usage(void);
+static int vertcol(int, char **);
+
+int
+main(int argc, char *argv[])
+{
+ int ret_val;
+
+ if (signal(SIGINT, SIG_IGN) != SIG_IGN)
+ (void)signal(SIGINT, terminate);
+ ret_val = setup(argc, argv);
+ if (!ret_val) {
+ /*
+ * select the output format based on options
+ */
+ if (merge)
+ ret_val = mulfile(argc, argv);
+ else if (clcnt == 1)
+ ret_val = onecol(argc, argv);
+ else if (across)
+ ret_val = horzcol(argc, argv);
+ else
+ ret_val = vertcol(argc, argv);
+ } else
+ usage();
+ flsh_errs();
+ if (errcnt || ret_val)
+ exit(1);
+ return(0);
+}
+
+/*
+ * onecol: print files with only one column of output.
+ * Line length is unlimited.
+ */
+static int
+onecol(int argc, char *argv[])
+{
+ int cnt = -1;
+ int off;
+ int lrgln;
+ int linecnt;
+ int num;
+ int lncnt;
+ int pagecnt;
+ int ips;
+ int ops;
+ int cps;
+ char *obuf = NULL;
+ char *lbuf;
+ char *nbuf;
+ char *hbuf = NULL;
+ char *ohbuf;
+ FILE *inf = NULL;
+ const char *fname;
+ int mor;
+ int error = 1;
+
+ if (nmwd)
+ num = nmwd + 1;
+ else
+ num = 0;
+ off = num + offst;
+
+ /*
+ * allocate line buffer
+ */
+ if ((obuf = malloc((unsigned)(LBUF + off)*sizeof(char))) == NULL)
+ goto oomem;
+ /*
+ * allocate header buffer
+ */
+ if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL)
+ goto oomem;
+
+ ohbuf = hbuf + offst;
+ nbuf = obuf + offst;
+ lbuf = nbuf + num;
+ if (num)
+ nbuf[--num] = nmchar;
+ if (offst) {
+ (void)memset(obuf, (int)' ', offst);
+ (void)memset(hbuf, (int)' ', offst);
+ }
+
+ /*
+ * loop by file
+ */
+ while ((inf = nxtfile(argc, argv, &fname, ohbuf, 0)) != NULL) {
+ if (pgnm) {
+ /*
+ * skip to specified page
+ */
+ if (inskip(inf, pgnm, lines))
+ continue;
+ pagecnt = pgnm;
+ } else
+ pagecnt = 1;
+ lncnt = 0;
+
+ /*
+ * loop by page
+ */
+ for(;;) {
+ linecnt = 0;
+ lrgln = 0;
+ ops = 0;
+ ips = 0;
+ cps = 0;
+
+ /*
+ * loop by line
+ */
+ while (linecnt < lines) {
+ /*
+ * input next line
+ */
+ if ((cnt = inln(inf,lbuf,LBUF,&cps,0,&mor)) < 0)
+ break;
+ if (!linecnt) {
+ if (pgpause)
+ prpause(pagecnt);
+
+ if (!nohead &&
+ prhead(hbuf, fname, pagecnt))
+ goto out;
+ }
+
+ /*
+ * start of new line.
+ */
+ if (!lrgln) {
+ if (num)
+ addnum(nbuf, num, ++lncnt);
+ if (otln(obuf,cnt+off, &ips, &ops, mor))
+ goto out;
+ } else if (otln(lbuf, cnt, &ips, &ops, mor))
+ goto out;
+
+ /*
+ * if line bigger than buffer, get more
+ */
+ if (mor) {
+ lrgln = 1;
+ continue;
+ }
+
+ /*
+ * whole line rcvd. reset tab proc. state
+ */
+ ++linecnt;
+ lrgln = 0;
+ ops = 0;
+ ips = 0;
+ }
+
+ /*
+ * fill to end of page
+ */
+ if (linecnt && prtail(lines-linecnt-lrgln, lrgln))
+ goto out;
+
+ /*
+ * On EOF go to next file
+ */
+ if (cnt < 0)
+ break;
+ ++pagecnt;
+ }
+ if (inf != stdin)
+ (void)fclose(inf);
+ }
+ if (eoptind < argc)
+ goto out;
+ error = 0;
+ goto out;
+oomem:
+ mfail();
+out:
+ free(obuf);
+ free(hbuf);
+ if (inf != NULL && inf != stdin)
+ (void)fclose(inf);
+ return error;
+}
+
+/*
+ * vertcol: print files with more than one column of output down a page
+ */
+static int
+vertcol(int argc, char *argv[])
+{
+ char *ptbf;
+ char **lstdat = NULL;
+ int i;
+ int j;
+ int cnt = -1;
+ int pln;
+ int *indy = NULL;
+ int cvc;
+ int *lindy = NULL;
+ int lncnt;
+ int stp;
+ int pagecnt;
+ int col = colwd + 1;
+ int mxlen = pgwd + offst + 1;
+ int mclcnt = clcnt - 1;
+ struct vcol *vc = NULL;
+ int mvc;
+ int tvc;
+ int cw = nmwd + 1;
+ int fullcol;
+ char *buf = NULL;
+ char *hbuf = NULL;
+ char *ohbuf;
+ const char *fname;
+ FILE *inf = NULL;
+ int ips = 0;
+ int cps = 0;
+ int ops = 0;
+ int mor = 0;
+ int error = 1;
+
+ /*
+ * allocate page buffer
+ */
+ if ((buf = malloc((unsigned)lines*mxlen*sizeof(char))) == NULL)
+ goto oomem;
+
+ /*
+ * allocate page header
+ */
+ if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL)
+ goto oomem;
+ ohbuf = hbuf + offst;
+ if (offst)
+ (void)memset(hbuf, (int)' ', offst);
+
+ /*
+ * col pointers when no headers
+ */
+ mvc = lines * clcnt;
+ if ((vc = malloc((unsigned)mvc*sizeof(struct vcol))) == NULL)
+ goto oomem;
+
+ /*
+ * pointer into page where last data per line is located
+ */
+ if ((lstdat = malloc((unsigned)lines*sizeof(char *))) == NULL)
+ goto oomem;
+
+ /*
+ * fast index lookups to locate start of lines
+ */
+ if ((indy = malloc((unsigned)lines*sizeof(int))) == NULL)
+ goto oomem;
+ if ((lindy = malloc((unsigned)lines*sizeof(int))) == NULL)
+ goto oomem;
+
+ if (nmwd)
+ fullcol = col + cw;
+ else
+ fullcol = col;
+
+ /*
+ * initialize buffer lookup indexes and offset area
+ */
+ for (j = 0; j < lines; ++j) {
+ lindy[j] = j * mxlen;
+ indy[j] = lindy[j] + offst;
+ if (offst) {
+ ptbf = buf + lindy[j];
+ (void)memset(ptbf, (int)' ', offst);
+ ptbf += offst;
+ } else
+ ptbf = buf + indy[j];
+ lstdat[j] = ptbf;
+ }
+
+ /*
+ * loop by file
+ */
+ while ((inf = nxtfile(argc, argv, &fname, ohbuf, 0)) != NULL) {
+ if (pgnm) {
+ /*
+ * skip to requested page
+ */
+ if (inskip(inf, pgnm, lines))
+ continue;
+ pagecnt = pgnm;
+ } else
+ pagecnt = 1;
+ lncnt = 0;
+
+ /*
+ * loop by page
+ */
+ for(;;) {
+ /*
+ * loop by column
+ */
+ cvc = 0;
+ for (i = 0; i < clcnt; ++i) {
+ j = 0;
+ /*
+ * if last column, do not pad
+ */
+ if (i == mclcnt)
+ stp = 1;
+ else
+ stp = 0;
+ /*
+ * loop by line
+ */
+ for(;;) {
+ /*
+ * is this first column
+ */
+ if (!i) {
+ ptbf = buf + indy[j];
+ lstdat[j] = ptbf;
+ } else
+ ptbf = lstdat[j];
+ vc[cvc].pt = ptbf;
+
+ /*
+ * add number
+ */
+ if (nmwd) {
+ addnum(ptbf, nmwd, ++lncnt);
+ ptbf += nmwd;
+ *ptbf++ = nmchar;
+ }
+
+ /*
+ * input next line
+ */
+ cnt = inln(inf,ptbf,colwd,&cps,1,&mor);
+ vc[cvc++].cnt = cnt;
+ if (cnt < 0)
+ break;
+ ptbf += cnt;
+
+ /*
+ * pad all but last column on page
+ */
+ if (!stp) {
+ /*
+ * pad to end of column
+ */
+ if (sflag)
+ *ptbf++ = schar;
+ else if ((pln = col-cnt) > 0) {
+ (void)memset(ptbf,
+ (int)' ',pln);
+ ptbf += pln;
+ }
+ }
+ /*
+ * remember last char in line
+ */
+ lstdat[j] = ptbf;
+ if (++j >= lines)
+ break;
+ }
+ if (cnt < 0)
+ break;
+ }
+
+ /*
+ * when -t (no header) is specified the spec requires
+ * the min number of lines. The last page may not have
+ * balanced length columns. To fix this we must reorder
+ * the columns. This is a very slow technique so it is
+ * only used under limited conditions. Without -t, the
+ * balancing of text columns is unspecified. To NOT
+ * balance the last page, add the global variable
+ * nohead to the if statement below e.g.
+ *
+ * if ((cnt < 0) && nohead && cvc ......
+ */
+ --cvc;
+
+ /*
+ * check to see if last page needs to be reordered
+ */
+ if ((cnt < 0) && cvc && ((mvc-cvc) >= clcnt)){
+ pln = cvc/clcnt;
+ if (cvc % clcnt)
+ ++pln;
+
+ if (pgpause)
+ prpause(pagecnt);
+
+ /*
+ * print header
+ */
+ if (!nohead && prhead(hbuf, fname, pagecnt))
+ goto out;
+ for (i = 0; i < pln; ++i) {
+ ips = 0;
+ ops = 0;
+ if (offst&& otln(buf,offst,&ips,&ops,1)) {
+ error = 1;
+ goto out;
+ }
+ tvc = i;
+
+ for (j = 0; j < clcnt; ++j) {
+ /*
+ * determine column length
+ */
+ if (j == mclcnt) {
+ /*
+ * last column
+ */
+ cnt = vc[tvc].cnt;
+ if (nmwd)
+ cnt += cw;
+ } else if (sflag) {
+ /*
+ * single ch between
+ */
+ cnt = vc[tvc].cnt + 1;
+ if (nmwd)
+ cnt += cw;
+ } else
+ cnt = fullcol;
+ if (otln(vc[tvc].pt, cnt, &ips,
+ &ops, 1))
+ goto out;
+ tvc += pln;
+ if (tvc >= cvc)
+ break;
+ }
+ /*
+ * terminate line
+ */
+ if (otln(buf, 0, &ips, &ops, 0))
+ goto out;
+ }
+ /*
+ * pad to end of page
+ */
+ if (prtail((lines - pln), 0))
+ goto out;
+ /*
+ * done with output, go to next file
+ */
+ break;
+ }
+
+ /*
+ * determine how many lines to output
+ */
+ if (i > 0)
+ pln = lines;
+ else
+ pln = j;
+
+ /*
+ * print header
+ */
+ if (pln) {
+ if (pgpause)
+ prpause(pagecnt);
+
+ if (!nohead && prhead(hbuf, fname, pagecnt))
+ goto out;
+ }
+
+ /*
+ * output each line
+ */
+ for (i = 0; i < pln; ++i) {
+ ptbf = buf + lindy[i];
+ if ((j = lstdat[i] - ptbf) <= offst)
+ break;
+ if (otln(ptbf, j, &ips, &ops, 0))
+ goto out;
+ }
+
+ /*
+ * pad to end of page
+ */
+ if (pln && prtail((lines - pln), 0))
+ goto out;
+
+ /*
+ * if EOF go to next file
+ */
+ if (cnt < 0)
+ break;
+ ++pagecnt;
+ }
+ if (inf != stdin)
+ (void)fclose(inf);
+ }
+ if (eoptind < argc)
+ goto out;
+ error = 0;
+ goto out;
+oomem:
+ mfail();
+out:
+ free(buf);
+ free(hbuf);
+ free(vc);
+ free(lstdat);
+ free(lindy);
+ if (inf != NULL && inf != stdin)
+ (void)fclose(inf);
+ return error;
+}
+
+/*
+ * horzcol: print files with more than one column of output across a page
+ */
+static int
+horzcol(int argc, char *argv[])
+{
+ char *ptbf;
+ int pln;
+ int cnt = -1;
+ char *lstdat;
+ int col = colwd + 1;
+ int j;
+ int i;
+ int lncnt;
+ int pagecnt;
+ char *buf = NULL;
+ char *hbuf = NULL;
+ char *ohbuf;
+ const char *fname;
+ FILE *inf = NULL;
+ int ips = 0;
+ int cps = 0;
+ int ops = 0;
+ int mor = 0;
+ int error = 1;
+
+ if ((buf = malloc((unsigned)(pgwd+offst+1)*sizeof(char))) == NULL)
+ goto oomem;
+
+ /*
+ * page header
+ */
+ if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL)
+ goto oomem;
+ ohbuf = hbuf + offst;
+ if (offst) {
+ (void)memset(buf, (int)' ', offst);
+ (void)memset(hbuf, (int)' ', offst);
+ }
+
+ /*
+ * loop by file
+ */
+ while ((inf = nxtfile(argc, argv, &fname, ohbuf, 0)) != NULL) {
+ if (pgnm) {
+ if (inskip(inf, pgnm, lines))
+ continue;
+ pagecnt = pgnm;
+ } else
+ pagecnt = 1;
+ lncnt = 0;
+
+ /*
+ * loop by page
+ */
+ for(;;) {
+ /*
+ * loop by line
+ */
+ for (i = 0; i < lines; ++i) {
+ ptbf = buf + offst;
+ lstdat = ptbf;
+ j = 0;
+ /*
+ * loop by col
+ */
+ for(;;) {
+ if (nmwd) {
+ /*
+ * add number to column
+ */
+ addnum(ptbf, nmwd, ++lncnt);
+ ptbf += nmwd;
+ *ptbf++ = nmchar;
+ }
+ /*
+ * input line
+ */
+ if ((cnt = inln(inf,ptbf,colwd,&cps,1,
+ &mor)) < 0)
+ break;
+ ptbf += cnt;
+ lstdat = ptbf;
+
+ /*
+ * if last line skip padding
+ */
+ if (++j >= clcnt)
+ break;
+
+ /*
+ * pad to end of column
+ */
+ if (sflag)
+ *ptbf++ = schar;
+ else if ((pln = col - cnt) > 0) {
+ (void)memset(ptbf,(int)' ',pln);
+ ptbf += pln;
+ }
+ }
+
+ /*
+ * determine line length
+ */
+ if ((j = lstdat - buf) <= offst)
+ break;
+ if (!i) {
+ if (pgpause)
+ prpause(pagecnt);
+
+ if (!nohead &&
+ prhead(hbuf, fname, pagecnt))
+ goto out;
+ }
+ /*
+ * output line
+ */
+ if (otln(buf, j, &ips, &ops, 0))
+ goto out;
+ }
+
+ /*
+ * pad to end of page
+ */
+ if (i && prtail(lines-i, 0))
+ goto out;
+
+ /*
+ * if EOF go to next file
+ */
+ if (cnt < 0)
+ break;
+ ++pagecnt;
+ }
+ if (inf != stdin)
+ (void)fclose(inf);
+ }
+ if (eoptind < argc)
+ goto out;
+ error = 0;
+ goto out;
+oomem:
+ mfail();
+out:
+ free(buf);
+ free(hbuf);
+ if (inf != NULL && inf != stdin)
+ (void)fclose(inf);
+ return error;
+}
+
+/*
+ * mulfile: print files with more than one column of output and
+ * more than one file concurrently
+ */
+static int
+mulfile(int argc, char *argv[])
+{
+ char *ptbf;
+ int j;
+ int pln;
+ int cnt;
+ char *lstdat;
+ int i;
+ FILE **fbuf = NULL;
+ int actf;
+ int lncnt;
+ int col;
+ int pagecnt;
+ int fproc;
+ char *buf = NULL;
+ char *hbuf = NULL;
+ char *ohbuf;
+ const char *fname;
+ int ips = 0;
+ int cps = 0;
+ int ops = 0;
+ int mor = 0;
+ int error = 1;
+
+ /*
+ * array of FILE *, one for each operand
+ */
+ if ((fbuf = calloc(clcnt, sizeof(FILE *))) == NULL)
+ goto oomem;
+
+ /*
+ * page header
+ */
+ if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL)
+ goto oomem;
+ ohbuf = hbuf + offst;
+
+ /*
+ * do not know how many columns yet. The number of operands provide an
+ * upper bound on the number of columns. We use the number of files
+ * we can open successfully to set the number of columns. The operation
+ * of the merge operation (-m) in relation to unsuccesful file opens
+ * is unspecified by posix.
+ */
+ j = 0;
+ while (j < clcnt) {
+ if ((fbuf[j] = nxtfile(argc, argv, &fname, ohbuf, 1)) == NULL)
+ break;
+ if (pgnm && (inskip(fbuf[j], pgnm, lines)))
+ fbuf[j] = NULL;
+ ++j;
+ }
+
+ /*
+ * if no files, exit
+ */
+ if (!j)
+ goto out;
+
+ /*
+ * calculate page boundries based on open file count
+ */
+ clcnt = j;
+ if (nmwd) {
+ colwd = (pgwd - clcnt - nmwd)/clcnt;
+ pgwd = ((colwd + 1) * clcnt) - nmwd - 2;
+ } else {
+ colwd = (pgwd + 1 - clcnt)/clcnt;
+ pgwd = ((colwd + 1) * clcnt) - 1;
+ }
+ if (colwd < 1) {
+ (void)fprintf(errf,
+ "pr: page width too small for %d columns\n", clcnt);
+ goto out;
+ }
+ actf = clcnt;
+ col = colwd + 1;
+
+ /*
+ * line buffer
+ */
+ if ((buf = malloc((unsigned)(pgwd+offst+1)*sizeof(char))) == NULL)
+ goto out;
+ if (offst) {
+ (void)memset(buf, (int)' ', offst);
+ (void)memset(hbuf, (int)' ', offst);
+ }
+ if (pgnm)
+ pagecnt = pgnm;
+ else
+ pagecnt = 1;
+ lncnt = 0;
+
+ /*
+ * continue to loop while any file still has data
+ */
+ while (actf > 0) {
+ /*
+ * loop by line
+ */
+ for (i = 0; i < lines; ++i) {
+ ptbf = buf + offst;
+ lstdat = ptbf;
+ if (nmwd) {
+ /*
+ * add line number to line
+ */
+ addnum(ptbf, nmwd, ++lncnt);
+ ptbf += nmwd;
+ *ptbf++ = nmchar;
+ }
+ j = 0;
+ fproc = 0;
+
+ /*
+ * loop by column
+ */
+ for (j = 0; j < clcnt; ++j) {
+ if (fbuf[j] == NULL) {
+ /*
+ * empty column; EOF
+ */
+ cnt = 0;
+ } else if ((cnt = inln(fbuf[j], ptbf, colwd,
+ &cps, 1, &mor)) < 0) {
+ /*
+ * EOF hit; no data
+ */
+ if (fbuf[j] != stdin)
+ (void)fclose(fbuf[j]);
+ fbuf[j] = NULL;
+ --actf;
+ cnt = 0;
+ } else {
+ /*
+ * process file data
+ */
+ ptbf += cnt;
+ lstdat = ptbf;
+ fproc++;
+ }
+
+ /*
+ * if last ACTIVE column, done with line
+ */
+ if (fproc >= actf)
+ break;
+
+ /*
+ * pad to end of column
+ */
+ if (sflag) {
+ *ptbf++ = schar;
+ } else if ((pln = col - cnt) > 0) {
+ (void)memset(ptbf, (int)' ', pln);
+ ptbf += pln;
+ }
+ }
+
+ /*
+ * calculate data in line
+ */
+ if ((j = lstdat - buf) <= offst)
+ break;
+
+ if (!i) {
+ if (pgpause)
+ prpause(pagecnt);
+
+ if (!nohead && prhead(hbuf, fname, pagecnt))
+ goto out;
+ }
+
+ /*
+ * output line
+ */
+ if (otln(buf, j, &ips, &ops, 0))
+ goto out;
+
+ /*
+ * if no more active files, done
+ */
+ if (actf <= 0) {
+ ++i;
+ break;
+ }
+ }
+
+ /*
+ * pad to end of page
+ */
+ if (i && prtail(lines-i, 0))
+ goto out;
+ ++pagecnt;
+ }
+ if (eoptind < argc)
+ goto out;
+ error = 0;
+ goto out;
+oomem:
+ mfail();
+out:
+ if (fbuf) {
+ for (j = 0; j < clcnt; j++)
+ if (fbuf[j] && fbuf[j] != stdin)
+ (void)fclose(fbuf[j]);
+ free(fbuf);
+ }
+ free(hbuf);
+ free(buf);
+ return error;
+}
+
+/*
+ * inln(): input a line of data (unlimited length lines supported)
+ * Input is optionally expanded to spaces
+ *
+ * inf: file
+ * buf: buffer
+ * lim: buffer length
+ * cps: column positon 1st char in buffer (large line support)
+ * trnc: throw away data more than lim up to \n
+ * mor: set if more data in line (not truncated)
+ */
+static int
+inln(FILE *inf, char *buf, int lim, int *cps, int trnc, int *mor)
+{
+ int col;
+ int gap = ingap;
+ int ch = EOF;
+ char *ptbuf;
+ int chk = (int)inchar;
+
+ ptbuf = buf;
+
+ if (gap) {
+ /*
+ * expanding input option
+ */
+ while ((--lim >= 0) && ((ch = getc(inf)) != EOF)) {
+ /*
+ * is this the input "tab" char
+ */
+ if (ch == chk) {
+ /*
+ * expand to number of spaces
+ */
+ col = (ptbuf - buf) + *cps;
+ col = gap - (col % gap);
+
+ /*
+ * if more than this line, push back
+ */
+ if ((col > lim) && (ungetc(ch, inf) == EOF))
+ return(1);
+
+ /*
+ * expand to spaces
+ */
+ while ((--col >= 0) && (--lim >= 0))
+ *ptbuf++ = ' ';
+ continue;
+ }
+ if (ch == '\n')
+ break;
+ *ptbuf++ = ch;
+ }
+ } else {
+ /*
+ * no expansion
+ */
+ while ((--lim >= 0) && ((ch = getc(inf)) != EOF)) {
+ if (ch == '\n')
+ break;
+ *ptbuf++ = ch;
+ }
+ }
+ col = ptbuf - buf;
+ if (ch == EOF) {
+ *mor = 0;
+ *cps = 0;
+ if (!col)
+ return(-1);
+ return(col);
+ }
+ if (ch == '\n') {
+ /*
+ * entire line processed
+ */
+ *mor = 0;
+ *cps = 0;
+ return(col);
+ }
+
+ /*
+ * line was larger than limit
+ */
+ if (trnc) {
+ /*
+ * throw away rest of line
+ */
+ while ((ch = getc(inf)) != EOF) {
+ if (ch == '\n')
+ break;
+ }
+ *cps = 0;
+ *mor = 0;
+ } else {
+ /*
+ * save column offset if not truncated
+ */
+ *cps += col;
+ *mor = 1;
+ }
+
+ return(col);
+}
+
+/*
+ * otln(): output a line of data. (Supports unlimited length lines)
+ * output is optionally contracted to tabs
+ *
+ * buf: output buffer with data
+ * cnt: number of chars of valid data in buf
+ * svips: buffer input column position (for large lines)
+ * svops: buffer output column position (for large lines)
+ * mor: output line not complete in this buf; more data to come.
+ * 1 is more, 0 is complete, -1 is no \n's
+ */
+static int
+otln(char *buf, int cnt, int *svips, int *svops, int mor)
+{
+ int ops; /* last col output */
+ int ips; /* last col in buf examined */
+ int gap = ogap;
+ int tbps;
+ char *endbuf;
+
+ if (ogap) {
+ /*
+ * contracting on output
+ */
+ endbuf = buf + cnt;
+ ops = *svops;
+ ips = *svips;
+ while (buf < endbuf) {
+ /*
+ * count number of spaces and ochar in buffer
+ */
+ if (*buf == ' ') {
+ ++ips;
+ ++buf;
+ continue;
+ }
+
+ /*
+ * simulate ochar processing
+ */
+ if (*buf == ochar) {
+ ips += gap - (ips % gap);
+ ++buf;
+ continue;
+ }
+
+ /*
+ * got a non space char; contract out spaces
+ */
+ while (ips - ops > 1) {
+ /*
+ * use as many ochar as will fit
+ */
+ if ((tbps = ops + gap - (ops % gap)) > ips)
+ break;
+ if (putchar(ochar) == EOF) {
+ pfail();
+ return(1);
+ }
+ ops = tbps;
+ }
+
+ while (ops < ips) {
+ /*
+ * finish off with spaces
+ */
+ if (putchar(' ') == EOF) {
+ pfail();
+ return(1);
+ }
+ ++ops;
+ }
+
+ /*
+ * output non space char
+ */
+ if (putchar(*buf++) == EOF) {
+ pfail();
+ return(1);
+ }
+ ++ips;
+ ++ops;
+ }
+
+ if (mor > 0) {
+ /*
+ * if incomplete line, save position counts
+ */
+ *svops = ops;
+ *svips = ips;
+ return(0);
+ }
+
+ if (mor < 0) {
+ while (ips - ops > 1) {
+ /*
+ * use as many ochar as will fit
+ */
+ if ((tbps = ops + gap - (ops % gap)) > ips)
+ break;
+ if (putchar(ochar) == EOF) {
+ pfail();
+ return(1);
+ }
+ ops = tbps;
+ }
+ while (ops < ips) {
+ /*
+ * finish off with spaces
+ */
+ if (putchar(' ') == EOF) {
+ pfail();
+ return(1);
+ }
+ ++ops;
+ }
+ return(0);
+ }
+ } else {
+ /*
+ * output is not contracted
+ */
+ if (cnt && (fwrite(buf, sizeof(char), cnt, stdout) <= 0)) {
+ pfail();
+ return(1);
+ }
+ if (mor != 0)
+ return(0);
+ }
+
+ /*
+ * process line end and double space as required
+ */
+ if ((putchar('\n') == EOF) || (dspace && (putchar('\n') == EOF))) {
+ pfail();
+ return(1);
+ }
+ return(0);
+}
+
+/*
+ * inskip(): skip over pgcnt pages with lncnt lines per page
+ * file is closed at EOF (if not stdin).
+ *
+ * inf FILE * to read from
+ * pgcnt number of pages to skip
+ * lncnt number of lines per page
+ */
+static int
+inskip(FILE *inf, int pgcnt, int lncnt)
+{
+ int c;
+ int cnt;
+
+ while(--pgcnt > 0) {
+ cnt = lncnt;
+ while ((c = getc(inf)) != EOF) {
+ if ((c == '\n') && (--cnt == 0))
+ break;
+ }
+ if (c == EOF) {
+ if (inf != stdin)
+ (void)fclose(inf);
+ return(1);
+ }
+ }
+ return(0);
+}
+
+/*
+ * nxtfile: returns a FILE * to next file in arg list and sets the
+ * time field for this file (or current date).
+ *
+ * buf array to store proper date for the header.
+ * dt if set skips the date processing (used with -m)
+ */
+static FILE *
+nxtfile(int argc, char **argv, const char **fname, char *buf, int dt)
+{
+ FILE *inf = NULL;
+ struct timeval tv;
+ struct timezone tz;
+ struct tm *timeptr = NULL;
+ struct stat statbuf;
+ time_t curtime;
+ static int twice = -1;
+
+ ++twice;
+ if (eoptind >= argc) {
+ /*
+ * no file listed; default, use standard input
+ */
+ if (twice)
+ return(NULL);
+ clearerr(stdin);
+ inf = stdin;
+ if (header != NULL)
+ *fname = header;
+ else
+ *fname = FNAME;
+ if (nohead)
+ return(inf);
+ if (gettimeofday(&tv, &tz) < 0) {
+ ++errcnt;
+ (void)fprintf(errf, "pr: cannot get time of day, %s\n",
+ strerror(errno));
+ eoptind = argc - 1;
+ return(NULL);
+ }
+ curtime = tv.tv_sec;
+ timeptr = localtime(&curtime);
+ }
+ for (; eoptind < argc; ++eoptind) {
+ if (strcmp(argv[eoptind], "-") == 0) {
+ /*
+ * process a "-" for filename
+ */
+ clearerr(stdin);
+ inf = stdin;
+ if (header != NULL)
+ *fname = header;
+ else
+ *fname = FNAME;
+ ++eoptind;
+ if (nohead || (dt && twice))
+ return(inf);
+ if (gettimeofday(&tv, &tz) < 0) {
+ ++errcnt;
+ (void)fprintf(errf,
+ "pr: cannot get time of day, %s\n",
+ strerror(errno));
+ return(NULL);
+ }
+ curtime = tv.tv_sec;
+ timeptr = localtime(&curtime);
+ } else {
+ /*
+ * normal file processing
+ */
+ if ((inf = fopen(argv[eoptind], "r")) == NULL) {
+ ++errcnt;
+ if (nodiag)
+ continue;
+ (void)fprintf(errf, "pr: Cannot open %s, %s\n",
+ argv[eoptind], strerror(errno));
+ continue;
+ }
+ if (header != NULL)
+ *fname = header;
+ else if (dt)
+ *fname = FNAME;
+ else
+ *fname = argv[eoptind];
+ ++eoptind;
+ if (nohead || (dt && twice))
+ return(inf);
+
+ if (dt) {
+ if (gettimeofday(&tv, &tz) < 0) {
+ ++errcnt;
+ (void)fprintf(errf,
+ "pr: cannot get time of day, %s\n",
+ strerror(errno));
+ return(NULL);
+ }
+ curtime = tv.tv_sec;
+ timeptr = localtime(&curtime);
+ } else {
+ if (fstat(fileno(inf), &statbuf) < 0) {
+ ++errcnt;
+ (void)fclose(inf);
+ (void)fprintf(errf,
+ "pr: Cannot stat %s, %s\n",
+ argv[eoptind], strerror(errno));
+ return(NULL);
+ }
+ timeptr = localtime(&(statbuf.st_mtime));
+ }
+ }
+ break;
+ }
+ if (inf == NULL)
+ return(NULL);
+
+ /*
+ * set up time field used in header
+ */
+ if (strftime(buf, HDBUF, timefrmt, timeptr) <= 0) {
+ ++errcnt;
+ if (inf != stdin)
+ (void)fclose(inf);
+ (void)fputs("pr: time conversion failed\n", errf);
+ return(NULL);
+ }
+ return(inf);
+}
+
+/*
+ * addnum(): adds the line number to the column
+ * Truncates from the front or pads with spaces as required.
+ * Numbers are right justified.
+ *
+ * buf buffer to store the number
+ * wdth width of buffer to fill
+ * line line number
+ *
+ * NOTE: numbers occupy part of the column. The posix
+ * spec does not specify if -i processing should or should not
+ * occur on number padding. The spec does say it occupies
+ * part of the column. The usage of addnum currently treats
+ * numbers as part of the column so spaces may be replaced.
+ */
+void
+addnum(char *buf, int wdth, int line)
+{
+ char *pt = buf + wdth;
+
+ do {
+ *--pt = digs[line % 10];
+ line /= 10;
+ } while (line && (pt > buf));
+
+ /*
+ * pad with space as required
+ */
+ while (pt > buf)
+ *--pt = ' ';
+}
+
+/*
+ * prpause(): pause before printing each page
+ *
+ * pagcnt page number
+ */
+static void
+prpause(int pagcnt)
+{
+
+ if (ttyout) {
+ int c;
+
+ (void)putc('\a', stderr);
+ (void)fflush(stderr);
+
+ while ((c = getc(ttyinf)) != '\n' && c != EOF)
+ ;
+
+ /*
+ * pause ONLY before first page of first file
+ */
+ if (pgpause == FIRSTPAGE && pagcnt == 1)
+ pgpause = NO_PAUSE;
+ }
+}
+
+/*
+ * prhead(): prints the top of page header
+ *
+ * buf buffer with time field (and offset)
+ * cnt number of chars in buf
+ * fname fname field for header
+ * pagcnt page number
+ */
+static int
+prhead(char *buf, const char *fname, int pagcnt)
+{
+ int ips = 0;
+ int ops = 0;
+
+ if ((putchar('\n') == EOF) || (putchar('\n') == EOF)) {
+ pfail();
+ return(1);
+ }
+ /*
+ * posix is not clear if the header is subject to line length
+ * restrictions. The specification for header line format
+ * in the spec clearly does not limit length. No pr currently
+ * restricts header length. However if we need to truncate in
+ * an reasonable way, adjust the length of the printf by
+ * changing HDFMT to allow a length max as an argument printf.
+ * buf (which contains the offset spaces and time field could
+ * also be trimmed
+ *
+ * note only the offset (if any) is processed for tab expansion
+ */
+ if (offst && otln(buf, offst, &ips, &ops, -1))
+ return(1);
+ (void)printf(HDFMT,buf+offst, fname, pagcnt);
+ return(0);
+}
+
+/*
+ * prtail(): pad page with empty lines (if required) and print page trailer
+ * if requested
+ *
+ * cnt number of lines of padding needed
+ * incomp was a '\n' missing from last line output
+ */
+static int
+prtail(int cnt, int incomp)
+{
+ if (nohead) {
+ /*
+ * only pad with no headers when incomplete last line
+ */
+ if (!incomp)
+ return(0);
+ if ((dspace && (putchar('\n') == EOF)) ||
+ (putchar('\n') == EOF)) {
+ pfail();
+ return(1);
+ }
+ return(0);
+ }
+
+ /*
+ * if double space output two \n
+ */
+ if (dspace)
+ cnt *= 2;
+
+ /*
+ * if an odd number of lines per page, add an extra \n
+ */
+ if (addone)
+ ++cnt;
+
+ /*
+ * pad page
+ */
+ if (formfeed) {
+ if ((incomp && (putchar('\n') == EOF)) ||
+ (putchar('\f') == EOF)) {
+ pfail();
+ return(1);
+ }
+ return(0);
+ }
+ cnt += TAILLEN;
+ while (--cnt >= 0) {
+ if (putchar('\n') == EOF) {
+ pfail();
+ return(1);
+ }
+ }
+ return(0);
+}
+
+/*
+ * terminate(): when a SIGINT is recvd
+ */
+static void
+terminate(int which_sig)
+{
+ flsh_errs();
+ (void)raise_default_signal(which_sig);
+ exit(1);
+}
+
+
+/*
+ * flsh_errs(): output saved up diagnostic messages after all normal
+ * processing has completed
+ */
+static void
+flsh_errs(void)
+{
+ char buf[BUFSIZ];
+
+ (void)fflush(stdout);
+ (void)fflush(errf);
+ if (errf == stderr)
+ return;
+ rewind(errf);
+ while (fgets(buf, BUFSIZ, errf) != NULL)
+ (void)fputs(buf, stderr);
+}
+
+static void
+mfail(void)
+{
+ (void)fputs("pr: memory allocation failed\n", errf);
+}
+
+static void
+pfail(void)
+{
+ (void)fprintf(errf, "pr: write failure, %s\n", strerror(errno));
+}
+
+static void
+usage(void)
+{
+ (void)fputs(
+ "usage: pr [+page] [-col] [-adFfmprt] [-e[ch][gap]] [-h header]\n",
+ errf);
+ (void)fputs(
+ " [-i[ch][gap]] [-l line] [-n[ch][width]] [-o offset]\n",
+ errf);
+ (void)fputs(
+ " [-s[ch]] [-T timefmt] [-w width] [-] [file ...]\n",
+ errf);
+}
+
+/*
+ * setup: Validate command args, initialize and perform sanity
+ * checks on options
+ */
+static int
+setup(int argc, char **argv)
+{
+ int c;
+ int eflag = 0;
+ int iflag = 0;
+ int wflag = 0;
+ int cflag = 0;
+
+ ttyinf = stdin;
+
+ if (isatty(fileno(stdout))) {
+ /*
+ * defer diagnostics until processing is done
+ */
+ if ((errf = tmpfile()) == NULL) {
+ (void)fputs("Cannot defer diagnostic messages\n",stderr);
+ return(1);
+ }
+ ttyout = 1;
+ } else
+ errf = stderr;
+ while ((c = egetopt(argc, argv, "#adFfmrte?h:i?l:n?o:ps?T:w:")) != -1) {
+ switch (c) {
+ case '+':
+ if ((pgnm = atoi(eoptarg)) < 1) {
+ (void)fputs("pr: +page number must be 1 or more\n",
+ errf);
+ return(1);
+ }
+ break;
+ case '-':
+ if ((clcnt = atoi(eoptarg)) < 1) {
+ (void)fputs("pr: -columns must be 1 or more\n",errf);
+ return(1);
+ }
+ if (clcnt > 1)
+ ++cflag;
+ break;
+ case 'a':
+ ++across;
+ break;
+ case 'd':
+ ++dspace;
+ break;
+ case 'e':
+ ++eflag;
+ if ((eoptarg != NULL) &&
+ !isdigit((unsigned char)*eoptarg))
+ inchar = *eoptarg++;
+ else
+ inchar = INCHAR;
+ if ((eoptarg != NULL) &&
+ isdigit((unsigned char)*eoptarg)) {
+ if ((ingap = atoi(eoptarg)) < 0) {
+ (void)fputs(
+ "pr: -e gap must be 0 or more\n", errf);
+ return(1);
+ }
+ if (ingap == 0)
+ ingap = INGAP;
+ } else if ((eoptarg != NULL) && (*eoptarg != '\0')) {
+ (void)fprintf(errf,
+ "pr: invalid value for -e %s\n", eoptarg);
+ return(1);
+ } else
+ ingap = INGAP;
+ break;
+ case 'f':
+ pgpause |= FIRSTPAGE;
+ /*FALLTHROUGH*/
+ case 'F':
+ ++formfeed;
+ break;
+ case 'h':
+ header = eoptarg;
+ break;
+ case 'i':
+ ++iflag;
+ if ((eoptarg != NULL) &&
+ !isdigit((unsigned char)*eoptarg))
+ ochar = *eoptarg++;
+ else
+ ochar = OCHAR;
+ if ((eoptarg != NULL) &&
+ isdigit((unsigned char)*eoptarg)) {
+ if ((ogap = atoi(eoptarg)) < 0) {
+ (void)fputs(
+ "pr: -i gap must be 0 or more\n", errf);
+ return(1);
+ }
+ if (ogap == 0)
+ ogap = OGAP;
+ } else if ((eoptarg != NULL) && (*eoptarg != '\0')) {
+ (void)fprintf(errf,
+ "pr: invalid value for -i %s\n", eoptarg);
+ return(1);
+ } else
+ ogap = OGAP;
+ break;
+ case 'l':
+ if (!isdigit((unsigned char)*eoptarg) ||
+ ((lines=atoi(eoptarg)) < 1)) {
+ (void)fputs(
+ "pr: Number of lines must be 1 or more\n",errf);
+ return(1);
+ }
+ break;
+ case 'm':
+ ++merge;
+ break;
+ case 'n':
+ if ((eoptarg != NULL) &&
+ !isdigit((unsigned char)*eoptarg))
+ nmchar = *eoptarg++;
+ else
+ nmchar = NMCHAR;
+ if ((eoptarg != NULL) &&
+ isdigit((unsigned char)*eoptarg)) {
+ if ((nmwd = atoi(eoptarg)) < 1) {
+ (void)fputs(
+ "pr: -n width must be 1 or more\n",errf);
+ return(1);
+ }
+ } else if ((eoptarg != NULL) && (*eoptarg != '\0')) {
+ (void)fprintf(errf,
+ "pr: invalid value for -n %s\n", eoptarg);
+ return(1);
+ } else
+ nmwd = NMWD;
+ break;
+ case 'o':
+ if (!isdigit((unsigned char)*eoptarg) ||
+ ((offst = atoi(eoptarg))< 1)){
+ (void)fputs("pr: -o offset must be 1 or more\n",
+ errf);
+ return(1);
+ }
+ break;
+ case 'p':
+ pgpause |= EACHPAGE;
+ break;
+ case 'r':
+ ++nodiag;
+ break;
+ case 's':
+ ++sflag;
+ if (eoptarg == NULL)
+ schar = SCHAR;
+ else
+ schar = *eoptarg++;
+ if (eoptarg && *eoptarg != '\0') {
+ (void)fprintf(errf,
+ "pr: invalid value for -s %s\n", eoptarg);
+ return(1);
+ }
+ break;
+ case 'T':
+ timefrmt = eoptarg;
+ break;
+ case 't':
+ ++nohead;
+ break;
+ case 'w':
+ ++wflag;
+ if (!isdigit((unsigned char)*eoptarg) ||
+ ((pgwd = atoi(eoptarg)) < 1)){
+ (void)fputs(
+ "pr: -w width must be 1 or more \n",errf);
+ return(1);
+ }
+ break;
+ case '?':
+ default:
+ return(1);
+ }
+ }
+
+ /*
+ * default and sanity checks
+ */
+ if (!clcnt) {
+ if (merge) {
+ if ((clcnt = argc - eoptind) <= 1) {
+ clcnt = CLCNT;
+ merge = 0;
+ }
+ } else
+ clcnt = CLCNT;
+ }
+ if (across) {
+ if (clcnt == 1) {
+ (void)fputs("pr: -a flag requires multiple columns\n",
+ errf);
+ return(1);
+ }
+ if (merge) {
+ (void)fputs("pr: -m cannot be used with -a\n", errf);
+ return(1);
+ }
+ }
+ if (!wflag) {
+ if (sflag)
+ pgwd = SPGWD;
+ else
+ pgwd = PGWD;
+ }
+ if (cflag || merge) {
+ if (!eflag) {
+ inchar = INCHAR;
+ ingap = INGAP;
+ }
+ if (!iflag) {
+ ochar = OCHAR;
+ ogap = OGAP;
+ }
+ }
+ if (cflag) {
+ if (merge) {
+ (void)fputs(
+ "pr: -m cannot be used with multiple columns\n", errf);
+ return(1);
+ }
+ if (nmwd) {
+ colwd = (pgwd + 1 - (clcnt * (nmwd + 2)))/clcnt;
+ pgwd = ((colwd + nmwd + 2) * clcnt) - 1;
+ } else {
+ colwd = (pgwd + 1 - clcnt)/clcnt;
+ pgwd = ((colwd + 1) * clcnt) - 1;
+ }
+ if (colwd < 1) {
+ (void)fprintf(errf,
+ "pr: page width is too small for %d columns\n",clcnt);
+ return(1);
+ }
+ }
+ if (!lines)
+ lines = LINES;
+
+ /*
+ * make sure long enough for headers. if not disable
+ */
+ if (lines <= HEADLEN + TAILLEN)
+ ++nohead;
+ else if (!nohead)
+ lines -= HEADLEN + TAILLEN;
+
+ /*
+ * adjust for double space on odd length pages
+ */
+ if (dspace) {
+ if (lines == 1)
+ dspace = 0;
+ else {
+ if (lines & 1)
+ ++addone;
+ lines /= 2;
+ }
+ }
+
+ /*
+ * open /dev/tty if we are to pause before each page
+ * but only if stdout is a terminal and stdin is not a terminal
+ */
+ if (ttyout && pgpause && !isatty(fileno(stdin))) {
+ if ((ttyinf = fopen("/dev/tty", "r")) == NULL) {
+ (void)fprintf(errf, "pr: cannot open terminal\n");
+ return(1);
+ }
+ }
+
+ return(0);
+}
diff --git a/usr.bin/pr/pr.h b/usr.bin/pr/pr.h
new file mode 100644
index 0000000..0afd374
--- /dev/null
+++ b/usr.bin/pr/pr.h
@@ -0,0 +1,79 @@
+/* $NetBSD: pr.h,v 1.5 2012/07/24 02:13:04 ginsbach Exp $ */
+
+/*-
+ * Copyright (c) 1991 Keith Muller.
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2012
+ * The NetBSD Foundation, Inc.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Keith Muller of the University of California, San Diego.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)pr.h 8.1 (Berkeley) 6/6/93
+ * $NetBSD: pr.h,v 1.5 2012/07/24 02:13:04 ginsbach Exp $
+ */
+
+/*
+ * parameter defaults
+ */
+#define CLCNT 1
+#define INCHAR '\t'
+#define INGAP 8
+#define OCHAR '\t'
+#define OGAP 8
+#define LINES 66
+#define NMWD 5
+#define NMCHAR '\t'
+#define SCHAR '\t'
+#define PGWD 72
+#define SPGWD 512
+
+/*
+ * misc default values
+ */
+#define HDFMT "%s %s Page %d\n\n\n"
+#define HEADLEN 5
+#define TAILLEN 5
+#define TIMEFMT "%b %e %H:%M %Y"
+#define FNAME ""
+#define LBUF 8192
+#define HDBUF 512
+
+/* when to pause before (for -f and -p options) */
+#define NO_PAUSE 0
+#define FIRSTPAGE 1
+#define ENSUINGPAGES 2
+#define EACHPAGE (FIRSTPAGE | ENSUINGPAGES)
+
+/*
+ * structure for vertical columns. Used to balance cols on last page
+ */
+struct vcol {
+ char *pt; /* ptr to col */
+ int cnt; /* char count */
+};
diff --git a/usr.bin/printf/printf.1 b/usr.bin/printf/printf.1
new file mode 100644
index 0000000..588614d
--- /dev/null
+++ b/usr.bin/printf/printf.1
@@ -0,0 +1,438 @@
+.\" $NetBSD: printf.1,v 1.31 2018/08/31 17:27:35 kre Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)printf.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd August 31, 2018
+.Dt PRINTF 1
+.Os
+.Sh NAME
+.Nm printf
+.Nd formatted output
+.Sh SYNOPSIS
+.Nm
+.Ar format
+.Op Ar arguments ...
+.Sh DESCRIPTION
+.Nm
+formats and prints its arguments, after the first, under control
+of the
+.Ar format .
+The
+.Ar format
+is a character string which contains three types of objects: plain characters,
+which are simply copied to standard output, character escape sequences which
+are converted and copied to the standard output, and format specifications,
+each of which causes printing of the next successive
+.Ar argument .
+.Pp
+The
+.Ar arguments
+after the first are treated as strings if the corresponding format is
+either
+.Cm b ,
+.Cm B ,
+.Cm c ,
+or
+.Cm s ;
+otherwise it is evaluated as a C constant, with the following extensions:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+A leading plus or minus sign is allowed.
+.It
+If the leading character is a single or double quote, the value is the ASCII
+code of the next character.
+.El
+.Pp
+The format string is reused as often as necessary to satisfy the
+.Ar arguments .
+Any extra format specifications are evaluated with zero or the null
+string.
+.Pp
+Character escape sequences are in backslash notation as defined in
+.St -ansiC .
+The characters and their meanings are as follows:
+.Bl -tag -width Ds -offset indent
+.It Cm \ee
+Write an
+.Aq escape
+character.
+.It Cm \ea
+Write a
+.Aq bell
+character.
+.It Cm \eb
+Write a
+.Aq backspace
+character.
+.It Cm \ef
+Write a
+.Aq form-feed
+character.
+.It Cm \en
+Write a
+.Aq new-line
+character.
+.It Cm \er
+Write a
+.Aq carriage return
+character.
+.It Cm \et
+Write a
+.Aq tab
+character.
+.It Cm \ev
+Write a
+.Aq vertical tab
+character.
+.It Cm \e\'
+Write a
+.Aq single quote
+character.
+.It Cm \e"
+Write a
+.Aq double quote
+character.
+.It Cm \e\e
+Write a backslash character.
+.It Cm \e Ns Ar num
+Write an 8\-bit character whose ASCII
+value is the 1\-, 2\-, or 3\-digit octal number
+.Ar num .
+.It Cm \ex Ns Ar xx
+Write an 8\-bit character whose ASCII
+value is the 1\- or 2\-digit hexadecimal number
+.Ar xx .
+.El
+.Pp
+Each format specification is introduced by the percent character
+.Pq Dq \&% .
+The remainder of the format specification includes,
+in the following order:
+.Bl -tag -width Ds
+.It Zero or more of the following flags :
+.Bl -tag -width Ds
+.It Cm #
+A
+.Sq #
+character specifying that the value should be printed in an
+.Dq alternative form .
+For
+.Cm b ,
+.Cm c ,
+.Cm d ,
+and
+.Cm s
+formats, this option has no effect.
+For the
+.Cm o
+format the precision of the number is increased to force the first
+character of the output string to a zero.
+For the
+.Cm x
+.Pq Cm X
+format, a non-zero result has the string
+.Li 0x
+.Pq Li 0X
+prepended to it.
+For
+.Cm e ,
+.Cm E ,
+.Cm f ,
+.Cm F ,
+.Cm g ,
+and
+.Cm G
+formats, the result will always contain a decimal point, even if no
+digits follow the point (normally, a decimal point only appears in the
+results of those formats if a digit follows the decimal point).
+For
+.Cm g
+and
+.Cm G
+formats, trailing zeros are not removed from the result as they
+would otherwise be.
+.\" I turned this off - decided it isn't a valid use of '#'
+.\" For the
+.\" .Cm B
+.\" format, backslash-escape sequences are expanded first;
+.It Cm \&\-
+A minus sign
+.Sq \-
+which specifies
+.Em left adjustment
+of the output in the indicated field;
+.It Cm \&+
+A
+.Sq \&+
+character specifying that there should always be
+a sign placed before the number when using signed formats.
+.It Sq \&\ \&
+A space specifying that a blank should be left before a positive number
+for a signed format.
+A
+.Sq \&+
+overrides a space if both are used;
+.It Cm \&0
+A zero `0' character indicating that zero-padding should be used
+rather than blank-padding.
+A
+.Sq \-
+overrides a
+.Sq \&0
+if both are used;
+.El
+.It Field Width :
+An optional digit string specifying a
+.Em field width ;
+if the output string has fewer characters than the field width it will
+be blank-padded on the left (or right, if the left-adjustment indicator
+has been given) to make up the field width (note that a leading zero
+is a flag, but an embedded zero is part of a field width);
+.It Precision :
+An optional period,
+.Sq Cm \&. ,
+followed by an optional digit string giving a
+.Em precision
+which specifies the number of digits to appear after the decimal point,
+for
+.Cm e
+and
+.Cm f
+formats, or the maximum number of characters to be printed
+from a string
+.Sm off
+.Pf ( Cm b ,
+.Sm on
+.Cm B ,
+and
+.Cm s
+formats); if the digit string is missing, the precision is treated
+as zero;
+.It Format :
+A character which indicates the type of format to use (one of
+.Cm diouxXfFeEgGaAbBcs ) .
+.El
+.Pp
+A field width or precision may be
+.Sq Cm \&*
+instead of a digit string.
+In this case an
+.Ar argument
+supplies the field width or precision.
+.Pp
+The format characters and their meanings are:
+.Bl -tag -width Fl
+.It Cm diouXx
+The
+.Ar argument ,
+which must represent an integer constant,
+with an optional leading plus or minus sign,
+is printed as a signed decimal (d or i),
+unsigned octal (o), unsigned decimal (u),
+or unsigned hexadecimal (X or x).
+.It Cm fF
+The
+.Ar argument
+is printed in the style
+.Sm off
+.Pf [\-]ddd Cm \&. No ddd
+.Sm on
+where the number of d's
+after the decimal point is equal to the precision specification for
+the argument.
+If the precision is missing, 6 digits are given; if the precision
+is explicitly 0, no digits and no decimal point are printed.
+If the number is Infinity, or Not a Number (NaN), then
+.Dq inf
+.Pq \&or Dq nan
+is printed for
+.Cm f
+format, and
+.Dq INF
+.Pq \&or Dq NAN
+for
+.Cm F
+format.
+.It Cm eE
+The
+.Ar argument
+is printed in the style
+.Sm off
+.Pf [\-]d Cm \&. No ddd Cm e No \*(Pmdd
+.Sm on
+where there
+is one digit before the decimal point and the number after is equal to
+the precision specification for the argument; when the precision is
+missing, 6 digits are produced.
+An upper-case E is used for an
+.Sq E
+format, and upper-case for Infinity and NaN as for
+.Sq F
+format.
+.It Cm gG
+The
+.Ar argument
+is printed in style
+.Cm f
+.Pq Cm F
+or in style
+.Cm e
+.Pq Cm E
+whichever gives full precision in minimum space.
+.It Cm aA
+The
+.Ar argument
+is treated as a floating point number,
+for which the underlying hexadecimal representation is
+printed.
+See
+.Xr printf 3
+for the details.
+.It Cm b
+Characters from the string
+.Ar argument
+are printed with backslash-escape sequences expanded.
+.Pp
+The following additional backslash-escape sequences are supported:
+.Bl -tag -width Ds
+.It Cm \ec
+Causes
+.Nm
+to ignore any remaining characters in the string operand containing it,
+any remaining string operands, and any additional characters in
+the format operand.
+.It Cm \e0 Ns Ar num
+Write an 8\-bit character whose ASCII value is the 1\-, 2\-, or
+3\-digit octal number
+.Ar num .
+.It Cm \e^ Ns Ar c
+Write the control character
+.Ar c .
+Generates characters `\e000' through `\e037`, and `\e177' (from `\e^?').
+.It Cm \eM\- Ns Ar c
+Write the character
+.Ar c
+with the 8th bit set.
+Generates characters `\e241' through `\e376`.
+.It Cm \eM^ Ns Ar c
+Write the control character
+.Ar c
+with the 8th bit set.
+Generates characters `\e200' through `\e237`, and `\e377' (from `\eM^?').
+.El
+.It Cm B
+Characters from the string
+.Ar argument
+are printed with unprintable characters backslash-escaped using the
+.Sm off
+.Pf ` Cm \e Ar c No ',
+.Pf ` Cm \e^ Ar c No ',
+.Pf ` Cm \eM\- Ar c No '
+or
+.Pf ` Cm \eM^ Ar c No ',
+.Sm on
+formats described above.
+.It Cm c
+The first character of
+.Ar argument
+is printed.
+.It Cm s
+Characters from the string
+.Ar argument
+are printed until the end is reached or until the number of characters
+indicated by the precision specification is reached; if the
+precision is omitted, all characters in the string are printed.
+.It Cm \&%
+Print a `%'; no argument is used.
+.El
+.Pp
+In no case does a non-existent or small field width cause truncation of
+a field; padding takes place only if the specified field width exceeds
+the actual width.
+.Pp
+If the first character of
+.Ar format
+is a dash,
+.Ar format
+must be preceded by a word consisting of two dashes (--) to prevent it
+from being interpreted as an option string.
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr echo 1 ,
+.Xr printf 3 ,
+.Xr vis 3 ,
+.Xr printf 9
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.1-2001 .
+.Pp
+Support for the floating point formats and `*' as a field width and precision
+are optional in POSIX.
+.Pp
+The behaviour of the %B format and the \e', \e", \exxx, \ee and
+\e[M][\-|^]c escape sequences are undefined in POSIX.
+.Sh BUGS
+Since the floating point numbers are translated from ASCII to
+floating-point and then back again, floating-point precision may be lost.
+.Pp
+Hexadecimal character constants are restricted to, and should be specified
+as, two character constants.
+This is contrary to the ISO C standard but
+does guarantee detection of the end of the constant.
+.Sh NOTES
+All formats which treat the
+.Ar argument
+as a number first convert the
+.Ar argument
+from its external representation as a character string
+to an internal numeric representation, and then apply the
+format to the internal numeric representation, producing
+another external character string representation.
+One might expect the
+.Cm \&%c
+format to do likewise, but in fact it does not.
+.Pp
+To convert a string representation of a decimal, octal, or hexadecimal
+number into the corresponding character, two nested
+.Nm
+invocations may be used, in which the inner invocation
+converts the input to an octal string, and the outer
+invocation uses the octal string as part of a format.
+For example, the following command outputs the character whose code
+is 0x0A, which is a newline in ASCII:
+.Pp
+.Dl printf \&"$(printf \&"\e\e%o" \&"0x0A")"
diff --git a/usr.bin/printf/printf.c b/usr.bin/printf/printf.c
new file mode 100644
index 0000000..4d88e7d
--- /dev/null
+++ b/usr.bin/printf/printf.c
@@ -0,0 +1,709 @@
+/* $NetBSD: printf.c,v 1.48 2019/01/27 12:03:09 kre Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if !defined(BUILTIN) && !defined(SHELL)
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif
+#endif
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)printf.c 8.2 (Berkeley) 3/22/95";
+#else
+__RCSID("$NetBSD: printf.c,v 1.48 2019/01/27 12:03:09 kre Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef __GNUC__
+#define ESCAPE '\e'
+#else
+#define ESCAPE 033
+#endif
+
+static void conv_escape_str(char *, void (*)(int), int);
+static char *conv_escape(char *, char *, int);
+static char *conv_expand(const char *);
+static char getchr(void);
+static double getdouble(void);
+static int getwidth(void);
+static intmax_t getintmax(void);
+static char *getstr(void);
+static char *mklong(const char *, char);
+static void check_conversion(const char *, const char *);
+static void usage(void);
+
+static void b_count(int);
+static void b_output(int);
+static size_t b_length;
+static char *b_fmt;
+
+static int rval;
+static char **gargv;
+
+#ifdef BUILTIN /* csh builtin */
+#define main progprintf
+#endif
+
+#ifdef SHELL /* sh (aka ash) builtin */
+#define main printfcmd
+#include "../../bin/sh/bltin/bltin.h"
+#endif /* SHELL */
+
+#define PF(f, func) { \
+ if (fieldwidth != -1) { \
+ if (precision != -1) \
+ error = printf(f, fieldwidth, precision, func); \
+ else \
+ error = printf(f, fieldwidth, func); \
+ } else if (precision != -1) \
+ error = printf(f, precision, func); \
+ else \
+ error = printf(f, func); \
+}
+
+#define APF(cpp, f, func) { \
+ if (fieldwidth != -1) { \
+ if (precision != -1) \
+ error = asprintf(cpp, f, fieldwidth, precision, func); \
+ else \
+ error = asprintf(cpp, f, fieldwidth, func); \
+ } else if (precision != -1) \
+ error = asprintf(cpp, f, precision, func); \
+ else \
+ error = asprintf(cpp, f, func); \
+}
+
+#ifdef main
+int main(int, char *[]);
+#endif
+
+int
+main(int argc, char *argv[])
+{
+ char *fmt, *start;
+ int fieldwidth, precision;
+ char nextch;
+ char *format;
+ char ch;
+ int error, o;
+
+#if !defined(SHELL) && !defined(BUILTIN)
+ (void)setlocale (LC_ALL, "");
+#endif
+
+ rval = 0; /* clear for builtin versions (avoid holdover) */
+
+ while ((o = getopt(argc, argv, "")) != -1) {
+ switch (o) {
+ case '?':
+ default:
+ usage();
+ return 1;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ usage();
+ return 1;
+ }
+
+ format = *argv;
+ gargv = ++argv;
+
+#define SKIP1 "#-+ 0'"
+#define SKIP2 "0123456789"
+ do {
+ /*
+ * Basic algorithm is to scan the format string for conversion
+ * specifications -- once one is found, find out if the field
+ * width or precision is a '*'; if it is, gather up value.
+ * Note, format strings are reused as necessary to use up the
+ * provided arguments, arguments of zero/null string are
+ * provided to use up the format string.
+ */
+
+ /* find next format specification */
+ for (fmt = format; (ch = *fmt++) != '\0';) {
+ if (ch == '\\') {
+ char c_ch;
+ fmt = conv_escape(fmt, &c_ch, 0);
+ putchar(c_ch);
+ continue;
+ }
+ if (ch != '%' || (*fmt == '%' && ++fmt)) {
+ (void)putchar(ch);
+ continue;
+ }
+
+ /*
+ * Ok - we've found a format specification,
+ * Save its address for a later printf().
+ */
+ start = fmt - 1;
+
+ /* skip to field width */
+ fmt += strspn(fmt, SKIP1);
+ if (*fmt == '*') {
+ fmt++;
+ fieldwidth = getwidth();
+ } else {
+ fieldwidth = -1;
+
+ /* skip to possible '.' for precision */
+ fmt += strspn(fmt, SKIP2);
+ }
+
+ if (*fmt == '.') {
+ /* get following precision */
+ fmt++;
+ if (*fmt == '*') {
+ fmt++;
+ precision = getwidth();
+ } else {
+ precision = -1;
+ fmt += strspn(fmt, SKIP2);
+ }
+ } else
+ precision = -1;
+
+ ch = *fmt;
+ if (!ch) {
+ warnx("%s: missing format character", start);
+ return 1;
+ }
+
+ /*
+ * null terminate format string to we can use it
+ * as an argument to printf.
+ */
+ nextch = fmt[1];
+ fmt[1] = 0;
+
+ switch (ch) {
+
+ case 'B': {
+ const char *p = conv_expand(getstr());
+
+ if (p == NULL)
+ goto out;
+ *fmt = 's';
+ PF(start, p);
+ if (error < 0)
+ goto out;
+ break;
+ }
+ case 'b': {
+ /*
+ * There has to be a better way to do this,
+ * but the string we generate might have
+ * embedded nulls
+ */
+ static char *a, *t;
+ char *cp = getstr();
+
+ /* Free on entry in case shell longjumped out */
+ if (a != NULL)
+ free(a);
+ a = NULL;
+ if (t != NULL)
+ free(t);
+ t = NULL;
+
+ /* Count number of bytes we want to output */
+ b_length = 0;
+ conv_escape_str(cp, b_count, 0);
+ t = malloc(b_length + 1);
+ if (t == NULL)
+ goto out;
+ (void)memset(t, 'x', b_length);
+ t[b_length] = 0;
+
+ /* Get printf to calculate the lengths */
+ *fmt = 's';
+ APF(&a, start, t);
+ if (error == -1)
+ goto out;
+ b_fmt = a;
+
+ /* Output leading spaces and data bytes */
+ conv_escape_str(cp, b_output, 1);
+
+ /* Add any trailing spaces */
+ printf("%s", b_fmt);
+ break;
+ }
+ case 'c': {
+ char p = getchr();
+
+ PF(start, p);
+ if (error < 0)
+ goto out;
+ break;
+ }
+ case 's': {
+ char *p = getstr();
+
+ PF(start, p);
+ if (error < 0)
+ goto out;
+ break;
+ }
+ case 'd':
+ case 'i': {
+ intmax_t p = getintmax();
+ char *f = mklong(start, ch);
+
+ PF(f, p);
+ if (error < 0)
+ goto out;
+ break;
+ }
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X': {
+ uintmax_t p = (uintmax_t)getintmax();
+ char *f = mklong(start, ch);
+
+ PF(f, p);
+ if (error < 0)
+ goto out;
+ break;
+ }
+ case 'a':
+ case 'A':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G': {
+ double p = getdouble();
+
+ PF(start, p);
+ if (error < 0)
+ goto out;
+ break;
+ }
+ case '%':
+ /* Don't ask, but this is useful ... */
+ if (fieldwidth == 'N' && precision == 'B')
+ return 0;
+ /* FALLTHROUGH */
+ default:
+ warnx("%s: invalid directive", start);
+ return 1;
+ }
+ *fmt++ = ch;
+ *fmt = nextch;
+ /* escape if a \c was encountered */
+ if (rval & 0x100)
+ return rval & ~0x100;
+ }
+ } while (gargv != argv && *gargv);
+
+ return rval & ~0x100;
+ out:
+ warn("print failed");
+ return 1;
+}
+
+/* helper functions for conv_escape_str */
+
+static void
+/*ARGSUSED*/
+b_count(int ch)
+{
+ b_length++;
+}
+
+/* Output one converted character for every 'x' in the 'format' */
+
+static void
+b_output(int ch)
+{
+ for (;;) {
+ switch (*b_fmt++) {
+ case 0:
+ b_fmt--;
+ return;
+ case ' ':
+ putchar(' ');
+ break;
+ default:
+ putchar(ch);
+ return;
+ }
+ }
+}
+
+
+/*
+ * Print SysV echo(1) style escape string
+ * Halts processing string if a \c escape is encountered.
+ */
+static void
+conv_escape_str(char *str, void (*do_putchar)(int), int quiet)
+{
+ int value;
+ int ch;
+ char c;
+
+ while ((ch = *str++) != '\0') {
+ if (ch != '\\') {
+ do_putchar(ch);
+ continue;
+ }
+
+ ch = *str++;
+ if (ch == 'c') {
+ /* \c as in SYSV echo - abort all processing.... */
+ rval |= 0x100;
+ break;
+ }
+
+ /*
+ * %b string octal constants are not like those in C.
+ * They start with a \0, and are followed by 0, 1, 2,
+ * or 3 octal digits.
+ */
+ if (ch == '0') {
+ int octnum = 0, i;
+ for (i = 0; i < 3; i++) {
+ if (!isdigit((unsigned char)*str) || *str > '7')
+ break;
+ octnum = (octnum << 3) | (*str++ - '0');
+ }
+ do_putchar(octnum);
+ continue;
+ }
+
+ /* \[M][^|-]C as defined by vis(3) */
+ if (ch == 'M' && *str == '-') {
+ do_putchar(0200 | str[1]);
+ str += 2;
+ continue;
+ }
+ if (ch == 'M' && *str == '^') {
+ str++;
+ value = 0200;
+ ch = '^';
+ } else
+ value = 0;
+ if (ch == '^') {
+ ch = *str++;
+ if (ch == '?')
+ value |= 0177;
+ else
+ value |= ch & 037;
+ do_putchar(value);
+ continue;
+ }
+
+ /* Finally test for sequences valid in the format string */
+ str = conv_escape(str - 1, &c, quiet);
+ do_putchar(c);
+ }
+}
+
+/*
+ * Print "standard" escape characters
+ */
+static char *
+conv_escape(char *str, char *conv_ch, int quiet)
+{
+ char value;
+ char ch;
+ char num_buf[4], *num_end;
+
+ ch = *str++;
+
+ switch (ch) {
+ case '\0':
+ if (!quiet)
+ warnx("incomplete escape sequence");
+ rval = 1;
+ value = '\\';
+ --str;
+ break;
+
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ num_buf[0] = ch;
+ ch = str[0];
+ num_buf[1] = ch;
+ num_buf[2] = (char)(ch != '\0' ? str[1] : '\0');
+ num_buf[3] = '\0';
+ value = (char)strtoul(num_buf, &num_end, 8);
+ str += num_end - (num_buf + 1);
+ break;
+
+ case 'x':
+ /*
+ * Hexadecimal character constants are not required to be
+ * supported (by SuS v1) because there is no consistent
+ * way to detect the end of the constant.
+ * Supporting 2 byte constants is a compromise.
+ */
+ ch = str[0];
+ num_buf[0] = ch;
+ num_buf[1] = (char)(ch != '\0' ? str[1] : '\0');
+ num_buf[2] = '\0';
+ value = (char)strtoul(num_buf, &num_end, 16);
+ str += num_end - num_buf;
+ break;
+
+ case '\\': value = '\\'; break; /* backslash */
+ case '\'': value = '\''; break; /* single quote */
+ case '"': value = '"'; break; /* double quote */
+ case 'a': value = '\a'; break; /* alert */
+ case 'b': value = '\b'; break; /* backspace */
+ case 'e': value = ESCAPE; break; /* escape */
+ case 'E': value = ESCAPE; break; /* escape */
+ case 'f': value = '\f'; break; /* form-feed */
+ case 'n': value = '\n'; break; /* newline */
+ case 'r': value = '\r'; break; /* carriage-return */
+ case 't': value = '\t'; break; /* tab */
+ case 'v': value = '\v'; break; /* vertical-tab */
+
+ default:
+ if (!quiet)
+ warnx("unknown escape sequence `\\%c'", ch);
+ rval = 1;
+ value = ch;
+ break;
+ }
+
+ *conv_ch = value;
+ return str;
+}
+
+/* expand a string so that everything is printable */
+
+static char *
+conv_expand(const char *str)
+{
+ static char *conv_str;
+ char *cp;
+ char ch;
+
+ if (conv_str)
+ free(conv_str);
+ /* get a buffer that is definitely large enough.... */
+ conv_str = malloc(4 * strlen(str) + 1);
+ if (!conv_str)
+ return NULL;
+ cp = conv_str;
+
+ while ((ch = *(const char *)str++) != '\0') {
+ switch (ch) {
+ /* Use C escapes for expected control characters */
+ case '\\': ch = '\\'; break; /* backslash */
+ case '\'': ch = '\''; break; /* single quote */
+ case '"': ch = '"'; break; /* double quote */
+ case '\a': ch = 'a'; break; /* alert */
+ case '\b': ch = 'b'; break; /* backspace */
+ case ESCAPE: ch = 'e'; break; /* escape */
+ case '\f': ch = 'f'; break; /* form-feed */
+ case '\n': ch = 'n'; break; /* newline */
+ case '\r': ch = 'r'; break; /* carriage-return */
+ case '\t': ch = 't'; break; /* tab */
+ case '\v': ch = 'v'; break; /* vertical-tab */
+ default:
+ /* Copy anything printable */
+ if (isprint((unsigned char)ch)) {
+ *cp++ = ch;
+ continue;
+ }
+ /* Use vis(3) encodings for the rest */
+ *cp++ = '\\';
+ if (ch & 0200) {
+ *cp++ = 'M';
+ ch &= (char)~0200;
+ }
+ if (ch == 0177) {
+ *cp++ = '^';
+ *cp++ = '?';
+ continue;
+ }
+ if (ch < 040) {
+ *cp++ = '^';
+ *cp++ = ch | 0100;
+ continue;
+ }
+ *cp++ = '-';
+ *cp++ = ch;
+ continue;
+ }
+ *cp++ = '\\';
+ *cp++ = ch;
+ }
+
+ *cp = 0;
+ return conv_str;
+}
+
+static char *
+mklong(const char *str, char ch)
+{
+ static char copy[64];
+ size_t len;
+
+ len = strlen(str) + 2;
+ if (len > sizeof copy) {
+ warnx("format %s too complex", str);
+ len = 4;
+ }
+ (void)memmove(copy, str, len - 3);
+ copy[len - 3] = 'j';
+ copy[len - 2] = ch;
+ copy[len - 1] = '\0';
+ return copy;
+}
+
+static char
+getchr(void)
+{
+ if (!*gargv)
+ return 0;
+ return **gargv++;
+}
+
+static char *
+getstr(void)
+{
+ static char empty[] = "";
+ if (!*gargv)
+ return empty;
+ return *gargv++;
+}
+
+static int
+getwidth(void)
+{
+ unsigned long val;
+ char *s, *ep;
+
+ s = *gargv;
+ if (s == NULL)
+ return 0;
+ gargv++;
+
+ errno = 0;
+ val = strtoul(s, &ep, 0);
+ check_conversion(s, ep);
+
+ /* Arbitrarily 'restrict' field widths to 1Mbyte */
+ if (val > 1 << 20) {
+ warnx("%s: invalid field width", s);
+ return 0;
+ }
+
+ return (int)val;
+}
+
+static intmax_t
+getintmax(void)
+{
+ intmax_t val;
+ char *cp, *ep;
+
+ cp = *gargv;
+ if (cp == NULL)
+ return 0;
+ gargv++;
+
+ if (*cp == '\"' || *cp == '\'')
+ return *(cp + 1);
+
+ errno = 0;
+ val = strtoimax(cp, &ep, 0);
+ check_conversion(cp, ep);
+ return val;
+}
+
+static double
+getdouble(void)
+{
+ double val;
+ char *ep;
+
+ if (!*gargv)
+ return 0.0;
+
+ if (**gargv == '\"' || **gargv == '\'')
+ return (double) *((*gargv++)+1);
+
+ errno = 0;
+ val = strtod(*gargv, &ep);
+ check_conversion(*gargv++, ep);
+ return val;
+}
+
+static void
+check_conversion(const char *s, const char *ep)
+{
+ if (*ep) {
+ if (ep == s)
+ warnx("%s: expected numeric value", s);
+ else
+ warnx("%s: not completely converted", s);
+ rval = 1;
+ } else if (errno == ERANGE) {
+ warnx("%s: %s", s, strerror(ERANGE));
+ rval = 1;
+ }
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s format [arg ...]\n", getprogname());
+}
diff --git a/usr.bin/renice/renice.8 b/usr.bin/renice/renice.8
new file mode 100644
index 0000000..5dedc3e
--- /dev/null
+++ b/usr.bin/renice/renice.8
@@ -0,0 +1,151 @@
+.\" $NetBSD: renice.8,v 1.15 2012/12/06 07:52:12 wiz Exp $
+.\"
+.\" Copyright (c) 1983, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)renice.8 8.1 (Berkeley) 6/9/93
+.\"
+.Dd December 6, 2012
+.Dt RENICE 8
+.Os
+.Sh NAME
+.Nm renice
+.Nd alter priority of running processes
+.Sh SYNOPSIS
+.Nm
+.Ar priority
+.Oo
+.Op Fl p
+.Ar pid ...
+.Oc
+.Oo
+.Fl g
+.Ar pgrp ...
+.Oc
+.Oo
+.Fl u
+.Ar user ...
+.Oc
+.Nm
+.Fl n
+.Ar increment
+.Oo
+.Op Fl p
+.Ar pid ...
+.Oc
+.Oo
+.Fl g
+.Ar pgrp ...
+.Oc
+.Oo
+.Fl u
+.Ar user ...
+.Oc
+.Sh DESCRIPTION
+.Nm
+alters the
+scheduling priority of one or more running processes.
+The following
+.Ar who
+parameters are interpreted as process ID's, process group
+ID's, or user names.
+.Nm Ns 'ing
+a process group causes all processes in the process group
+to have their scheduling priority altered.
+.Nm Ns 'ing
+a user causes all processes owned by the user to have
+their scheduling priority altered.
+By default, the processes to be affected are specified by
+their process ID's.
+.Pp
+Options supported by
+.Nm :
+.Bl -tag -width Ds
+.It Fl g
+Force
+.Ar who
+parameters to be interpreted as process group ID's.
+.It Fl n
+Instead of changing the specified processes to the given priority,
+interpret the following argument as an increment to be applied to
+the current priority of each process.
+.It Fl u
+Force the
+.Ar who
+parameters to be interpreted as user names.
+.It Fl p
+Resets the
+.Ar who
+interpretation to be (the default) process ID's.
+.El
+.Pp
+For example,
+.Bd -literal -offset indent
+renice +1 987 -u daemon root -p 32
+.Ed
+.Pp
+would change the priority of process ID's 987 and 32, and
+all processes owned by users daemon and root.
+.Pp
+Users other than the super-user may only alter the priority of
+processes they own,
+and can only monotonically increase their ``nice value''
+within the range 0 to
+.Dv PRIO_MAX
+(20).
+(This prevents overriding administrative fiats.)
+The super-user
+may alter the priority of any process
+and set the priority to any value in the range
+.Dv PRIO_MIN
+(\-20)
+to
+.Dv PRIO_MAX .
+.Pp
+Useful priorities are:
+0, the ``base'' scheduling priority;
+20, the affected processes will run only when nothing at the base priority
+wants to;
+anything negative, the processes will receive a scheduling preference.
+.Sh FILES
+.Bl -tag -width /etc/passwd -compact
+.It Pa /etc/passwd
+to map user names to user ID's
+.El
+.Sh SEE ALSO
+.Xr nice 1 ,
+.Xr prenice 1 ,
+.Xr getpriority 2 ,
+.Xr setpriority 2
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.0 .
+.Sh BUGS
+Non super-users can not increase scheduling priorities of their own processes,
+even if they were the ones that decreased the priorities in the first place.
diff --git a/usr.bin/renice/renice.c b/usr.bin/renice/renice.c
new file mode 100644
index 0000000..977f081
--- /dev/null
+++ b/usr.bin/renice/renice.c
@@ -0,0 +1,181 @@
+/* $NetBSD: renice.c,v 1.18 2008/07/21 14:19:25 lukem Exp $ */
+
+/*
+ * Copyright (c) 1983, 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1983, 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+/*static char sccsid[] = "from: @(#)renice.c 8.1 (Berkeley) 6/9/93";*/
+__RCSID("$NetBSD: renice.c,v 1.18 2008/07/21 14:19:25 lukem Exp $");
+#endif /* not lint */
+
+#include <sys/resource.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+static int getnum(const char *, const char *, int *);
+static int donice(int, id_t, int, int);
+static void usage(void) __dead;
+
+/*
+ * Change the priority (nice) of processes
+ * or groups of processes which are already
+ * running.
+ */
+int
+main(int argc, char **argv)
+{
+ int which = PRIO_PROCESS;
+ int prio, errs = 0, incr = 0;
+ id_t who = 0;
+
+ argc--, argv++;
+ if (argc < 2)
+ usage();
+ if (strcmp(*argv, "-n") == 0) {
+ incr = 1;
+ argc--, argv++;
+ if (argc == 0)
+ usage();
+ }
+ if (getnum("priority", *argv, &prio))
+ return 1;
+ argc--, argv++;
+ for (; argc > 0; argc--, argv++) {
+ if (strcmp(*argv, "-g") == 0) {
+ which = PRIO_PGRP;
+ continue;
+ }
+ if (strcmp(*argv, "-u") == 0) {
+ which = PRIO_USER;
+ continue;
+ }
+ if (strcmp(*argv, "-p") == 0) {
+ which = PRIO_PROCESS;
+ continue;
+ }
+ if (which == PRIO_USER) {
+ struct passwd *pwd = getpwnam(*argv);
+
+ if (pwd == NULL) {
+ warnx("%s: unknown user", *argv);
+ errs++;
+ continue;
+ }
+ who = (id_t)pwd->pw_uid;
+ } else {
+ int twho;
+ if (getnum("pid", *argv, &twho)) {
+ errs++;
+ continue;
+ }
+ if (twho < 0) {
+ warnx("%s: bad value", *argv);
+ errs++;
+ continue;
+ }
+ who = (id_t)twho;
+ }
+ errs += donice(which, who, prio, incr);
+ }
+ return errs == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+static int
+getnum(const char *com, const char *str, int *val)
+{
+ long v;
+ char *ep;
+
+ errno = 0;
+ v = strtol(str, &ep, 0);
+
+ if (*ep) {
+ warnx("Bad %s argument: %s", com, str);
+ return 1;
+ }
+ if ((v == LONG_MIN || v == LONG_MAX) && errno == ERANGE) {
+ warn("Invalid %s argument: %s", com, str);
+ return 1;
+ }
+
+ *val = (int)v;
+ return 0;
+}
+
+static int
+donice(int which, id_t who, int prio, int incr)
+{
+ int oldprio;
+
+ errno = 0;
+ if ((oldprio = getpriority(which, who)) == -1 && errno != 0) {
+ warn("%d: getpriority", who);
+ return 1;
+ }
+
+ if (incr)
+ prio = oldprio + prio;
+
+ if (prio > PRIO_MAX)
+ prio = PRIO_MAX;
+ if (prio < PRIO_MIN)
+ prio = PRIO_MIN;
+
+ if (setpriority(which, who, prio) == -1) {
+ warn("%d: setpriority", who);
+ return 1;
+ }
+ (void)printf("%d: old priority %d, new priority %d\n",
+ who, oldprio, prio);
+ return 0;
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "Usage: %s [<priority> | -n <incr>] ",
+ getprogname());
+ (void)fprintf(stderr, "[[-p] <pids>...] [-g <pgrp>...] ");
+ (void)fprintf(stderr, "[-u <user>...]\n");
+ exit(1);
+}
diff --git a/usr.bin/sed/POSIX b/usr.bin/sed/POSIX
new file mode 100644
index 0000000..cbacd89
--- /dev/null
+++ b/usr.bin/sed/POSIX
@@ -0,0 +1,205 @@
+# $NetBSD: POSIX,v 1.5 2014/06/06 00:13:13 christos Exp $
+# @(#)POSIX 8.1 (Berkeley) 6/6/93
+# $FreeBSD: head/usr.bin/sed/POSIX 168417 2007-04-06 08:43:30Z yar $
+
+Comments on the IEEE P1003.2 Draft 12
+ Part 2: Shell and Utilities
+ Section 4.55: sed - Stream editor
+
+Diomidis Spinellis <dds@doc.ic.ac.uk>
+Keith Bostic <bostic@cs.berkeley.edu>
+
+In the following paragraphs, "wrong" usually means "inconsistent with
+historic practice", as most of the following comments refer to
+undocumented inconsistencies between the historical versions of sed and
+the POSIX 1003.2 standard. All the comments are notes taken while
+implementing a POSIX-compatible version of sed, and should not be
+interpreted as official opinions or criticism towards the POSIX committee.
+All uses of "POSIX" refer to section 4.55, Draft 12 of POSIX 1003.2.
+
+ 1. 32V and BSD derived implementations of sed strip the text
+ arguments of the a, c and i commands of their initial blanks,
+ i.e.
+
+ #!/bin/sed -f
+ a\
+ foo\
+ \ indent\
+ bar
+
+ produces:
+
+ foo
+ indent
+ bar
+
+ POSIX does not specify this behavior as the System V versions of
+ sed do not do this stripping. The argument against stripping is
+ that it is difficult to write sed scripts that have leading blanks
+ if they are stripped. The argument for stripping is that it is
+ difficult to write readable sed scripts unless indentation is allowed
+ and ignored, and leading whitespace is obtainable by entering a
+ backslash in front of it. This implementation follows the BSD
+ historic practice.
+
+ 2. Historical versions of sed required that the w flag be the last
+ flag to an s command as it takes an additional argument. This
+ is obvious, but not specified in POSIX.
+
+ 3. Historical versions of sed required that whitespace follow a w
+ flag to an s command. This is not specified in POSIX. This
+ implementation permits whitespace but does not require it.
+
+ 4. Historical versions of sed permitted any number of whitespace
+ characters to follow the w command. This is not specified in
+ POSIX. This implementation permits whitespace but does not
+ require it.
+
+ 5. The rule for the l command differs from historic practice. Table
+ 2-15 includes the various ANSI C escape sequences, including \\
+ for backslash. Some historical versions of sed displayed two
+ digit octal numbers, too, not three as specified by POSIX. POSIX
+ is a cleanup, and is followed by this implementation.
+
+ 6. The POSIX specification for ! does not specify that for a single
+ command the command must not contain an address specification
+ whereas the command list can contain address specifications. The
+ specification for ! implies that "3!/hello/p" works, and it never
+ has, historically. Note,
+
+ 3!{
+ /hello/p
+ }
+
+ does work.
+
+ 7. POSIX does not specify what happens with consecutive ! commands
+ (e.g. /foo/!!!p). Historic implementations allow any number of
+ !'s without changing the behaviour. (It seems logical that each
+ one might reverse the behaviour.) This implementation follows
+ historic practice.
+
+ 8. Historic versions of sed permitted commands to be separated
+ by semi-colons, e.g. 'sed -ne '1p;2p;3q' printed the first
+ three lines of a file. This is not specified by POSIX.
+ Note, the ; command separator is not allowed for the commands
+ a, c, i, w, r, :, b, t, # and at the end of a w flag in the s
+ command. This implementation follows historic practice and
+ implements the ; separator.
+
+ 9. Historic versions of sed terminated the script if EOF was reached
+ during the execution of the 'n' command, i.e.:
+
+ sed -e '
+ n
+ i\
+ hello
+ ' </dev/null
+
+ did not produce any output. POSIX does not specify this behavior.
+ This implementation follows historic practice.
+
+10. Deleted.
+
+11. Historical implementations do not output the change text of a c
+ command in the case of an address range whose first line number
+ is greater than the second (e.g. 3,1). POSIX requires that the
+ text be output. Since the historic behavior doesn't seem to have
+ any particular purpose, this implementation follows the POSIX
+ behavior.
+
+12. POSIX does not specify whether address ranges are checked and
+ reset if a command is not executed due to a jump. The following
+ program will behave in different ways depending on whether the
+ 'c' command is triggered at the third line, i.e. will the text
+ be output even though line 3 of the input will never logically
+ encounter that command.
+
+ 2,4b
+ 1,3c\
+ text
+
+ Historic implementations did not output the text in the above
+ example. Therefore it was believed that a range whose second
+ address was never matched extended to the end of the input.
+ However, the current practice adopted by this implementation,
+ as well as by those from GNU and SUN, is as follows: The text
+ from the 'c' command still isn't output because the second address
+ isn't actually matched; but the range is reset after all if its
+ second address is a line number. In the above example, only the
+ first line of the input will be deleted.
+
+13. Historical implementations allow an output suppressing #n at the
+ beginning of -e arguments as well as in a script file. POSIX
+ does not specify this. This implementation follows historical
+ practice.
+
+14. POSIX does not explicitly specify how sed behaves if no script is
+ specified. Since the sed Synopsis permits this form of the command,
+ and the language in the Description section states that the input
+ is output, it seems reasonable that it behave like the cat(1)
+ command. Historic sed implementations behave differently for "ls |
+ sed", where they produce no output, and "ls | sed -e#", where they
+ behave like cat. This implementation behaves like cat in both cases.
+
+15. The POSIX requirement to open all w files at the beginning makes
+ sed behave nonintuitively when the w commands are preceded by
+ addresses or are within conditional blocks. This implementation
+ follows historic practice and POSIX, by default, and provides the
+ -a option which opens the files only when they are needed.
+
+16. POSIX does not specify how escape sequences other than \n and \D
+ (where D is the delimiter character) are to be treated. This is
+ reasonable, however, it also doesn't state that the backslash is
+ to be discarded from the output regardless. A strict reading of
+ POSIX would be that "echo xyz | sed s/./\a" would display "\ayz".
+ As historic sed implementations always discarded the backslash,
+ this implementation does as well.
+
+17. POSIX specifies that an address can be "empty". This implies
+ that constructs like ",d" or "1,d" and ",5d" are allowed. This
+ is not true for historic implementations or this implementation
+ of sed.
+
+18. The b t and : commands are documented in POSIX to ignore leading
+ white space, but no mention is made of trailing white space.
+ Historic implementations of sed assigned different locations to
+ the labels "x" and "x ". This is not useful, and leads to subtle
+ programming errors, but it is historic practice and changing it
+ could theoretically break working scripts. This implementation
+ follows historic practice.
+
+19. Although POSIX specifies that reading from files that do not exist
+ from within the script must not terminate the script, it does not
+ specify what happens if a write command fails. Historic practice
+ is to fail immediately if the file cannot be opened or written.
+ This implementation follows historic practice.
+
+20. Historic practice is that the \n construct can be used for either
+ string1 or string2 of the y command. This is not specified by
+ POSIX. This implementation follows historic practice.
+
+21. Deleted.
+
+22. Historic implementations of sed ignore the RE delimiter characters
+ within character classes. This is not specified in POSIX. This
+ implementation follows historic practice.
+
+23. Historic implementations handle empty RE's in a special way: the
+ empty RE is interpreted as if it were the last RE encountered,
+ whether in an address or elsewhere. POSIX does not document this
+ behavior. For example the command:
+
+ sed -e /abc/s//XXX/
+
+ substitutes XXX for the pattern abc. The semantics of "the last
+ RE" can be defined in two different ways:
+
+ 1. The last RE encountered when compiling (lexical/static scope).
+ 2. The last RE encountered while running (dynamic scope).
+
+ While many historical implementations fail on programs depending
+ on scope differences, the SunOS version exhibited dynamic scope
+ behaviour. This implementation does dynamic scoping, as this seems
+ the most useful and in order to remain consistent with historical
+ practice.
diff --git a/usr.bin/sed/TEST/hanoi.sed b/usr.bin/sed/TEST/hanoi.sed
new file mode 100644
index 0000000..e58a967
--- /dev/null
+++ b/usr.bin/sed/TEST/hanoi.sed
@@ -0,0 +1,103 @@
+# Towers of Hanoi in sed.
+#
+# from: @(#)hanoi.sed 8.1 (Berkeley) 6/6/93
+# $NetBSD: hanoi.sed,v 1.3 1997/01/09 20:21:35 tls Exp $
+#
+#
+# Ex:
+# Run "sed -f hanoi.sed", and enter:
+#
+# :abcd: : :<CR><CR>
+#
+# note -- TWO carriage returns, a peculiarity of sed), this will output the
+# sequence of states involved in moving 4 rings, the largest called "a" and
+# the smallest called "d", from the first to the second of three towers, so
+# that the rings on any tower at any time are in descending order of size.
+# You can start with a different arrangement and a different number of rings,
+# say :ce:b:ax: and it will give the shortest procedure for moving them all
+# to the middle tower. The rules are: the names of the rings must all be
+# lower-case letters, they must be input within 3 fields (representing the
+# towers) and delimited by 4 colons, such that the letters within each field
+# are in alphabetical order (i.e. rings are in descending order of size).
+#
+# For the benefit of anyone who wants to figure out the script, an "internal"
+# line of the form
+# b:0abx:1a2b3 :2 :3x2
+# has the following meaning: the material after the three markers :1, :2,
+# and :3 represents the three towers; in this case the current set-up is
+# ":ab : :x :". The numbers after a, b and x in these fields indicate
+# that the next time it gets a chance, it will move a to tower 2, move b
+# to tower 3, and move x to tower 2. The string after :0 just keeps track
+# of the alphabetical order of the names of the rings. The b at the
+# beginning means that it is now dealing with ring b (either about to move
+# it, or re-evaluating where it should next be moved to).
+#
+# Although this version is "limited" to 26 rings because of the size of the
+# alphabet, one could write a script using the same idea in which the rings
+# were represented by arbitrary [strings][within][brackets], and in place of
+# the built-in line of the script giving the order of the letters of the
+# alphabet, it would accept from the user a line giving the ordering to be
+# assumed, e.g. [ucbvax][decvax][hplabs][foo][bar].
+#
+# George Bergman
+# Math, UC Berkeley 94720 USA
+
+# cleaning, diagnostics
+s/ *//g
+/^$/d
+/[^a-z:]/{a\
+Illegal characters: use only a-z and ":". Try again.
+d
+}
+/^:[a-z]*:[a-z]*:[a-z]*:$/!{a\
+Incorrect format: use\
+\ : string1 : string2 : string3 :<CR><CR>\
+Try again.
+d
+}
+/\([a-z]\).*\1/{a\
+Repeated letters not allowed. Try again.
+d
+}
+# initial formatting
+h
+s/[a-z]/ /g
+G
+s/^:\( *\):\( *\):\( *\):\n:\([a-z]*\):\([a-z]*\):\([a-z]*\):$/:1\4\2\3:2\5\1\3:3\6\1\2:0/
+s/[a-z]/&2/g
+s/^/abcdefghijklmnopqrstuvwxyz/
+:a
+s/^\(.\).*\1.*/&\1/
+s/.//
+/^[^:]/ba
+s/\([^0]*\)\(:0.*\)/\2\1:/
+s/^[^0]*0\(.\)/\1&/
+:b
+# outputting current state without markers
+h
+s/.*:1/:/
+s/[123]//gp
+g
+:c
+# establishing destinations
+/^\(.\).*\1:1/td
+/^\(.\).*:1[^:]*\11/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\31/
+/^\(.\).*:1[^:]*\12/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\33/
+/^\(.\).*:1[^:]*\13/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\32/
+/^\(.\).*:2[^:]*\11/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\33/
+/^\(.\).*:2[^:]*\12/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\32/
+/^\(.\).*:2[^:]*\13/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\31/
+/^\(.\).*:3[^:]*\11/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\32/
+/^\(.\).*:3[^:]*\12/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\31/
+/^\(.\).*:3[^:]*\13/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\33/
+bc
+# iterate back to find smallest out-of-place ring
+:d
+s/^\(.\)\(:0[^:]*\([^:]\)\1.*:\([123]\)[^:]*\1\)\4/\3\2\4/
+td
+# move said ring (right, resp. left)
+s/^\(.\)\(.*\)\1\([23]\)\(.*:\3[^ ]*\) /\1\2 \4\1\3/
+s/^\(.\)\(.*:\([12]\)[^ ]*\) \(.*\)\1\3/\1\2\1\3\4 /
+tb
+s/.*/Done! Try another, or end with ^D./p
+d
diff --git a/usr.bin/sed/TEST/math.sed b/usr.bin/sed/TEST/math.sed
new file mode 100644
index 0000000..82968b5
--- /dev/null
+++ b/usr.bin/sed/TEST/math.sed
@@ -0,0 +1,164 @@
+#
+# from: @(#)math.sed 8.1 (Berkeley) 6/6/93
+# $NetBSD: math.sed,v 1.3 1997/01/09 20:21:36 tls Exp $
+#
+# Addition and multiplication in sed.
+# ++ for a limited time only do (expr) too!!!
+#
+# Kevin S Braunsdorf, PUCC UNIX Group, ksb@cc.purdue.edu.
+#
+# Ex:
+# echo "4+7*3" | sed -f %f
+
+# make sure the expression is well formed
+s/[ ]//g
+/[+*\/-]$/{
+ a\
+ poorly formed expression, operator on the end
+ q
+}
+/^[+*\/]/{
+ a\
+ poorly formed expression, leading operator
+ q
+}
+
+# fill hold space with done token
+x
+s/^.*/done/
+x
+
+# main loop, process operators (*, + and () )
+: loop
+/^\+/{
+ s///
+ b loop
+}
+/^\(.*\)(\([^)]*\))\(.*\)$/{
+ H
+ s//\2/
+ x
+ s/^\(.*\)\n\(.*\)(\([^()]*\))\(.*\)$/()\2@\4@\1/
+ x
+ b loop
+}
+/^[0-9]*\*/b mul
+/^\([0-9]*\)\+\([0-9+*]*\*[0-9]*\)$/{
+ s//\2+\1/
+ b loop
+}
+/^[0-9]*\+/{
+ s/$/=/
+ b add
+}
+x
+/^done$/{
+ x
+ p
+ d
+}
+/^()/{
+ s///
+ x
+ G
+ s/\(.*\)\n\([^@]*\)@\([^@]*\)@\(.*\)/\2\1\3/
+ x
+ s/[^@]*@[^@]*@\(.*\)/\1/
+ x
+ b loop
+}
+i\
+help, stack problem
+p
+x
+p
+q
+
+# turn mul into add until 1*x -> x
+: mul
+/^0*1\*/{
+ s///
+ b loop
+}
+/^\([0-9]*\)0\*/{
+ s/^\([0-9]*\)0\*\([0-9]*\)/\1*\20/
+ b mul
+}
+s/^\([0-9]*\)1\*/\10*/
+s/^\([0-9]*\)2\*/\11*/
+s/^\([0-9]*\)3\*/\12*/
+s/^\([0-9]*\)4\*/\13*/
+s/^\([0-9]*\)5\*/\14*/
+s/^\([0-9]*\)6\*/\15*/
+s/^\([0-9]*\)7\*/\16*/
+s/^\([0-9]*\)8\*/\17*/
+s/^\([0-9]*\)9\*/\18*/
+s/\*\([0-9*]*\)/*\1+\1/
+b mul
+
+# get rid of a plus term until 0+x -> x
+: add
+/^\+\([0-9+*]*\)=/{
+ s//\1/
+ b loop
+}
+/^\([0-9*]*\)\+=/{
+ s//\1/
+ b loop
+}
+/^\([0-9]*\)\+\([0-9*+]*\)\+=/{
+ s//\2+\1/
+ b loop
+}
+/^\([0-9]*\)0\+\([0-9]*\)\([0-9]\)=/{
+ s//\1+\2=\3/
+ b add
+}
+/^\([0-9]*\)\([0-9]\)\+\([0-9]*\)0=/{
+ s//\1+\3=\2/
+ b add
+}
+/^\([0-9]*\)0\+\([0-9*+]*\)\+\([0-9]*\)\([0-9]\)=/{
+ s//\1+\2+\3=\4/
+ b add
+}
+/^\([0-9]*\)\([0-9]\)\+\([0-9*+]*\)\+\([0-9]*\)0=/{
+ s//\1+\3+\4=\2/
+ b add
+}
+s/^\([0-9]*\)1\+/\10+/
+s/^\([0-9]*\)2\+/\11+/
+s/^\([0-9]*\)3\+/\12+/
+s/^\([0-9]*\)4\+/\13+/
+s/^\([0-9]*\)5\+/\14+/
+s/^\([0-9]*\)6\+/\15+/
+s/^\([0-9]*\)7\+/\16+/
+s/^\([0-9]*\)8\+/\17+/
+s/^\([0-9]*\)9\+/\18+/
+
+s/9=\([0-9]*\)$/_=\1/
+s/8=\([0-9]*\)$/9=\1/
+s/7=\([0-9]*\)$/8=\1/
+s/6=\([0-9]*\)$/7=\1/
+s/5=\([0-9]*\)$/6=\1/
+s/4=\([0-9]*\)$/5=\1/
+s/3=\([0-9]*\)$/4=\1/
+s/2=\([0-9]*\)$/3=\1/
+s/1=\([0-9]*\)$/2=\1/
+/_/{
+ s//_0/
+ : inc
+ s/9_/_0/
+ s/8_/9/
+ s/7_/8/
+ s/6_/7/
+ s/5_/6/
+ s/4_/5/
+ s/3_/4/
+ s/2_/3/
+ s/1_/2/
+ s/0_/1/
+ s/\+_/+1/
+ /_/b inc
+}
+b add
diff --git a/usr.bin/sed/TEST/sed.test b/usr.bin/sed/TEST/sed.test
new file mode 100644
index 0000000..ff4093a
--- /dev/null
+++ b/usr.bin/sed/TEST/sed.test
@@ -0,0 +1,554 @@
+#!/bin/sh -
+# $NetBSD: sed.test,v 1.5 2011/11/12 03:15:05 christos Exp $
+#
+# Copyright (c) 1992 Diomidis Spinellis.
+# Copyright (c) 1992, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# from: @(#)sed.test 8.1 (Berkeley) 6/6/93
+# $NetBSD: sed.test,v 1.5 2011/11/12 03:15:05 christos Exp $
+#
+
+# sed Regression Tests
+#
+# The following files are created:
+# lines[1-4], script1, script2
+# Two directories *.out contain the test results
+
+main()
+{
+ BASE=/usr/bin/sed
+ BASELOG=sed.out
+ TEST=$(cd $(dirname $0)/.. && make -V .OBJDIR)/sed
+ TESTLOG=nsed.out
+ DICT=/usr/share/dict/words
+
+ test_error | more
+
+ awk 'END { for (i = 1; i < 15; i++) print "l1_" i}' </dev/null >lines1
+ awk 'END { for (i = 1; i < 10; i++) print "l2_" i}' </dev/null >lines2
+
+ exec 4>&1 5>&2
+
+ # Set these flags to get messages about known problems
+ BSD=1
+ GNU=0
+ SUN=0
+ tests $BASE $BASELOG
+
+ BSD=0
+ GNU=0
+ SUN=0
+ tests $TEST $TESTLOG
+ exec 1>&4 2>&5
+ diff -c $BASELOG $TESTLOG | more
+}
+
+tests()
+{
+ SED=$1
+ DIR=$2
+ rm -rf $DIR
+ mkdir $DIR
+ MARK=100
+
+ test_args
+ test_addr
+ echo Testing commands
+ test_group
+ test_acid
+ test_branch
+ test_pattern
+ test_print
+ test_subst
+}
+
+mark()
+{
+ MARK=$(expr $MARK + 1)
+ exec 1>&4 2>&5
+ exec >"$DIR/${MARK}_$1"
+ echo "Test $1:$MARK"
+ # Uncomment this line to match tests with sed error messages
+ echo "Test $1:$MARK" >&5
+}
+
+test_args()
+{
+ mark '1.1'
+ echo Testing argument parsing
+ echo First type
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED 's/^/e1_/p' lines1
+ fi
+ mark '1.2' ; $SED -n 's/^/e1_/p' lines1
+ mark '1.3'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED 's/^/e1_/p' <lines1
+ fi
+ mark '1.4' ; $SED -n 's/^/e1_/p' <lines1
+ echo Second type
+ mark '1.4.1'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed fails this
+ fi
+ $SED -e '' <lines1
+ echo 's/^/s1_/p' >script1
+ echo 's/^/s2_/p' >script2
+ mark '1.5'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -f script1 lines1
+ fi
+ mark '1.6'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -f script1 <lines1
+ fi
+ mark '1.7'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -e 's/^/e1_/p' lines1
+ fi
+ mark '1.8'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -e 's/^/e1_/p' <lines1
+ fi
+ mark '1.9' ; $SED -n -f script1 lines1
+ mark '1.10' ; $SED -n -f script1 <lines1
+ mark '1.11' ; $SED -n -e 's/^/e1_/p' lines1
+ mark '1.12'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -n -e 's/^/e1_/p' <lines1
+ fi
+ mark '1.13'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -e 's/^/e1_/p' -e 's/^/e2_/p' lines1
+ fi
+ mark '1.14'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -f script1 -f script2 lines1
+ fi
+ mark '1.15'
+ if [ $GNU -eq 1 -o $SUN -eq 1 ] ; then
+ echo GNU and SunOS sed fail this following older POSIX draft
+ else
+ $SED -e 's/^/e1_/p' -f script1 lines1
+ fi
+ mark '1.16'
+ if [ $SUN -eq 1 ] ; then
+ echo SunOS sed prints only with -n
+ else
+ $SED -e 's/^/e1_/p' lines1 lines1
+ fi
+ # POSIX D11.2:11251
+ mark '1.17' ; $SED p <lines1 lines1
+cat >script1 <<EOF
+#n
+# A comment
+
+p
+EOF
+ mark '1.18' ; $SED -f script1 <lines1 lines1
+}
+
+test_addr()
+{
+ echo Testing address ranges
+ mark '2.1' ; $SED -n -e '4p' lines1
+ mark '2.2' ; $SED -n -e '20p' lines1 lines2
+ mark '2.3' ; $SED -n -e '$p' lines1
+ mark '2.4' ; $SED -n -e '$p' lines1 lines2
+ mark '2.5' ; $SED -n -e '$a\
+hello' /dev/null
+ mark '2.6' ; $SED -n -e '$p' lines1 /dev/null lines2
+ # Should not print anything
+ mark '2.7' ; $SED -n -e '20p' lines1
+ mark '2.8' ; $SED -n -e '0p' lines1
+ mark '2.9' ; $SED -n '/l1_7/p' lines1
+ mark '2.10' ; $SED -n ' /l1_7/ p' lines1
+ mark '2.11'
+ if [ $BSD -eq 1 ] ; then
+ echo BSD sed fails this test
+ fi
+ if [ $GNU -eq 1 ] ; then
+ echo GNU sed fails this
+ fi
+ $SED -n '\_l1\_7_p' lines1
+ mark '2.12' ; $SED -n '1,4p' lines1
+ mark '2.13' ; $SED -n '1,$p' lines1 lines2
+ mark '2.14' ; $SED -n '1,/l2_9/p' lines1 lines2
+ mark '2.15' ; $SED -n '/4/,$p' lines1 lines2
+ mark '2.16' ; $SED -n '/4/,20p' lines1 lines2
+ mark '2.17' ; $SED -n '/4/,/10/p' lines1 lines2
+ mark '2.18' ; $SED -n '/l2_3/,/l1_8/p' lines1 lines2
+ mark '2.19'
+ if [ $GNU -eq 1 ] ; then
+ echo GNU sed fails this
+ fi
+ $SED -n '12,3p' lines1 lines2
+ mark '2.20'
+ if [ $GNU -eq 1 ] ; then
+ echo GNU sed fails this
+ fi
+ $SED -n '/l1_7/,3p' lines1 lines2
+}
+
+test_group()
+{
+ echo Brace and other grouping
+ mark '3.1' ; $SED -e '
+4,12 {
+ s/^/^/
+ s/$/$/
+ s/_/T/
+}' lines1
+ mark '3.2' ; $SED -e '
+4,12 {
+ s/^/^/
+ /6/,/10/ {
+ s/$/$/
+ /8/ s/_/T/
+ }
+}' lines1
+ mark '3.3' ; $SED -e '
+4,12 !{
+ s/^/^/
+ /6/,/10/ !{
+ s/$/$/
+ /8/ !s/_/T/
+ }
+}' lines1
+ mark '3.4' ; $SED -e '4,12!s/^/^/' lines1
+}
+
+test_acid()
+{
+ echo Testing a c d and i commands
+ mark '4.1' ; $SED -n -e '
+s/^/before_i/p
+20i\
+inserted
+s/^/after_i/p
+' lines1 lines2
+ mark '4.2' ; $SED -n -e '
+5,12s/^/5-12/
+s/^/before_a/p
+/5-12/a\
+appended
+s/^/after_a/p
+' lines1 lines2
+ mark '4.3'
+ if [ $GNU -eq 1 ] ; then
+ echo GNU sed fails this
+ fi
+ $SED -n -e '
+s/^/^/p
+/l1_/a\
+appended
+8,10N
+s/$/$/p
+' lines1 lines2
+ mark '4.4' ; $SED -n -e '
+c\
+hello
+' lines1
+ mark '4.5' ; $SED -n -e '
+8c\
+hello
+' lines1
+ mark '4.6' ; $SED -n -e '
+3,14c\
+hello
+' lines1
+# SunOS and GNU sed behave differently. We follow POSIX
+# mark '4.7' ; $SED -n -e '
+#8,3c\
+#hello
+#' lines1
+ mark '4.8' ; $SED d <lines1
+}
+
+test_branch()
+{
+ echo Testing labels and branching
+ mark '5.1' ; $SED -n -e '
+b label4
+:label3
+s/^/label3_/p
+b end
+:label4
+2,12b label1
+b label2
+:label1
+s/^/label1_/p
+b
+:label2
+s/^/label2_/p
+b label3
+:end
+' lines1
+ mark '5.2'
+ if [ $BSD -eq 1 ] ; then
+ echo BSD sed fails this test
+ fi
+ $SED -n -e '
+s/l1_/l2_/
+t ok
+b
+:ok
+s/^/tested /p
+' lines1 lines2
+# SunOS sed behaves differently here. Clarification needed.
+# mark '5.3' ; $SED -n -e '
+#5,8b inside
+#1,5 {
+# s/^/^/p
+# :inside
+# s/$/$/p
+#}
+#' lines1
+# Check that t clears the substitution done flag
+ mark '5.4' ; $SED -n -e '
+1,8s/^/^/
+t l1
+:l1
+t l2
+s/$/$/p
+b
+:l2
+s/^/ERROR/
+' lines1
+# Check that reading a line clears the substitution done flag
+ mark '5.5'
+ if [ $BSD -eq 1 ] ; then
+ echo BSD sed fails this test
+ fi
+ $SED -n -e '
+t l2
+1,8s/^/^/p
+2,7N
+b
+:l2
+s/^/ERROR/p
+' lines1
+ mark '5.6' ; $SED 5q lines1
+ mark '5.7' ; $SED -e '
+5i\
+hello
+5q' lines1
+# Branch across block boundary
+ mark '5.8' ; $SED -e '
+{
+:b
+}
+s/l/m/
+tb' lines1
+}
+
+test_pattern()
+{
+echo Pattern space commands
+# Check that the pattern space is deleted
+ mark '6.1' ; $SED -n -e '
+c\
+changed
+p
+' lines1
+ mark '6.2' ; $SED -n -e '
+4d
+p
+' lines1
+# SunOS sed refused to print here
+# mark '6.3' ; $SED -e '
+#N
+#N
+#N
+#D
+#P
+#4p
+#' lines1
+ mark '6.4' ; $SED -e '
+2h
+3H
+4g
+5G
+6x
+6p
+6x
+6p
+' lines1
+ mark '6.5' ; $SED -e '4n' lines1
+ mark '6.6' ; $SED -n -e '4n' lines1
+}
+
+test_print()
+{
+ echo Testing print and file routines
+ awk 'END {for (i = 1; i < 256; i++) printf("%c", i);print "\n"}' \
+ </dev/null >lines3
+ # GNU and SunOS sed behave differently here
+ mark '7.1'
+ if [ $BSD -eq 1 ] ; then
+ echo 'BSD sed drops core on this one; TEST SKIPPED'
+ else
+ $SED -n l lines3
+ fi
+ mark '7.2' ; $SED -e '/l2_/=' lines1 lines2
+ rm -f lines4
+ mark '7.3' ; $SED -e '3,12w lines4' lines1
+ echo w results
+ cat lines4
+ mark '7.4' ; $SED -e '4r lines2' lines1
+ mark '7.5' ; $SED -e '5r /dev/dds' lines1
+ mark '7.6' ; $SED -e '6r /dev/null' lines1
+ mark '7.7'
+ if [ $BSD -eq 1 -o $GNU -eq 1 -o $SUN -eq 1 ] ; then
+ echo BSD, GNU and SunOS cannot pass this one
+ else
+ sed '200q' $DICT | sed 's$.*$s/^/&/w tmpdir/&$' >script1
+ rm -rf tmpdir
+ mkdir tmpdir
+ $SED -f script1 lines1
+ cat tmpdir/*
+ rm -rf tmpdir
+ fi
+ mark '7.8'
+ if [ $BSD -eq 1 ] ; then
+ echo BSD sed cannot pass 7.7
+ else
+ echo line1 > lines3
+ echo "" >> lines3
+ $SED -n -e '$p' lines3 /dev/null
+ fi
+
+}
+
+test_subst()
+{
+ echo Testing substitution commands
+ mark '8.1' ; $SED -e 's/./X/g' lines1
+ mark '8.2' ; $SED -e 's,.,X,g' lines1
+# GNU and SunOS sed thinks we are escaping . as wildcard, not as separator
+# mark '8.3' ; $SED -e 's.\..X.g' lines1
+# POSIX does not say that this should work
+# mark '8.4' ; $SED -e 's/[/]/Q/' lines1
+ mark '8.4' ; $SED -e 's/[\/]/Q/' lines1
+ mark '8.5' ; $SED -e 's_\__X_' lines1
+ mark '8.6' ; $SED -e 's/./(&)/g' lines1
+ mark '8.7' ; $SED -e 's/./(\&)/g' lines1
+ mark '8.8' ; $SED -e 's/\(.\)\(.\)\(.\)/x\3x\2x\1/g' lines1
+ mark '8.9' ; $SED -e 's/_/u0\
+u1\
+u2/g' lines1
+ mark '8.10'
+ if [ $BSD -eq 1 -o $GNU -eq 1 ] ; then
+ echo 'BSD/GNU sed do not understand digit flags on s commands'
+ fi
+ $SED -e 's/./X/4' lines1
+ rm -f lines4
+ mark '8.11' ; $SED -e 's/1/X/w lines4' lines1
+ echo s wfile results
+ cat lines4
+ mark '8.12' ; $SED -e 's/[123]/X/g' lines1
+ mark '8.13' ; $SED -e 'y/0123456789/9876543210/' lines1
+ mark '8.14' ;
+ if [ $BSD -eq 1 -o $GNU -eq 1 -o $SUN -eq 1 ] ; then
+ echo BSD/GNU/SUN sed fail this test
+ else
+ $SED -e 'y10\123456789198765432\101' lines1
+ fi
+ mark '8.15' ; $SED -e '1N;2y/\n/X/' lines1
+ mark '8.16'
+ if [ $BSD -eq 1 ] ; then
+ echo 'BSD sed does not handle branch defined REs'
+ else
+ echo 'eeefff' | $SED -e 'p' -e 's/e/X/p' -e ':x' \
+ -e 's//Y/p' -e '/f/bx'
+ fi
+}
+
+test_error()
+{
+ exec 3<&0 4>&1 5>&2
+ exec 0</dev/null
+ exec 2>&1
+ set -x
+ $TEST -x && exit 1
+ $TEST -f && exit 1
+ $TEST -e && exit 1
+ $TEST -f /dev/dds && exit 1
+ $TEST p /dev/dds && exit 1
+ $TEST -f /bin/sh && exit 1
+ $TEST '{' && exit 1
+ $TEST '{' && exit 1
+ $TEST '/hello/' && exit 1
+ $TEST '1,/hello/' && exit 1
+ $TEST -e '-5p' && exit 1
+ $TEST '/jj' && exit 1
+ $TEST 'a hello' && exit 1
+ $TEST 'a \ hello' && exit 1
+ $TEST 'b foo' && exit 1
+ $TEST 'd hello' && exit 1
+ $TEST 's/aa' && exit 1
+ $TEST 's/aa/' && exit 1
+ $TEST 's/a/b' && exit 1
+ $TEST 's/a/b/c/d' && exit 1
+ $TEST 's/a/b/ 1 2' && exit 1
+ $TEST 's/a/b/ 1 g' && exit 1
+ $TEST 's/a/b/w' && exit 1
+ $TEST 'y/aa' && exit 1
+ $TEST 'y/aa/b/' && exit 1
+ $TEST 'y/aa/' && exit 1
+ $TEST 'y/a/b' && exit 1
+ $TEST 'y/a/b/c/d' && exit 1
+ $TEST '!' && exit 1
+ $TEST supercalifrangolisticexprialidociussupercalifrangolisticexcius
+ set +x
+ exec 0<&3 1>&4 2>&5
+}
+
+main
diff --git a/usr.bin/sed/compile.c b/usr.bin/sed/compile.c
new file mode 100644
index 0000000..300a77f
--- /dev/null
+++ b/usr.bin/sed/compile.c
@@ -0,0 +1,946 @@
+/* $NetBSD: compile.c,v 1.47 2016/04/05 00:13:03 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: compile.c,v 1.47 2016/04/05 00:13:03 christos Exp $");
+#ifdef __FBSDID
+__FBSDID("$FreeBSD: head/usr.bin/sed/compile.c 259132 2013-12-09 18:57:20Z eadler $");
+#endif
+
+#if 0
+static const char sccsid[] = "@(#)compile.c 8.1 (Berkeley) 6/6/93";
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include "defs.h"
+#include "extern.h"
+
+#define LHSZ 128
+#define LHMASK (LHSZ - 1)
+static struct labhash {
+ struct labhash *lh_next;
+ u_int lh_hash;
+ struct s_command *lh_cmd;
+ int lh_ref;
+} *labels[LHSZ];
+
+static char *compile_addr(char *, struct s_addr *);
+static char *compile_ccl(char **, char *);
+static char *compile_delimited(char *, char *, int);
+static char *compile_flags(char *, struct s_subst *);
+static regex_t *compile_re(char *, int);
+static char *compile_subst(char *, struct s_subst *);
+static char *compile_text(void);
+static char *compile_tr(char *, struct s_tr **);
+static struct s_command
+ **compile_stream(struct s_command **);
+static char *duptoeol(char *, const char *);
+static void enterlabel(struct s_command *);
+static struct s_command
+ *findlabel(char *);
+static void fixuplabel(struct s_command *, struct s_command *);
+static void uselabel(void);
+
+/*
+ * Command specification. This is used to drive the command parser.
+ */
+struct s_format {
+ char code; /* Command code */
+ int naddr; /* Number of address args */
+ enum e_args args; /* Argument type */
+};
+
+static struct s_format cmd_fmts[] = {
+ {'{', 2, GROUP},
+ {'}', 0, ENDGROUP},
+ {'a', 1, TEXT},
+ {'b', 2, BRANCH},
+ {'c', 2, TEXT},
+ {'d', 2, EMPTY},
+ {'D', 2, EMPTY},
+ {'g', 2, EMPTY},
+ {'G', 2, EMPTY},
+ {'h', 2, EMPTY},
+ {'H', 2, EMPTY},
+ {'i', 1, TEXT},
+ {'l', 2, EMPTY},
+ {'n', 2, EMPTY},
+ {'N', 2, EMPTY},
+ {'p', 2, EMPTY},
+ {'P', 2, EMPTY},
+ {'q', 1, EMPTY},
+ {'r', 1, RFILE},
+ {'s', 2, SUBST},
+ {'t', 2, BRANCH},
+ {'w', 2, WFILE},
+ {'x', 2, EMPTY},
+ {'y', 2, TR},
+ {'!', 2, NONSEL},
+ {':', 0, LABEL},
+ {'#', 0, COMMENT},
+ {'=', 1, EMPTY},
+ {'\0', 0, COMMENT},
+};
+
+/* The compiled program. */
+struct s_command *prog;
+
+/*
+ * Compile the program into prog.
+ * Initialise appends.
+ */
+void
+compile(void)
+{
+ *compile_stream(&prog) = NULL;
+ fixuplabel(prog, NULL);
+ uselabel();
+ if (appendnum > 0)
+ appends = xmalloc(sizeof(struct s_appends) * appendnum);
+ match = xmalloc((maxnsub + 1) * sizeof(regmatch_t));
+}
+
+#define EATSPACE() do { \
+ if (p) \
+ while (*p && isspace((unsigned char)*p)) \
+ p++; \
+ } while (0)
+
+static struct s_command **
+compile_stream(struct s_command **link)
+{
+ char *p;
+ static char lbuf[_POSIX2_LINE_MAX + 1]; /* To save stack */
+ struct s_command *cmd, *cmd2, *stack;
+ struct s_format *fp;
+ char re[_POSIX2_LINE_MAX + 1];
+ int naddr; /* Number of addresses */
+
+ stack = 0;
+ for (;;) {
+ if ((p = cu_fgets(lbuf, sizeof(lbuf), NULL)) == NULL) {
+ if (stack != 0)
+ errx(1, "%lu: %s: unexpected EOF (pending }'s)",
+ linenum, fname);
+ return (link);
+ }
+
+semicolon: EATSPACE();
+ if (p) {
+ if (*p == '#' || *p == '\0')
+ continue;
+ else if (*p == ';') {
+ p++;
+ goto semicolon;
+ }
+ }
+ *link = cmd = xmalloc(sizeof(struct s_command));
+ link = &cmd->next;
+ cmd->startline = cmd->nonsel = 0;
+ /* First parse the addresses */
+ naddr = 0;
+
+/* Valid characters to start an address */
+#define addrchar(c) (strchr("0123456789/\\$", (c)))
+ if (addrchar(*p)) {
+ naddr++;
+ cmd->a1 = xmalloc(sizeof(struct s_addr));
+ p = compile_addr(p, cmd->a1);
+ EATSPACE(); /* EXTENSION */
+ if (*p == ',') {
+ p++;
+ EATSPACE(); /* EXTENSION */
+ naddr++;
+ cmd->a2 = xmalloc(sizeof(struct s_addr));
+ p = compile_addr(p, cmd->a2);
+ EATSPACE();
+ } else
+ cmd->a2 = 0;
+ } else
+ cmd->a1 = cmd->a2 = 0;
+
+nonsel: /* Now parse the command */
+ if (!*p)
+ errx(1, "%lu: %s: command expected", linenum, fname);
+ cmd->code = *p;
+ for (fp = cmd_fmts; fp->code; fp++)
+ if (fp->code == *p)
+ break;
+ if (!fp->code)
+ errx(1, "%lu: %s: invalid command code %c", linenum, fname, *p);
+ if (naddr > fp->naddr)
+ errx(1,
+ "%lu: %s: command %c expects up to %d address(es), found %d",
+ linenum, fname, *p, fp->naddr, naddr);
+ switch (fp->args) {
+ case NONSEL: /* ! */
+ p++;
+ EATSPACE();
+ cmd->nonsel = ! cmd->nonsel;
+ goto nonsel;
+ case GROUP: /* { */
+ p++;
+ EATSPACE();
+ cmd->next = stack;
+ stack = cmd;
+ link = &cmd->u.c;
+ if (*p)
+ goto semicolon;
+ break;
+ case ENDGROUP:
+ /*
+ * Short-circuit command processing, since end of
+ * group is really just a noop.
+ */
+ cmd->nonsel = 1;
+ if (stack == 0)
+ errx(1, "%lu: %s: unexpected }", linenum, fname);
+ cmd2 = stack;
+ stack = cmd2->next;
+ cmd2->next = cmd;
+ /*FALLTHROUGH*/
+ case EMPTY: /* d D g G h H l n N p P q x = \0 */
+ p++;
+ EATSPACE();
+ switch (*p) {
+ case ';':
+ p++;
+ link = &cmd->next;
+ goto semicolon;
+ case '}':
+ goto semicolon;
+ case '\0':
+ break;
+ default:
+ errx(1, "%lu: %s: extra characters at the end of %c command",
+ linenum, fname, cmd->code);
+ }
+ break;
+ case TEXT: /* a c i */
+ p++;
+ EATSPACE();
+ if (*p != '\\')
+ errx(1,
+"%lu: %s: command %c expects \\ followed by text", linenum, fname, cmd->code);
+ p++;
+ EATSPACE();
+ if (*p)
+ errx(1,
+ "%lu: %s: extra characters after \\ at the end of %c command",
+ linenum, fname, cmd->code);
+ cmd->t = compile_text();
+ break;
+ case COMMENT: /* \0 # */
+ break;
+ case WFILE: /* w */
+ p++;
+ EATSPACE();
+ if (*p == '\0')
+ errx(1, "%lu: %s: filename expected", linenum, fname);
+ cmd->t = duptoeol(p, "w command");
+ if (aflag)
+ cmd->u.fd = -1;
+ else if ((cmd->u.fd = open(p,
+ O_WRONLY|O_APPEND|O_CREAT|O_TRUNC,
+ DEFFILEMODE)) == -1)
+ err(1, "%s", p);
+ break;
+ case RFILE: /* r */
+ p++;
+ EATSPACE();
+ if (*p == '\0')
+ errx(1, "%lu: %s: filename expected", linenum, fname);
+ else
+ cmd->t = duptoeol(p, "read command");
+ break;
+ case BRANCH: /* b t */
+ p++;
+ EATSPACE();
+ if (*p == '\0')
+ cmd->t = NULL;
+ else
+ cmd->t = duptoeol(p, "branch");
+ break;
+ case LABEL: /* : */
+ p++;
+ EATSPACE();
+ cmd->t = duptoeol(p, "label");
+ if (strlen(p) == 0)
+ errx(1, "%lu: %s: empty label", linenum, fname);
+ enterlabel(cmd);
+ break;
+ case SUBST: /* s */
+ p++;
+ if (*p == '\0' || *p == '\\')
+ errx(1,
+"%lu: %s: substitute pattern can not be delimited by newline or backslash",
+ linenum, fname);
+ cmd->u.s = xcalloc(1, sizeof(struct s_subst));
+ p = compile_delimited(p, re, 0);
+ if (p == NULL)
+ errx(1,
+ "%lu: %s: unterminated substitute pattern", linenum, fname);
+
+ /* Compile RE with no case sensitivity temporarily */
+ if (*re == '\0')
+ cmd->u.s->re = NULL;
+ else
+ cmd->u.s->re = compile_re(re, 0);
+ --p;
+ p = compile_subst(p, cmd->u.s);
+ p = compile_flags(p, cmd->u.s);
+
+ /* Recompile RE with case sensitivity from "I" flag if any */
+ if (*re == '\0')
+ cmd->u.s->re = NULL;
+ else
+ cmd->u.s->re = compile_re(re, cmd->u.s->icase);
+ EATSPACE();
+ if (*p == ';') {
+ p++;
+ link = &cmd->next;
+ goto semicolon;
+ }
+ break;
+ case TR: /* y */
+ p++;
+ p = compile_tr(p, &cmd->u.y);
+ EATSPACE();
+ switch (*p) {
+ case ';':
+ p++;
+ link = &cmd->next;
+ goto semicolon;
+ case '}':
+ goto semicolon;
+ case '\0':
+ break;
+ default:
+ errx(1,
+"%lu: %s: extra text at the end of a transform command", linenum, fname);
+ }
+ if (*p)
+ break;
+ }
+ }
+}
+
+/*
+ * Get a delimited string. P points to the delimeter of the string; d points
+ * to a buffer area. Newline and delimiter escapes are processed; other
+ * escapes are ignored.
+ *
+ * Returns a pointer to the first character after the final delimiter or NULL
+ * in the case of a non-terminated string. The character array d is filled
+ * with the processed string.
+ */
+static char *
+compile_delimited(char *p, char *d, int is_tr)
+{
+ char c;
+
+ c = *p++;
+ if (c == '\0')
+ return (NULL);
+ else if (c == '\\')
+ errx(1, "%lu: %s: \\ can not be used as a string delimiter",
+ linenum, fname);
+ else if (c == '\n')
+ errx(1, "%lu: %s: newline can not be used as a string delimiter",
+ linenum, fname);
+ while (*p) {
+ if (*p == '[' && *p != c) {
+ if ((d = compile_ccl(&p, d)) == NULL)
+ errx(1, "%lu: %s: unbalanced brackets ([])", linenum, fname);
+ continue;
+ } else if (*p == '\\' && p[1] == '[') {
+ *d++ = *p++;
+ } else if (*p == '\\' && p[1] == c)
+ p++;
+ else if (*p == '\\' && p[1] == 'n') {
+ *d++ = '\n';
+ p += 2;
+ continue;
+ } else if (*p == '\\' && p[1] == '\\') {
+ if (is_tr)
+ p++;
+ else
+ *d++ = *p++;
+ } else if (*p == c) {
+ *d = '\0';
+ return (p + 1);
+ }
+ *d++ = *p++;
+ }
+ return (NULL);
+}
+
+
+/* compile_ccl: expand a POSIX character class */
+static char *
+compile_ccl(char **sp, char *t)
+{
+ int c, d;
+ char *s = *sp;
+
+ *t++ = *s++;
+ if (*s == '^')
+ *t++ = *s++;
+ if (*s == ']')
+ *t++ = *s++;
+ for (; *s && (*t = *s) != ']'; s++, t++)
+ if (*s == '[' && ((d = *(s+1)) == '.' || d == ':' || d == '=')) {
+ *++t = *++s, t++, s++;
+ for (c = *s; (*t = *s) != ']' || c != d; s++, t++)
+ if ((c = *s) == '\0')
+ return NULL;
+ }
+ return (*s == ']') ? *sp = ++s, ++t : NULL;
+}
+
+/*
+ * Compiles the regular expression in RE and returns a pointer to the compiled
+ * regular expression.
+ * Cflags are passed to regcomp.
+ */
+static regex_t *
+compile_re(char *re, int case_insensitive)
+{
+ regex_t *rep;
+ int eval, flags;
+
+
+ flags = rflags;
+ if (case_insensitive)
+ flags |= REG_ICASE;
+ rep = xmalloc(sizeof(regex_t));
+ if ((eval = regcomp(rep, re, flags)) != 0)
+ errx(1, "%lu: %s: RE error: %s",
+ linenum, fname, strregerror(eval, rep));
+ if (maxnsub < rep->re_nsub)
+ maxnsub = rep->re_nsub;
+ return (rep);
+}
+
+/*
+ * Compile the substitution string of a regular expression and set res to
+ * point to a saved copy of it. Nsub is the number of parenthesized regular
+ * expressions.
+ */
+static char *
+compile_subst(char *p, struct s_subst *s)
+{
+ static char lbuf[_POSIX2_LINE_MAX + 1];
+ size_t asize, size;
+ u_char ref;
+ char c, *text, *op, *sp;
+ int more = 1, sawesc = 0;
+
+ c = *p++; /* Terminator character */
+ if (c == '\0')
+ return (NULL);
+
+ s->maxbref = 0;
+ s->linenum = linenum;
+ asize = 2 * _POSIX2_LINE_MAX + 1;
+ text = xmalloc(asize);
+ size = 0;
+ do {
+ op = sp = text + size;
+ for (; *p; p++) {
+ if (*p == '\\' || sawesc) {
+ /*
+ * If this is a continuation from the last
+ * buffer, we won't have a character to
+ * skip over.
+ */
+ if (sawesc)
+ sawesc = 0;
+ else
+ p++;
+
+ if (*p == '\0') {
+ /*
+ * This escaped character is continued
+ * in the next part of the line. Note
+ * this fact, then cause the loop to
+ * exit w/ normal EOL case and reenter
+ * above with the new buffer.
+ */
+ sawesc = 1;
+ p--;
+ continue;
+ } else if (strchr("123456789", *p) != NULL) {
+ *sp++ = '\\';
+ ref = (u_char)(*p - '0');
+ if (s->re != NULL &&
+ ref > s->re->re_nsub)
+ errx(1, "%lu: %s: \\%c not defined in the RE",
+ linenum, fname, *p);
+ if (s->maxbref < ref)
+ s->maxbref = ref;
+ } else if (*p == '&' || *p == '\\')
+ *sp++ = '\\';
+ } else if (*p == c) {
+ if (*++p == '\0' && more) {
+ if (cu_fgets(lbuf, sizeof(lbuf), &more))
+ p = lbuf;
+ }
+ *sp++ = '\0';
+ size += (size_t)(sp - op);
+ s->new = xrealloc(text, size);
+ return (p);
+ } else if (*p == '\n') {
+ errx(1,
+"%lu: %s: unescaped newline inside substitute pattern", linenum, fname);
+ /* NOTREACHED */
+ }
+ *sp++ = *p;
+ }
+ size += (size_t)(sp - op);
+ if (asize - size < _POSIX2_LINE_MAX + 1) {
+ asize *= 2;
+ text = xrealloc(text, asize);
+ }
+ } while (cu_fgets(p = lbuf, sizeof(lbuf), &more));
+ errx(1, "%lu: %s: unterminated substitute in regular expression",
+ linenum, fname);
+ /* NOTREACHED */
+}
+
+/*
+ * Compile the flags of the s command
+ */
+static char *
+compile_flags(char *p, struct s_subst *s)
+{
+ int gn; /* True if we have seen g or n */
+ unsigned long nval;
+ char wfile[_POSIX2_LINE_MAX + 1], *q;
+
+ s->n = 1; /* Default */
+ s->p = 0;
+ s->wfile = NULL;
+ s->wfd = -1;
+ s->icase = 0;
+ for (gn = 0;;) {
+ EATSPACE(); /* EXTENSION */
+ switch (*p) {
+ case 'g':
+ if (gn)
+ errx(1,
+"%lu: %s: more than one number or 'g' in substitute flags", linenum, fname);
+ gn = 1;
+ s->n = 0;
+ break;
+ case '\0':
+ case '\n':
+ case ';':
+ return (p);
+ case 'p':
+ s->p = 1;
+ break;
+ case 'i':
+ case 'I':
+ s->icase = 1;
+ break;
+ case '1': case '2': case '3':
+ case '4': case '5': case '6':
+ case '7': case '8': case '9':
+ if (gn)
+ errx(1,
+"%lu: %s: more than one number or 'g' in substitute flags", linenum, fname);
+ gn = 1;
+ errno = 0;
+ nval = strtoul(p, &p, 10);
+ if (errno == ERANGE || nval > INT_MAX)
+ errx(1,
+"%lu: %s: overflow in the 'N' substitute flag", linenum, fname);
+ s->n = (int)nval;
+ p--;
+ break;
+ case 'w':
+ p++;
+#ifdef HISTORIC_PRACTICE
+ if (*p != ' ') {
+ warnx("%lu: %s: space missing before w wfile", linenum, fname);
+ return (p);
+ }
+#endif
+ EATSPACE();
+ q = wfile;
+ while (*p) {
+ if (*p == '\n')
+ break;
+ *q++ = *p++;
+ }
+ *q = '\0';
+ if (q == wfile)
+ errx(1, "%lu: %s: no wfile specified", linenum, fname);
+ s->wfile = strdup(wfile);
+ if (!aflag && (s->wfd = open(wfile,
+ O_WRONLY|O_APPEND|O_CREAT|O_TRUNC,
+ DEFFILEMODE)) == -1)
+ err(1, "%s", wfile);
+ return (p);
+ default:
+ errx(1, "%lu: %s: bad flag in substitute command: '%c'",
+ linenum, fname, *p);
+ break;
+ }
+ p++;
+ }
+}
+
+/*
+ * Compile a translation set of strings into a lookup table.
+ */
+static char *
+compile_tr(char *p, struct s_tr **py)
+{
+ struct s_tr *y;
+ size_t i;
+ const char *op, *np;
+ char old[_POSIX2_LINE_MAX + 1];
+ char new[_POSIX2_LINE_MAX + 1];
+ size_t oclen, oldlen, nclen, newlen;
+ mbstate_t mbs1, mbs2;
+
+ *py = y = xmalloc(sizeof(*y));
+ y->multis = NULL;
+ y->nmultis = 0;
+
+ if (*p == '\0' || *p == '\\')
+ errx(1,
+ "%lu: %s: transform pattern can not be delimited by newline or backslash",
+ linenum, fname);
+ p = compile_delimited(p, old, 1);
+ if (p == NULL)
+ errx(1, "%lu: %s: unterminated transform source string",
+ linenum, fname);
+ p = compile_delimited(p - 1, new, 1);
+ if (p == NULL)
+ errx(1, "%lu: %s: unterminated transform target string",
+ linenum, fname);
+ EATSPACE();
+ op = old;
+ oldlen = mbsrtowcs(NULL, &op, 0, NULL);
+ if (oldlen == (size_t)-1)
+ err(1, NULL);
+ np = new;
+ newlen = mbsrtowcs(NULL, &np, 0, NULL);
+ if (newlen == (size_t)-1)
+ err(1, NULL);
+ if (newlen != oldlen)
+ errx(1, "%lu: %s: transform strings are not the same length",
+ linenum, fname);
+ if (MB_CUR_MAX == 1) {
+ /*
+ * The single-byte encoding case is easy: generate a
+ * lookup table.
+ */
+ for (i = 0; i <= UCHAR_MAX; i++)
+ y->bytetab[i] = (u_char)i;
+ for (; *op; op++, np++)
+ y->bytetab[(u_char)*op] = (u_char)*np;
+ } else {
+ /*
+ * Multi-byte encoding case: generate a lookup table as
+ * above, but only for single-byte characters. The first
+ * bytes of multi-byte characters have their lookup table
+ * entries set to 0, which causes do_tr() to search through
+ * an auxiliary vector of multi-byte mappings.
+ */
+ memset(&mbs1, 0, sizeof(mbs1));
+ memset(&mbs2, 0, sizeof(mbs2));
+ for (i = 0; i <= UCHAR_MAX; i++)
+ y->bytetab[i] = (u_char)((btowc((int)i) != WEOF) ? i : 0);
+ while (*op != '\0') {
+ oclen = mbrlen(op, MB_LEN_MAX, &mbs1);
+ if (oclen == (size_t)-1 || oclen == (size_t)-2)
+ errc(1, EILSEQ, NULL);
+ nclen = mbrlen(np, MB_LEN_MAX, &mbs2);
+ if (nclen == (size_t)-1 || nclen == (size_t)-2)
+ errc(1, EILSEQ, NULL);
+ if (oclen == 1 && nclen == 1)
+ y->bytetab[(u_char)*op] = (u_char)*np;
+ else {
+ y->bytetab[(u_char)*op] = 0;
+ y->multis = xrealloc(y->multis,
+ (y->nmultis + 1) * sizeof(*y->multis));
+ i = y->nmultis++;
+ y->multis[i].fromlen = oclen;
+ memcpy(y->multis[i].from, op, oclen);
+ y->multis[i].tolen = nclen;
+ memcpy(y->multis[i].to, np, nclen);
+ }
+ op += oclen;
+ np += nclen;
+ }
+ }
+ return (p);
+}
+
+/*
+ * Compile the text following an a or i command.
+ */
+static char *
+compile_text(void)
+{
+ size_t asize, size;
+ int esc_nl;
+ char *text, *p, *op, *s;
+ char lbuf[_POSIX2_LINE_MAX + 1];
+
+ asize = 2 * _POSIX2_LINE_MAX + 1;
+ text = xmalloc(asize);
+ size = 0;
+ while (cu_fgets(lbuf, sizeof(lbuf), NULL)) {
+ op = s = text + size;
+ p = lbuf;
+ for (esc_nl = 0; *p != '\0'; p++) {
+ if (*p == '\\' && p[1] != '\0' && *++p == '\n')
+ esc_nl = 1;
+ *s++ = *p;
+ }
+ size += (size_t)(s - op);
+ if (!esc_nl) {
+ *s = '\0';
+ break;
+ }
+ if (asize - size < _POSIX2_LINE_MAX + 1) {
+ asize *= 2;
+ text = xrealloc(text, asize);
+ }
+ }
+ text[size] = '\0';
+ p = xrealloc(text, size + 1);
+ return (p);
+}
+
+/*
+ * Get an address and return a pointer to the first character after
+ * it. Fill the structure pointed to according to the address.
+ */
+static char *
+compile_addr(char *p, struct s_addr *a)
+{
+ char *end, re[_POSIX2_LINE_MAX + 1];
+ int icase;
+
+ icase = 0;
+
+ a->type = 0;
+ switch (*p) {
+ case '\\': /* Context address */
+ ++p;
+ /* FALLTHROUGH */
+ case '/': /* Context address */
+ p = compile_delimited(p, re, 0);
+ if (p == NULL)
+ errx(1, "%lu: %s: unterminated regular expression", linenum, fname);
+ /* Check for case insensitive regexp flag */
+ if (*p == 'I') {
+ icase = 1;
+ p++;
+ }
+ if (*re == '\0')
+ a->u.r = NULL;
+ else
+ a->u.r = compile_re(re, icase);
+ a->type = AT_RE;
+ return (p);
+
+ case '$': /* Last line */
+ a->type = AT_LAST;
+ return (p + 1);
+
+ case '+': /* Relative line number */
+ a->type = AT_RELLINE;
+ p++;
+ /* FALLTHROUGH */
+ /* Line number */
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if (a->type == 0)
+ a->type = AT_LINE;
+ a->u.l = strtoul(p, &end, 10);
+ return (end);
+ default:
+ errx(1, "%lu: %s: expected context address", linenum, fname);
+ return (NULL);
+ }
+}
+
+/*
+ * duptoeol --
+ * Return a copy of all the characters up to \n or \0.
+ */
+static char *
+duptoeol(char *s, const char *ctype)
+{
+ size_t len;
+ int ws;
+ char *p, *start;
+
+ ws = 0;
+ for (start = s; *s != '\0' && *s != '\n'; ++s)
+ ws = isspace((unsigned char)*s);
+ *s = '\0';
+ if (ws)
+ warnx("%lu: %s: whitespace after %s", linenum, fname, ctype);
+ len = (size_t)(s - start + 1);
+ p = xmalloc(len);
+ return (memmove(p, start, len));
+}
+
+/*
+ * Convert goto label names to addresses, and count a and r commands, in
+ * the given subset of the script. Free the memory used by labels in b
+ * and t commands (but not by :).
+ *
+ * TODO: Remove } nodes
+ */
+static void
+fixuplabel(struct s_command *cp, struct s_command *end)
+{
+
+ for (; cp != end; cp = cp->next)
+ switch (cp->code) {
+ case 'a':
+ case 'r':
+ appendnum++;
+ break;
+ case 'b':
+ case 't':
+ /* Resolve branch target. */
+ if (cp->t == NULL) {
+ cp->u.c = NULL;
+ break;
+ }
+ if ((cp->u.c = findlabel(cp->t)) == NULL)
+ errx(1, "%lu: %s: undefined label '%s'", linenum, fname, cp->t);
+ free(cp->t);
+ break;
+ case '{':
+ /* Do interior commands. */
+ fixuplabel(cp->u.c, cp->next);
+ break;
+ }
+}
+
+/*
+ * Associate the given command label for later lookup.
+ */
+static void
+enterlabel(struct s_command *cp)
+{
+ struct labhash **lhp, *lh;
+ u_char *p;
+ u_int h, c;
+
+ for (h = 0, p = (u_char *)cp->t; (c = *p) != 0; p++)
+ h = (h << 5) + h + c;
+ lhp = &labels[h & LHMASK];
+ for (lh = *lhp; lh != NULL; lh = lh->lh_next)
+ if (lh->lh_hash == h && strcmp(cp->t, lh->lh_cmd->t) == 0)
+ errx(1, "%lu: %s: duplicate label '%s'", linenum, fname, cp->t);
+ lh = xmalloc(sizeof *lh);
+ lh->lh_next = *lhp;
+ lh->lh_hash = h;
+ lh->lh_cmd = cp;
+ lh->lh_ref = 0;
+ *lhp = lh;
+}
+
+/*
+ * Find the label contained in the command l in the command linked
+ * list cp. L is excluded from the search. Return NULL if not found.
+ */
+static struct s_command *
+findlabel(char *name)
+{
+ struct labhash *lh;
+ u_char *p;
+ u_int h, c;
+
+ for (h = 0, p = (u_char *)name; (c = *p) != 0; p++)
+ h = (h << 5) + h + c;
+ for (lh = labels[h & LHMASK]; lh != NULL; lh = lh->lh_next) {
+ if (lh->lh_hash == h && strcmp(name, lh->lh_cmd->t) == 0) {
+ lh->lh_ref = 1;
+ return (lh->lh_cmd);
+ }
+ }
+ return (NULL);
+}
+
+/*
+ * Warn about any unused labels. As a side effect, release the label hash
+ * table space.
+ */
+static void
+uselabel(void)
+{
+ struct labhash *lh, *next;
+ int i;
+
+ for (i = 0; i < LHSZ; i++) {
+ for (lh = labels[i]; lh != NULL; lh = next) {
+ next = lh->lh_next;
+ if (!lh->lh_ref)
+ warnx("%lu: %s: unused label '%s'",
+ linenum, fname, lh->lh_cmd->t);
+ free(lh);
+ }
+ }
+}
diff --git a/usr.bin/sed/defs.h b/usr.bin/sed/defs.h
new file mode 100644
index 0000000..b564115
--- /dev/null
+++ b/usr.bin/sed/defs.h
@@ -0,0 +1,150 @@
+/* $NetBSD: defs.h,v 1.12 2014/06/06 21:56:39 wiz Exp $ */
+
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)defs.h 8.1 (Berkeley) 6/6/93
+ * $FreeBSD: head/usr.bin/sed/defs.h 192732 2009-05-25 06:45:33Z brian $
+ */
+
+/*
+ * Types of address specifications
+ */
+enum e_atype {
+ AT_RE = 1, /* Line that match RE */
+ AT_LINE, /* Specific line */
+ AT_RELLINE, /* Relative line */
+ AT_LAST /* Last line */
+};
+
+/*
+ * Format of an address
+ */
+struct s_addr {
+ enum e_atype type; /* Address type */
+ union {
+ u_long l; /* Line number */
+ regex_t *r; /* Regular expression */
+ } u;
+};
+
+/*
+ * Substitution command
+ */
+struct s_subst {
+ int n; /* Occurrence to subst. */
+ int p; /* True if p flag */
+ int icase; /* True if I flag */
+ char *wfile; /* NULL if no wfile */
+ int wfd; /* Cached file descriptor */
+ regex_t *re; /* Regular expression */
+ unsigned int maxbref; /* Largest backreference. */
+ u_long linenum; /* Line number. */
+ char *new; /* Replacement text */
+};
+
+/*
+ * Translate command.
+ */
+struct s_tr {
+ unsigned char bytetab[256];
+ struct trmulti {
+ size_t fromlen;
+ char from[MB_LEN_MAX];
+ size_t tolen;
+ char to[MB_LEN_MAX];
+ } *multis;
+ size_t nmultis;
+};
+
+/*
+ * An internally compiled command.
+ * Initialy, label references are stored in t, on a second pass they
+ * are updated to pointers.
+ */
+struct s_command {
+ struct s_command *next; /* Pointer to next command */
+ struct s_addr *a1, *a2; /* Start and end address */
+ u_long startline; /* Start line number or zero */
+ char *t; /* Text for : a c i r w */
+ union {
+ struct s_command *c; /* Command(s) for b t { */
+ struct s_subst *s; /* Substitute command */
+ struct s_tr *y; /* Replace command array */
+ int fd; /* File descriptor for w */
+ } u;
+ char code; /* Command code */
+ u_int nonsel:1; /* True if ! */
+};
+
+/*
+ * Types of command arguments recognised by the parser
+ */
+enum e_args {
+ EMPTY, /* d D g G h H l n N p P q x = \0 */
+ TEXT, /* a c i */
+ NONSEL, /* ! */
+ GROUP, /* { */
+ ENDGROUP, /* } */
+ COMMENT, /* # */
+ BRANCH, /* b t */
+ LABEL, /* : */
+ RFILE, /* r */
+ WFILE, /* w */
+ SUBST, /* s */
+ TR /* y */
+};
+
+/*
+ * Structure containing things to append before a line is read
+ */
+struct s_appends {
+ enum {AP_STRING, AP_FILE} type;
+ char *s;
+ size_t len;
+};
+
+enum e_spflag {
+ APPEND, /* Append to the contents. */
+ REPLACE /* Replace the contents. */
+};
+
+/*
+ * Structure for a space (process, hold, otherwise).
+ */
+typedef struct {
+ char *space; /* Current space pointer. */
+ size_t len; /* Current length. */
+ int deleted; /* If deleted. */
+ char *back; /* Backing memory. */
+ size_t blen; /* Backing memory length. */
+} SPACE;
diff --git a/usr.bin/sed/extern.h b/usr.bin/sed/extern.h
new file mode 100644
index 0000000..0d28591
--- /dev/null
+++ b/usr.bin/sed/extern.h
@@ -0,0 +1,61 @@
+/* $NetBSD: extern.h,v 1.20 2015/03/12 12:40:41 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ * $FreeBSD: head/usr.bin/sed/extern.h 170608 2007-06-12 12:05:24Z yar $
+ */
+
+extern struct s_command *prog;
+extern struct s_appends *appends;
+extern regmatch_t *match;
+extern size_t maxnsub;
+extern u_long linenum;
+extern size_t appendnum;
+extern int aflag, eflag, nflag;
+extern const char *fname, *outfname;
+extern FILE *infile, *outfile;
+extern int rflags; /* regex flags to use */
+
+void cfclose(struct s_command *, struct s_command *);
+void compile(void);
+void cspace(SPACE *, const char *, size_t, enum e_spflag);
+char *cu_fgets(char *, int, int *);
+int mf_fgets(SPACE *, enum e_spflag);
+int lastline(void);
+void process(void);
+void resetstate(void);
+char *strregerror(int, regex_t *);
+void *xmalloc(size_t);
+void *xrealloc(void *, size_t);
+void *xcalloc(size_t, size_t);
diff --git a/usr.bin/sed/main.c b/usr.bin/sed/main.c
new file mode 100644
index 0000000..cfe9d35
--- /dev/null
+++ b/usr.bin/sed/main.c
@@ -0,0 +1,517 @@
+/* $NetBSD: main.c,v 1.34 2015/03/12 12:40:41 christos Exp $ */
+
+/*-
+ * Copyright (c) 2013 Johann 'Myrkraverk' Oskarsson.
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: main.c,v 1.34 2015/03/12 12:40:41 christos Exp $");
+#ifdef __FBSDID
+__FBSDID("$FreeBSD: head/usr.bin/sed/main.c 252231 2013-06-26 04:14:19Z pfg $");
+#endif
+
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1992, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif
+
+#if 0
+static const char sccsid[] = "@(#)main.c 8.2 (Berkeley) 1/3/94";
+#endif
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <locale.h>
+#include <regex.h>
+#include <stddef.h>
+#define _WITH_GETLINE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "defs.h"
+#include "extern.h"
+
+/*
+ * Linked list of units (strings and files) to be compiled
+ */
+struct s_compunit {
+ struct s_compunit *next;
+ enum e_cut {CU_FILE, CU_STRING} type;
+ char *s; /* Pointer to string or fname */
+};
+
+/*
+ * Linked list pointer to compilation units and pointer to current
+ * next pointer.
+ */
+static struct s_compunit *script, **cu_nextp = &script;
+
+/*
+ * Linked list of files to be processed
+ */
+struct s_flist {
+ char *fname;
+ struct s_flist *next;
+};
+
+/*
+ * Linked list pointer to files and pointer to current
+ * next pointer.
+ */
+static struct s_flist *files, **fl_nextp = &files;
+
+FILE *infile; /* Current input file */
+FILE *outfile; /* Current output file */
+
+int aflag, eflag, nflag;
+int rflags = 0;
+static int rval; /* Exit status */
+
+static int ispan; /* Whether inplace editing spans across files */
+
+/*
+ * Current file and line number; line numbers restart across compilation
+ * units, but span across input files. The latter is optional if editing
+ * in place.
+ */
+const char *fname; /* File name. */
+const char *outfname; /* Output file name */
+static char oldfname[PATH_MAX]; /* Old file name (for in-place editing) */
+static char tmpfname[PATH_MAX]; /* Temporary file name (for in-place editing) */
+static const char *inplace; /* Inplace edit file extension. */
+u_long linenum;
+
+static void add_compunit(enum e_cut, char *);
+static void add_file(char *);
+static void usage(void) __dead;
+
+int
+main(int argc, char *argv[])
+{
+ int c, fflag;
+ char *temp_arg;
+
+ setprogname(argv[0]);
+ (void) setlocale(LC_ALL, "");
+
+ fflag = 0;
+ inplace = NULL;
+
+ while ((c = getopt(argc, argv, "EI::ae:f:i::lnru")) != -1)
+ switch (c) {
+ case 'r': /* Gnu sed compat */
+ case 'E':
+ rflags = REG_EXTENDED;
+ break;
+ case 'I':
+ inplace = optarg ? optarg : __UNCONST("");
+ ispan = 1; /* span across input files */
+ break;
+ case 'a':
+ aflag = 1;
+ break;
+ case 'e':
+ eflag = 1;
+ temp_arg = xmalloc(strlen(optarg) + 2);
+ strcpy(temp_arg, optarg);
+ strcat(temp_arg, "\n");
+ add_compunit(CU_STRING, temp_arg);
+ break;
+ case 'f':
+ fflag = 1;
+ add_compunit(CU_FILE, optarg);
+ break;
+ case 'i':
+ inplace = optarg ? optarg : __UNCONST("");
+ ispan = 0; /* don't span across input files */
+ break;
+ case 'l':
+#ifdef _IOLBF
+ c = setvbuf(stdout, NULL, _IOLBF, 0);
+#else
+ c = setlinebuf(stdout);
+#endif
+ if (c)
+ warn("setting line buffered output failed");
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'u':
+#ifdef _IONBF
+ c = setvbuf(stdout, NULL, _IONBF, 0);
+#else
+ c = -1;
+ errno = EOPNOTSUPP;
+#endif
+ if (c)
+ warn("setting unbuffered output failed");
+ break;
+ default:
+ case '?':
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* First usage case; script is the first arg */
+ if (!eflag && !fflag && *argv) {
+ add_compunit(CU_STRING, *argv);
+ argv++;
+ }
+
+ compile();
+
+ /* Continue with first and start second usage */
+ if (*argv)
+ for (; *argv; argv++)
+ add_file(*argv);
+ else
+ add_file(NULL);
+ process();
+ cfclose(prog, NULL);
+ if (fclose(stdout))
+ err(1, "stdout");
+ exit(rval);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "Usage: %s [-aElnru] command [file ...]\n"
+ "\t%s [-aElnru] [-e command] [-f command_file] [-I[extension]]\n"
+ "\t [-i[extension]] [file ...]\n", getprogname(), getprogname());
+ exit(1);
+}
+
+/*
+ * Like fgets, but go through the chain of compilation units chaining them
+ * together. Empty strings and files are ignored.
+ */
+char *
+cu_fgets(char *buf, int n, int *more)
+{
+ static enum {ST_EOF, ST_FILE, ST_STRING} state = ST_EOF;
+ static FILE *f; /* Current open file */
+ static char *s; /* Current pointer inside string */
+ static char string_ident[30];
+ char *p;
+
+again:
+ switch (state) {
+ case ST_EOF:
+ if (script == NULL) {
+ if (more != NULL)
+ *more = 0;
+ return (NULL);
+ }
+ linenum = 0;
+ switch (script->type) {
+ case CU_FILE:
+ if ((f = fopen(script->s, "r")) == NULL)
+ err(1, "%s", script->s);
+ fname = script->s;
+ state = ST_FILE;
+ goto again;
+ case CU_STRING:
+ if (((size_t)snprintf(string_ident,
+ sizeof(string_ident), "\"%s\"", script->s)) >=
+ sizeof(string_ident) - 1)
+ (void)strcpy(string_ident +
+ sizeof(string_ident) - 6, " ...\"");
+ fname = string_ident;
+ s = script->s;
+ state = ST_STRING;
+ goto again;
+ }
+ case ST_FILE:
+ if ((p = fgets(buf, n, f)) != NULL) {
+ linenum++;
+ if (linenum == 1 && buf[0] == '#' && buf[1] == 'n')
+ nflag = 1;
+ if (more != NULL)
+ *more = !feof(f);
+ return (p);
+ }
+ script = script->next;
+ (void)fclose(f);
+ state = ST_EOF;
+ goto again;
+ case ST_STRING:
+ if (linenum == 0 && s[0] == '#' && s[1] == 'n')
+ nflag = 1;
+ p = buf;
+ for (;;) {
+ if (n-- <= 1) {
+ *p = '\0';
+ linenum++;
+ if (more != NULL)
+ *more = 1;
+ return (buf);
+ }
+ switch (*s) {
+ case '\0':
+ state = ST_EOF;
+ if (s == script->s) {
+ script = script->next;
+ goto again;
+ } else {
+ script = script->next;
+ *p = '\0';
+ linenum++;
+ if (more != NULL)
+ *more = 0;
+ return (buf);
+ }
+ case '\n':
+ *p++ = '\n';
+ *p = '\0';
+ s++;
+ linenum++;
+ if (more != NULL)
+ *more = 0;
+ return (buf);
+ default:
+ *p++ = *s++;
+ }
+ }
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
+
+/*
+ * Like fgets, but go through the list of files chaining them together.
+ * Set len to the length of the line.
+ */
+int
+mf_fgets(SPACE *sp, enum e_spflag spflag)
+{
+ struct stat sb;
+ size_t len;
+ static char *p = NULL;
+ static size_t plen = 0;
+ int c;
+ static int firstfile;
+
+ if (infile == NULL) {
+ /* stdin? */
+ if (files->fname == NULL) {
+ if (inplace != NULL)
+ errx(1, "-I or -i may not be used with stdin");
+ infile = stdin;
+ fname = "stdin";
+ outfile = stdout;
+ outfname = "stdout";
+ }
+ firstfile = 1;
+ }
+
+ for (;;) {
+ if (infile != NULL && (c = getc(infile)) != EOF) {
+ (void)ungetc(c, infile);
+ break;
+ }
+ /* If we are here then either eof or no files are open yet */
+ if (infile == stdin) {
+ sp->len = 0;
+ return (0);
+ }
+ if (infile != NULL) {
+ fclose(infile);
+ if (*oldfname != '\0') {
+ /* if there was a backup file, remove it */
+ unlink(oldfname);
+ /*
+ * Backup the original. Note that hard links
+ * are not supported on all filesystems.
+ */
+ if ((link(fname, oldfname) != 0) &&
+ (rename(fname, oldfname) != 0)) {
+ warn("rename()");
+ if (*tmpfname)
+ unlink(tmpfname);
+ exit(1);
+ }
+ *oldfname = '\0';
+ }
+ if (*tmpfname != '\0') {
+ if (outfile != NULL && outfile != stdout)
+ if (fclose(outfile) != 0) {
+ warn("fclose()");
+ unlink(tmpfname);
+ exit(1);
+ }
+ outfile = NULL;
+ if (rename(tmpfname, fname) != 0) {
+ /* this should not happen really! */
+ warn("rename()");
+ unlink(tmpfname);
+ exit(1);
+ }
+ *tmpfname = '\0';
+ }
+ outfname = NULL;
+ }
+ if (firstfile == 0)
+ files = files->next;
+ else
+ firstfile = 0;
+ if (files == NULL) {
+ sp->len = 0;
+ return (0);
+ }
+ fname = files->fname;
+ if (inplace != NULL) {
+ if (lstat(fname, &sb) != 0)
+ err(1, "%s", fname);
+ if (!(sb.st_mode & S_IFREG))
+ errx(1, "%s: %s %s", fname,
+ "in-place editing only",
+ "works for regular files");
+ if (*inplace != '\0') {
+ strlcpy(oldfname, fname,
+ sizeof(oldfname));
+ len = strlcat(oldfname, inplace,
+ sizeof(oldfname));
+ if (len > sizeof(oldfname))
+ errx(1, "%s: name too long", fname);
+ }
+ char d_name[PATH_MAX], f_name[PATH_MAX];
+ (void)strlcpy(d_name, fname, sizeof(d_name));
+ (void)strlcpy(f_name, fname, sizeof(f_name));
+ len = (size_t)snprintf(tmpfname, sizeof(tmpfname),
+ "%s/.!%ld!%s", dirname(d_name), (long)getpid(),
+ basename(f_name));
+ if (len >= sizeof(tmpfname))
+ errx(1, "%s: name too long", fname);
+ unlink(tmpfname);
+ if (outfile != NULL && outfile != stdout)
+ fclose(outfile);
+ if ((outfile = fopen(tmpfname, "w")) == NULL)
+ err(1, "%s", fname);
+ fchown(fileno(outfile), sb.st_uid, sb.st_gid);
+ fchmod(fileno(outfile), sb.st_mode & ALLPERMS);
+ outfname = tmpfname;
+ if (!ispan) {
+ linenum = 0;
+ resetstate();
+ }
+ } else {
+ outfile = stdout;
+ outfname = "stdout";
+ }
+ if ((infile = fopen(fname, "r")) == NULL) {
+ warn("%s", fname);
+ rval = 1;
+ continue;
+ }
+ }
+ /*
+ * We are here only when infile is open and we still have something
+ * to read from it.
+ *
+ * Use getline() so that we can handle essentially infinite input
+ * data. The p and plen are static so each invocation gives
+ * getline() the same buffer which is expanded as needed.
+ */
+ ssize_t slen = getline(&p, &plen, infile);
+ if (slen == -1)
+ err(1, "%s", fname);
+ if (slen != 0 && p[slen - 1] == '\n')
+ slen--;
+ cspace(sp, p, (size_t)slen, spflag);
+
+ linenum++;
+
+ return (1);
+}
+
+/*
+ * Add a compilation unit to the linked list
+ */
+static void
+add_compunit(enum e_cut type, char *s)
+{
+ struct s_compunit *cu;
+
+ cu = xmalloc(sizeof(struct s_compunit));
+ cu->type = type;
+ cu->s = s;
+ cu->next = NULL;
+ *cu_nextp = cu;
+ cu_nextp = &cu->next;
+}
+
+/*
+ * Add a file to the linked list
+ */
+static void
+add_file(char *s)
+{
+ struct s_flist *fp;
+
+ fp = xmalloc(sizeof(struct s_flist));
+ fp->next = NULL;
+ *fl_nextp = fp;
+ fp->fname = s;
+ fl_nextp = &fp->next;
+}
+
+int
+lastline(void)
+{
+ int ch;
+
+ if (files->next != NULL && (inplace == NULL || ispan))
+ return (0);
+ if ((ch = getc(infile)) == EOF)
+ return (1);
+ ungetc(ch, infile);
+ return (0);
+}
diff --git a/usr.bin/sed/misc.c b/usr.bin/sed/misc.c
new file mode 100644
index 0000000..bd0ecdf
--- /dev/null
+++ b/usr.bin/sed/misc.c
@@ -0,0 +1,119 @@
+/* $NetBSD: misc.c,v 1.15 2014/06/26 02:14:32 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: misc.c,v 1.15 2014/06/26 02:14:32 christos Exp $");
+#ifdef __FBSDID
+__FBSDID("$FreeBSD: head/usr.bin/sed/misc.c 200462 2009-12-13 03:14:06Z delphij $");
+#endif
+
+#if 0
+static const char sccsid[] = "@(#)misc.c 8.1 (Berkeley) 6/6/93";
+#endif
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "defs.h"
+#include "extern.h"
+
+/*
+ * malloc with result test
+ */
+void *
+xmalloc(size_t size)
+{
+ void *p;
+
+ if ((p = malloc(size)) == NULL)
+ err(1, "malloc(%zu)", size);
+ return p;
+}
+
+/*
+ * realloc with result test
+ */
+void *
+xrealloc(void *p, size_t size)
+{
+ if (p == NULL) /* Compatibility hack. */
+ return (xmalloc(size));
+
+ if ((p = realloc(p, size)) == NULL)
+ err(1, "realloc(%zu)", size);
+ return p;
+}
+
+/*
+ * realloc with result test
+ */
+void *
+xcalloc(size_t c, size_t n)
+{
+ void *p;
+
+ if ((p = calloc(c, n)) == NULL)
+ err(1, "calloc(%zu, %zu)", c, n);
+ return p;
+}
+/*
+ * Return a string for a regular expression error passed. This is overkill,
+ * because of the silly semantics of regerror (we can never know the size of
+ * the buffer).
+ */
+char *
+strregerror(int errcode, regex_t *preg)
+{
+ char buf[1];
+ static char *oe;
+ size_t s;
+
+ if (oe != NULL)
+ free(oe);
+ s = regerror(errcode, preg, buf, 0);
+ oe = xmalloc(s);
+ (void)regerror(errcode, preg, oe, s);
+ return (oe);
+}
diff --git a/usr.bin/sed/process.c b/usr.bin/sed/process.c
new file mode 100644
index 0000000..896df5a
--- /dev/null
+++ b/usr.bin/sed/process.c
@@ -0,0 +1,792 @@
+/* $NetBSD: process.c,v 1.52 2015/03/12 12:40:41 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: process.c,v 1.52 2015/03/12 12:40:41 christos Exp $");
+#ifdef __FBSDID
+__FBSDID("$FreeBSD: head/usr.bin/sed/process.c 192732 2009-05-25 06:45:33Z brian $");
+#endif
+
+#if 0
+static const char sccsid[] = "@(#)process.c 8.6 (Berkeley) 4/20/94";
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include "defs.h"
+#include "extern.h"
+
+static SPACE HS, PS, SS, YS;
+#define pd PS.deleted
+#define ps PS.space
+#define psl PS.len
+#define hs HS.space
+#define hsl HS.len
+
+static __inline int applies(struct s_command *);
+static void do_tr(struct s_tr *);
+static void flush_appends(void);
+static void lputs(char *, size_t);
+static __inline int regexec_e(regex_t *, const char *, int, int, size_t);
+static void regsub(SPACE *, char *, char *);
+static int substitute(struct s_command *);
+
+struct s_appends *appends; /* Array of pointers to strings to append. */
+static size_t appendx; /* Index into appends array. */
+size_t appendnum; /* Size of appends array. */
+
+static int lastaddr; /* Set by applies if last address of a range. */
+static int sdone; /* If any substitutes since last line input. */
+ /* Iov structure for 'w' commands. */
+static regex_t *defpreg;
+size_t maxnsub;
+regmatch_t *match;
+
+#define OUT() do {fwrite(ps, 1, psl, outfile); fputc('\n', outfile);} while (0)
+
+void
+process(void)
+{
+ struct s_command *cp;
+ SPACE tspace;
+ size_t oldpsl = 0;
+ char *p;
+
+ p = NULL;
+
+ for (linenum = 0; mf_fgets(&PS, REPLACE);) {
+ pd = 0;
+top:
+ cp = prog;
+redirect:
+ while (cp != NULL) {
+ if (!applies(cp)) {
+ cp = cp->next;
+ continue;
+ }
+ switch (cp->code) {
+ case '{':
+ cp = cp->u.c;
+ goto redirect;
+ case 'a':
+ if (appendx >= appendnum)
+ appends = xrealloc(appends,
+ sizeof(struct s_appends) *
+ (appendnum *= 2));
+ appends[appendx].type = AP_STRING;
+ appends[appendx].s = cp->t;
+ appends[appendx].len = strlen(cp->t);
+ appendx++;
+ break;
+ case 'b':
+ cp = cp->u.c;
+ goto redirect;
+ case 'c':
+ pd = 1;
+ psl = 0;
+ if (cp->a2 == NULL || lastaddr || lastline())
+ (void)fprintf(outfile, "%s", cp->t);
+ goto new;
+ case 'd':
+ pd = 1;
+ goto new;
+ case 'D':
+ if (pd)
+ goto new;
+ if (psl == 0 ||
+ (p = memchr(ps, '\n', psl - 1)) == NULL) {
+ pd = 1;
+ goto new;
+ } else {
+ psl -= (size_t)((p + 1) - ps);
+ memmove(ps, p + 1, psl);
+ goto top;
+ }
+ case 'g':
+ cspace(&PS, hs, hsl, REPLACE);
+ break;
+ case 'G':
+ cspace(&PS, "\n", 1, APPEND);
+ cspace(&PS, hs, hsl, APPEND);
+ break;
+ case 'h':
+ cspace(&HS, ps, psl, REPLACE);
+ break;
+ case 'H':
+ cspace(&HS, "\n", 1, APPEND);
+ cspace(&HS, ps, psl, APPEND);
+ break;
+ case 'i':
+ (void)fprintf(outfile, "%s", cp->t);
+ break;
+ case 'l':
+ lputs(ps, psl);
+ break;
+ case 'n':
+ if (!nflag && !pd)
+ OUT();
+ flush_appends();
+ if (!mf_fgets(&PS, REPLACE))
+ exit(0);
+ pd = 0;
+ break;
+ case 'N':
+ flush_appends();
+ cspace(&PS, "\n", 1, APPEND);
+ if (!mf_fgets(&PS, APPEND))
+ exit(0);
+ break;
+ case 'p':
+ if (pd)
+ break;
+ OUT();
+ break;
+ case 'P':
+ if (pd)
+ break;
+ if ((p = memchr(ps, '\n', psl - 1)) != NULL) {
+ oldpsl = psl;
+ psl = (size_t)(p - ps);
+ }
+ OUT();
+ if (p != NULL)
+ psl = oldpsl;
+ break;
+ case 'q':
+ if (!nflag && !pd)
+ OUT();
+ flush_appends();
+ exit(0);
+ case 'r':
+ if (appendx >= appendnum)
+ appends = xrealloc(appends,
+ sizeof(struct s_appends) *
+ (appendnum *= 2));
+ appends[appendx].type = AP_FILE;
+ appends[appendx].s = cp->t;
+ appends[appendx].len = strlen(cp->t);
+ appendx++;
+ break;
+ case 's':
+ sdone |= substitute(cp);
+ break;
+ case 't':
+ if (sdone) {
+ sdone = 0;
+ cp = cp->u.c;
+ goto redirect;
+ }
+ break;
+ case 'w':
+ if (pd)
+ break;
+ if (cp->u.fd == -1 && (cp->u.fd = open(cp->t,
+ O_WRONLY|O_APPEND|O_CREAT|O_TRUNC,
+ DEFFILEMODE)) == -1)
+ err(1, "%s", cp->t);
+ if (write(cp->u.fd, ps, psl) != (ssize_t)psl ||
+ write(cp->u.fd, "\n", 1) != 1)
+ err(1, "%s", cp->t);
+ break;
+ case 'x':
+ /*
+ * If the hold space is null, make it empty
+ * but not null. Otherwise the pattern space
+ * will become null after the swap, which is
+ * an abnormal condition.
+ */
+ if (hs == NULL)
+ cspace(&HS, "", 0, REPLACE);
+ tspace = PS;
+ PS = HS;
+ HS = tspace;
+ break;
+ case 'y':
+ if (pd || psl == 0)
+ break;
+ do_tr(cp->u.y);
+ break;
+ case ':':
+ case '}':
+ break;
+ case '=':
+ (void)fprintf(outfile, "%lu\n", linenum);
+ }
+ cp = cp->next;
+ } /* for all cp */
+
+new: if (!nflag && !pd)
+ OUT();
+ flush_appends();
+ } /* for all lines */
+}
+
+/*
+ * TRUE if the address passed matches the current program state
+ * (lastline, linenumber, ps).
+ */
+#define MATCH(a) \
+ ((a)->type == AT_RE ? regexec_e((a)->u.r, ps, 0, 1, psl) : \
+ (a)->type == AT_LINE ? linenum == (a)->u.l : lastline())
+
+/*
+ * Return TRUE if the command applies to the current line. Sets the start
+ * line for process ranges. Interprets the non-select (``!'') flag.
+ */
+static __inline int
+applies(struct s_command *cp)
+{
+ int r;
+
+ lastaddr = 0;
+ if (cp->a1 == NULL && cp->a2 == NULL)
+ r = 1;
+ else if (cp->a2)
+ if (cp->startline > 0) {
+ switch (cp->a2->type) {
+ case AT_RELLINE:
+ if (linenum - cp->startline <= cp->a2->u.l)
+ r = 1;
+ else {
+ cp->startline = 0;
+ r = 0;
+ }
+ break;
+ default:
+ if (MATCH(cp->a2)) {
+ cp->startline = 0;
+ lastaddr = 1;
+ r = 1;
+ } else if (cp->a2->type == AT_LINE &&
+ linenum > cp->a2->u.l) {
+ /*
+ * We missed the 2nd address due to a
+ * branch, so just close the range and
+ * return false.
+ */
+ cp->startline = 0;
+ r = 0;
+ } else
+ r = 1;
+ }
+ } else if (cp->a1 && MATCH(cp->a1)) {
+ /*
+ * If the second address is a number less than or
+ * equal to the line number first selected, only
+ * one line shall be selected.
+ * -- POSIX 1003.2
+ * Likewise if the relative second line address is zero.
+ */
+ if ((cp->a2->type == AT_LINE &&
+ linenum >= cp->a2->u.l) ||
+ (cp->a2->type == AT_RELLINE && cp->a2->u.l == 0))
+ lastaddr = 1;
+ else {
+ cp->startline = linenum;
+ }
+ r = 1;
+ } else
+ r = 0;
+ else
+ r = MATCH(cp->a1);
+ return (cp->nonsel ? ! r : r);
+}
+
+/*
+ * Reset the sed processor to its initial state.
+ */
+void
+resetstate(void)
+{
+ struct s_command *cp;
+
+ /*
+ * Reset all in-range markers.
+ */
+ for (cp = prog; cp; cp = cp->code == '{' ? cp->u.c : cp->next)
+ if (cp->a2)
+ cp->startline = 0;
+
+ /*
+ * Clear out the hold space.
+ */
+ cspace(&HS, "", 0, REPLACE);
+}
+
+/*
+ * substitute --
+ * Do substitutions in the pattern space. Currently, we build a
+ * copy of the new pattern space in the substitute space structure
+ * and then swap them.
+ */
+static int
+substitute(struct s_command *cp)
+{
+ SPACE tspace;
+ regex_t *re;
+ regoff_t re_off, slen;
+ int lastempty, n;
+ char *s;
+
+ s = ps;
+ re = cp->u.s->re;
+ if (re == NULL) {
+ if (defpreg != NULL && cp->u.s->maxbref > defpreg->re_nsub) {
+ linenum = cp->u.s->linenum;
+ errx(1, "%lu: %s: \\%u not defined in the RE",
+ linenum, fname, cp->u.s->maxbref);
+ }
+ }
+ if (!regexec_e(re, s, 0, 0, psl))
+ return (0);
+
+ SS.len = 0; /* Clean substitute space. */
+ slen = (regoff_t)psl;
+ n = cp->u.s->n;
+ lastempty = 1;
+
+ switch (n) {
+ case 0: /* Global */
+ do {
+ if (lastempty || match[0].rm_so != match[0].rm_eo) {
+ /* Locate start of replaced string. */
+ re_off = match[0].rm_so;
+ /* Copy leading retained string. */
+ cspace(&SS, s, (size_t)re_off, APPEND);
+ /* Add in regular expression. */
+ regsub(&SS, s, cp->u.s->new);
+ }
+
+ /* Move past this match. */
+ if (match[0].rm_so != match[0].rm_eo) {
+ s += match[0].rm_eo;
+ slen -= match[0].rm_eo;
+ lastempty = 0;
+ } else {
+ if (match[0].rm_so < slen)
+ cspace(&SS, s + match[0].rm_so, 1,
+ APPEND);
+ s += match[0].rm_so + 1;
+ slen -= match[0].rm_so + 1;
+ lastempty = 1;
+ }
+ } while (slen >= 0 && regexec_e(re, s, REG_NOTBOL, 0, (size_t)slen));
+ /* Copy trailing retained string. */
+ if (slen > 0)
+ cspace(&SS, s, (size_t)slen, APPEND);
+ break;
+ default: /* Nth occurrence */
+ while (--n) {
+ if (match[0].rm_eo == match[0].rm_so)
+ match[0].rm_eo = match[0].rm_so + 1;
+ s += match[0].rm_eo;
+ slen -= match[0].rm_eo;
+ if (slen < 0)
+ return (0);
+ if (!regexec_e(re, s, REG_NOTBOL, 0, (size_t)slen))
+ return (0);
+ }
+ /* FALLTHROUGH */
+ case 1: /* 1st occurrence */
+ /* Locate start of replaced string. */
+ re_off = match[0].rm_so + (s - ps);
+ /* Copy leading retained string. */
+ cspace(&SS, ps, (size_t)re_off, APPEND);
+ /* Add in regular expression. */
+ regsub(&SS, s, cp->u.s->new);
+ /* Copy trailing retained string. */
+ s += match[0].rm_eo;
+ slen -= match[0].rm_eo;
+ cspace(&SS, s, (size_t)slen, APPEND);
+ break;
+ }
+
+ /*
+ * Swap the substitute space and the pattern space, and make sure
+ * that any leftover pointers into stdio memory get lost.
+ */
+ tspace = PS;
+ PS = SS;
+ SS = tspace;
+ SS.space = SS.back;
+
+ /* Handle the 'p' flag. */
+ if (cp->u.s->p)
+ OUT();
+
+ /* Handle the 'w' flag. */
+ if (cp->u.s->wfile && !pd) {
+ if (cp->u.s->wfd == -1 && (cp->u.s->wfd = open(cp->u.s->wfile,
+ O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, DEFFILEMODE)) == -1)
+ err(1, "%s", cp->u.s->wfile);
+ if (write(cp->u.s->wfd, ps, psl) != (ssize_t)psl ||
+ write(cp->u.s->wfd, "\n", 1) != 1)
+ err(1, "%s", cp->u.s->wfile);
+ }
+ return (1);
+}
+
+/*
+ * do_tr --
+ * Perform translation ('y' command) in the pattern space.
+ */
+static void
+do_tr(struct s_tr *y)
+{
+ SPACE tmp;
+ char c, *p;
+ size_t clen, left;
+ size_t i;
+
+ if (MB_CUR_MAX == 1) {
+ /*
+ * Single-byte encoding: perform in-place translation
+ * of the pattern space.
+ */
+ for (p = ps; p < &ps[psl]; p++)
+ *p = (char)y->bytetab[(u_char)*p];
+ } else {
+ /*
+ * Multi-byte encoding: perform translation into the
+ * translation space, then swap the translation and
+ * pattern spaces.
+ */
+ /* Clean translation space. */
+ YS.len = 0;
+ for (p = ps, left = psl; left > 0; p += clen, left -= clen) {
+ if ((c = (char)y->bytetab[(u_char)*p]) != '\0') {
+ cspace(&YS, &c, 1, APPEND);
+ clen = 1;
+ continue;
+ }
+ for (i = 0; i < y->nmultis; i++)
+ if (left >= y->multis[i].fromlen &&
+ memcmp(p, y->multis[i].from,
+ y->multis[i].fromlen) == 0)
+ break;
+ if (i < y->nmultis) {
+ cspace(&YS, y->multis[i].to,
+ y->multis[i].tolen, APPEND);
+ clen = y->multis[i].fromlen;
+ } else {
+ cspace(&YS, p, 1, APPEND);
+ clen = 1;
+ }
+ }
+ /* Swap the translation space and the pattern space. */
+ tmp = PS;
+ PS = YS;
+ YS = tmp;
+ YS.space = YS.back;
+ }
+}
+
+/*
+ * Flush append requests. Always called before reading a line,
+ * therefore it also resets the substitution done (sdone) flag.
+ */
+static void
+flush_appends(void)
+{
+ FILE *f;
+ size_t count, i;
+ char buf[8 * 1024];
+
+ for (i = 0; i < appendx; i++)
+ switch (appends[i].type) {
+ case AP_STRING:
+ fwrite(appends[i].s, sizeof(char), appends[i].len,
+ outfile);
+ break;
+ case AP_FILE:
+ /*
+ * Read files probably shouldn't be cached. Since
+ * it's not an error to read a non-existent file,
+ * it's possible that another program is interacting
+ * with the sed script through the filesystem. It
+ * would be truly bizarre, but possible. It's probably
+ * not that big a performance win, anyhow.
+ */
+ if ((f = fopen(appends[i].s, "r")) == NULL)
+ break;
+ while ((count = fread(buf, sizeof(char), sizeof(buf), f)))
+ (void)fwrite(buf, sizeof(char), count, outfile);
+ (void)fclose(f);
+ break;
+ }
+ if (ferror(outfile))
+ errx(1, "%s: %s", outfname, strerror(errno ? errno : EIO));
+ appendx = 0;
+ sdone = 0;
+}
+
+static void
+lputs(char *s, size_t len)
+{
+ static const char escapes[] = "\\\a\b\f\r\t\v";
+ int c;
+ size_t col, width;
+ const char *p;
+#ifdef TIOCGWINSZ
+ struct winsize win;
+#endif
+ static size_t termwidth = (size_t)-1;
+ size_t clen, i;
+ wchar_t wc;
+ mbstate_t mbs;
+
+ if (outfile != stdout)
+ termwidth = 60;
+ if (termwidth == (size_t)-1) {
+ if ((p = getenv("COLUMNS")) && *p != '\0')
+ termwidth = (size_t)atoi(p);
+#ifdef TIOCGWINSZ
+ else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 &&
+ win.ws_col > 0)
+ termwidth = win.ws_col;
+#endif
+ else
+ termwidth = 60;
+ }
+ if (termwidth == 0)
+ termwidth = 1;
+
+ memset(&mbs, 0, sizeof(mbs));
+ col = 0;
+ while (len != 0) {
+ clen = mbrtowc(&wc, s, len, &mbs);
+ if (clen == 0)
+ clen = 1;
+ if (clen == (size_t)-1 || clen == (size_t)-2) {
+ wc = (unsigned char)*s;
+ clen = 1;
+ memset(&mbs, 0, sizeof(mbs));
+ }
+ if (wc == '\n') {
+ if (col + 1 >= termwidth)
+ fprintf(outfile, "\\\n");
+ fputc('$', outfile);
+ fputc('\n', outfile);
+ col = 0;
+ } else if (iswprint(wc)) {
+ width = (size_t)wcwidth(wc);
+ if (col + width >= termwidth) {
+ fprintf(outfile, "\\\n");
+ col = 0;
+ }
+ fwrite(s, 1, clen, outfile);
+ col += width;
+ } else if (wc != L'\0' && (c = wctob(wc)) != EOF &&
+ (p = strchr(escapes, c)) != NULL) {
+ if (col + 2 >= termwidth) {
+ fprintf(outfile, "\\\n");
+ col = 0;
+ }
+ fprintf(outfile, "\\%c", "\\abfrtv"[p - escapes]);
+ col += 2;
+ } else {
+ if (col + 4 * clen >= termwidth) {
+ fprintf(outfile, "\\\n");
+ col = 0;
+ }
+ for (i = 0; i < clen; i++)
+ fprintf(outfile, "\\%03o",
+ (int)(unsigned char)s[i]);
+ col += 4 * clen;
+ }
+ s += clen;
+ len -= clen;
+ }
+ if (col + 1 >= termwidth)
+ fprintf(outfile, "\\\n");
+ (void)fputc('$', outfile);
+ (void)fputc('\n', outfile);
+ if (ferror(outfile))
+ errx(1, "%s: %s", outfname, strerror(errno ? errno : EIO));
+}
+
+static __inline int
+regexec_e(regex_t *preg, const char *string, int eflags, int nomatch,
+ size_t slen)
+{
+ int eval;
+#ifndef REG_STARTEND
+ char *buf;
+#endif
+
+ if (preg == NULL) {
+ if (defpreg == NULL)
+ errx(1, "first RE may not be empty");
+ } else
+ defpreg = preg;
+
+ /* Set anchors */
+#ifndef REG_STARTEND
+ buf = xmalloc(slen + 1);
+ (void)memcpy(buf, string, slen);
+ buf[slen] = '\0';
+ eval = regexec(defpreg, buf,
+ nomatch ? 0 : maxnsub + 1, match, eflags);
+ free(buf);
+#else
+ match[0].rm_so = 0;
+ match[0].rm_eo = (regoff_t)slen;
+ eval = regexec(defpreg, string,
+ nomatch ? 0 : maxnsub + 1, match, eflags | REG_STARTEND);
+#endif
+ switch(eval) {
+ case 0:
+ return (1);
+ case REG_NOMATCH:
+ return (0);
+ }
+ errx(1, "RE error: %s", strregerror(eval, defpreg));
+ /* NOTREACHED */
+}
+
+/*
+ * regsub - perform substitutions after a regexp match
+ * Based on a routine by Henry Spencer
+ */
+static void
+regsub(SPACE *sp, char *string, char *src)
+{
+ size_t len;
+ int no;
+ char c, *dst;
+
+#define NEEDSP(reqlen) \
+ /* XXX What is the +1 for? */ \
+ if (sp->len + (reqlen) + 1 >= sp->blen) { \
+ sp->blen += (reqlen) + 1024; \
+ sp->space = sp->back = xrealloc(sp->back, sp->blen); \
+ dst = sp->space + sp->len; \
+ }
+
+ dst = sp->space + sp->len;
+ while ((c = *src++) != '\0') {
+ if (c == '&')
+ no = 0;
+ else if (c == '\\' && isdigit((unsigned char)*src))
+ no = *src++ - '0';
+ else
+ no = -1;
+ if (no < 0) { /* Ordinary character. */
+ if (c == '\\' && (*src == '\\' || *src == '&'))
+ c = *src++;
+ NEEDSP(1);
+ *dst++ = c;
+ ++sp->len;
+ } else if (match[no].rm_so != -1 && match[no].rm_eo != -1) {
+ len = (size_t)(match[no].rm_eo - match[no].rm_so);
+ NEEDSP(len);
+ memmove(dst, string + match[no].rm_so, len);
+ dst += len;
+ sp->len += len;
+ }
+ }
+ NEEDSP(1);
+ *dst = '\0';
+}
+
+/*
+ * cspace --
+ * Concatenate space: append the source space to the destination space,
+ * allocating new space as necessary.
+ */
+void
+cspace(SPACE *sp, const char *p, size_t len, enum e_spflag spflag)
+{
+ size_t tlen;
+
+ /* Make sure SPACE has enough memory and ramp up quickly. */
+ tlen = sp->len + len + 1;
+ if (tlen > sp->blen) {
+ sp->blen = tlen + 1024;
+ sp->space = sp->back = xrealloc(sp->back, sp->blen);
+ }
+
+ if (spflag == REPLACE)
+ sp->len = 0;
+
+ memmove(sp->space + sp->len, p, len);
+
+ sp->space[sp->len += len] = '\0';
+}
+
+/*
+ * Close all cached opened files and report any errors
+ */
+void
+cfclose(struct s_command *cp, struct s_command *end)
+{
+
+ for (; cp != end; cp = cp->next)
+ switch(cp->code) {
+ case 's':
+ if (cp->u.s->wfd != -1 && close(cp->u.s->wfd))
+ err(1, "%s", cp->u.s->wfile);
+ cp->u.s->wfd = -1;
+ break;
+ case 'w':
+ if (cp->u.fd != -1 && close(cp->u.fd))
+ err(1, "%s", cp->t);
+ cp->u.fd = -1;
+ break;
+ case '{':
+ cfclose(cp->u.c, cp->next);
+ break;
+ }
+}
diff --git a/usr.bin/sed/sed.1 b/usr.bin/sed/sed.1
new file mode 100644
index 0000000..82b3cbd
--- /dev/null
+++ b/usr.bin/sed/sed.1
@@ -0,0 +1,640 @@
+.\" $NetBSD: sed.1,v 1.40 2014/06/25 02:05:58 uwe Exp $
+.\" Copyright (c) 1992, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)sed.1 8.2 (Berkeley) 12/30/93
+.\" $FreeBSD: head/usr.bin/sed/sed.1 259132 2013-12-09 18:57:20Z eadler $
+.\"
+.Dd June 18, 2014
+.Dt SED 1
+.Os
+.Sh NAME
+.Nm sed
+.Nd stream editor
+.Sh SYNOPSIS
+.Nm
+.Op Fl aElnru
+.Ar command
+.Op Ar
+.Nm
+.Op Fl aElnru
+.Op Fl e Ar command
+.Op Fl f Ar command_file
+.Op Fl I Ns Op Ar extension
+.Op Fl i Ns Op Ar extension
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility reads the specified files, or the standard input if no files
+are specified, modifying the input as specified by a list of commands.
+The input is then written to the standard output.
+.Pp
+A single command may be specified as the first argument to
+.Nm .
+Multiple commands may be specified by using the
+.Fl e
+or
+.Fl f
+options.
+All commands are applied to the input in the order they are specified
+regardless of their origin.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl a
+The files listed as parameters for the
+.Dq w
+functions are created (or truncated) before any processing begins,
+by default.
+The
+.Fl a
+option causes
+.Nm
+to delay opening each file until a command containing the related
+.Dq w
+function is applied to a line of input.
+.It Fl E
+Interpret regular expressions as extended (modern) regular expressions
+rather than basic regular expressions (BRE's).
+The
+.Xr re_format 7
+manual page fully describes both formats.
+.It Fl e Ar command
+Append the editing commands specified by the
+.Ar command
+argument
+to the list of commands.
+.It Fl f Ar command_file
+Append the editing commands found in the file
+.Ar command_file
+to the list of commands.
+The editing commands should each be listed on a separate line.
+.It Fl I Ns Op Ar extension
+Edit files in-place, saving backups with the specified
+.Ar extension .
+If no
+.Ar extension
+is given, no backup will be saved.
+It is not recommended to give a zero-length
+.Ar extension
+when in-place editing files, as you risk corruption or partial content
+in situations where disk space is exhausted, etc.
+.Pp
+Note that in-place editing with
+.Fl I
+still takes place in a single continuous line address space covering
+all files, although each file preserves its individuality instead of
+forming one output stream.
+The line counter is never reset between files, address ranges can span
+file boundaries, and the
+.Dq $
+address matches only the last line of the last file.
+(See
+.Sx "Sed Addresses" . )
+That can lead to unexpected results in many cases of in-place editing,
+where using
+.Fl i
+is desired.
+.It Fl i Ns Op Ar extension
+Edit files in-place similarly to
+.Fl I ,
+but treat each file independently from other files.
+In particular, line numbers in each file start at 1,
+the
+.Dq $
+address matches the last line of the current file,
+and address ranges are limited to the current file.
+(See
+.Sx "Sed Addresses" . )
+The net result is as though each file were edited by a separate
+.Nm
+instance.
+.It Fl l
+Make output line buffered.
+.It Fl n
+By default, each line of input is echoed to the standard output after
+all of the commands have been applied to it.
+The
+.Fl n
+option suppresses this behavior.
+.It Fl r
+Same as
+.Fl E
+for compatibility with GNU sed.
+.It Fl u
+Make output unbuffered.
+.El
+.Pp
+The form of a
+.Nm
+command is as follows:
+.Pp
+.Dl [address[,address]]function[arguments]
+.Pp
+Whitespace may be inserted before the first address and the function
+portions of the command.
+.Pp
+Normally,
+.Nm
+cyclically copies a line of input, not including its terminating newline
+character, into a
+.Em "pattern space" ,
+(unless there is something left after a
+.Dq D
+function),
+applies all of the commands with addresses that select that pattern space,
+copies the pattern space to the standard output, appending a newline, and
+deletes the pattern space.
+.Pp
+Some of the functions use a
+.Em "hold space"
+to save all or part of the pattern space for subsequent retrieval.
+.Ss "Sed Addresses"
+An address is not required, but if specified must have one of the
+following formats:
+.Bl -bullet -offset indent
+.It
+a number that counts
+input lines
+cumulatively across input files (or in each file independently
+if a
+.Fl i
+option is in effect);
+.It
+a dollar
+.Pq Dq $
+character that addresses the last line of input (or the last line
+of the current file if a
+.Fl i
+option was specified);
+.It
+a context address
+that consists of a regular expression preceded and followed by a
+delimiter.
+The closing delimiter can also optionally be followed by the
+.Dq i
+character, to indicate that the regular expression is to be matched
+in a case-insensitive way.
+.El
+.Pp
+A command line with no addresses selects every pattern space.
+.Pp
+A command line with one address selects all of the pattern spaces
+that match the address.
+.Pp
+A command line with two addresses selects an inclusive range.
+This
+range starts with the first pattern space that matches the first
+address.
+The end of the range is the next following pattern space
+that matches the second address.
+If the second address is a number
+less than or equal to the line number first selected, only that
+line is selected.
+The number in the second address may be prefixed with a
+.Pq Dq \&+
+to specify the number of lines to match after the first pattern.
+In the case when the second address is a context
+address,
+.Nm
+does not re-match the second address against the
+pattern space that matched the first address.
+Starting at the
+first line following the selected range,
+.Nm
+starts looking again for the first address.
+.Pp
+Editing commands can be applied to non-selected pattern spaces by use
+of the exclamation character
+.Pq Dq \&!
+function.
+.Ss "Sed Regular Expressions"
+The regular expressions used in
+.Nm ,
+by default, are basic regular expressions (BREs, see
+.Xr re_format 7
+for more information), but extended (modern) regular expressions can be used
+instead if the
+.Fl E
+flag is given.
+In addition,
+.Nm
+has the following two additions to regular expressions:
+.Pp
+.Bl -enum -compact
+.It
+In a context address, any character other than a backslash
+.Pq Dq \e
+or newline character may be used to delimit the regular expression.
+The opening delimiter needs to be preceded by a backslash
+unless it is a slash.
+For example, the context address
+.Li \exabcx
+is equivalent to
+.Li /abc/ .
+Also, putting a backslash character before the delimiting character
+within the regular expression causes the character to be treated literally.
+For example, in the context address
+.Li \exabc\exdefx ,
+the RE delimiter is an
+.Dq x
+and the second
+.Dq x
+stands for itself, so that the regular expression is
+.Dq abcxdef .
+.Pp
+.It
+The escape sequence \en matches a newline character embedded in the
+pattern space.
+You cannot, however, use a literal newline character in an address or
+in the substitute command.
+.El
+.Pp
+One special feature of
+.Nm
+regular expressions is that they can default to the last regular
+expression used.
+If a regular expression is empty, i.e., just the delimiter characters
+are specified, the last regular expression encountered is used instead.
+The last regular expression is defined as the last regular expression
+used as part of an address or substitute command, and at run-time, not
+compile-time.
+For example, the command
+.Dq /abc/s//XXX/
+will substitute
+.Dq XXX
+for the pattern
+.Dq abc .
+.Ss "Sed Functions"
+In the following list of commands, the maximum number of permissible
+addresses for each command is indicated by [0addr], [1addr], or [2addr],
+representing zero, one, or two addresses.
+.Pp
+The argument
+.Em text
+consists of one or more lines.
+To embed a newline in the text, precede it with a backslash.
+Other backslashes in text are deleted and the following character
+taken literally.
+.Pp
+The
+.Dq r
+and
+.Dq w
+functions take an optional file parameter, which should be separated
+from the function letter by white space.
+Each file given as an argument to
+.Nm
+is created (or its contents truncated) before any input processing begins.
+.Pp
+The
+.Dq b ,
+.Dq r ,
+.Dq s ,
+.Dq t ,
+.Dq w ,
+.Dq y ,
+.Dq \&! ,
+and
+.Dq \&:
+functions all accept additional arguments.
+The following synopses indicate which arguments have to be separated from
+the function letters by white space characters.
+.Pp
+Two of the functions take a function-list.
+This is a list of
+.Nm
+functions separated by newlines, as follows:
+.Bd -literal -offset indent
+{ function
+ function
+ ...
+ function
+}
+.Ed
+.Pp
+The
+.Dq {
+can be preceded by white space and can be followed by white space.
+The function can be preceded by white space.
+The terminating
+.Dq }
+must be preceded by a newline, and may also be preceded by white space.
+.Pp
+.Bl -tag -width "XXXXXX" -compact
+.It [2addr] function-list
+Execute function-list only when the pattern space is selected.
+.Pp
+.It [1addr]a\e
+.It text
+Write
+.Em text
+to standard output immediately before each attempt to read a line of input,
+whether by executing the
+.Dq N
+function or by beginning a new cycle.
+.Pp
+.It [2addr]b[label]
+Branch to the
+.Dq \&:
+function with the specified label.
+If the label is not specified, branch to the end of the script.
+.Pp
+.It [2addr]c\e
+.It text
+Delete the pattern space.
+With 0 or 1 address or at the end of a 2-address range,
+.Em text
+is written to the standard output.
+.Pp
+.It [2addr]d
+Delete the pattern space and start the next cycle.
+.Pp
+.It [2addr]D
+Delete the initial segment of the pattern space through the first
+newline character and start the next cycle.
+.Pp
+.It [2addr]g
+Replace the contents of the pattern space with the contents of the
+hold space.
+.Pp
+.It [2addr]G
+Append a newline character followed by the contents of the hold space
+to the pattern space.
+.Pp
+.It [2addr]h
+Replace the contents of the hold space with the contents of the
+pattern space.
+.Pp
+.It [2addr]H
+Append a newline character followed by the contents of the pattern space
+to the hold space.
+.Pp
+.It [1addr]i\e
+.It text
+Write
+.Em text
+to the standard output.
+.Pp
+.It [2addr]l
+(The letter ell.)
+Write the pattern space to the standard output in a visually unambiguous
+form.
+This form is as follows:
+.Pp
+.Bl -tag -width "carriage-returnXX" -offset indent -compact
+.It backslash
+\e\e
+.It alert
+\ea
+.It form-feed
+\ef
+.It carriage-return
+\er
+.It tab
+\et
+.It vertical tab
+\ev
+.El
+.Pp
+Nonprintable characters are written as three-digit octal numbers (with a
+preceding backslash) for each byte in the character (most significant byte
+first).
+Long lines are folded, with the point of folding indicated by displaying
+a backslash followed by a newline.
+The end of each line is marked with a
+.Dq $ .
+.Pp
+.It [2addr]n
+Write the pattern space to the standard output if the default output has
+not been suppressed, and replace the pattern space with the next line of
+input.
+.Pp
+.It [2addr]N
+Append the next line of input to the pattern space, using an embedded
+newline character to separate the appended material from the original
+contents.
+Note that the current line number changes.
+.Pp
+.It [2addr]p
+Write the pattern space to standard output.
+.Pp
+.It [2addr]P
+Write the pattern space, up to the first newline character to the
+standard output.
+.Pp
+.It [1addr]q
+Branch to the end of the script and quit without starting a new cycle.
+.Pp
+.It [1addr]r file
+Copy the contents of
+.Em file
+to the standard output immediately before the next attempt to read a
+line of input.
+If
+.Em file
+cannot be read for any reason, it is silently ignored and no error
+condition is set.
+.Pp
+.It [2addr]s/regular expression/replacement/flags
+Substitute the replacement string for the first instance of the regular
+expression in the pattern space.
+Any character other than backslash or newline can be used instead of
+a slash to delimit the RE and the replacement.
+Within the RE and the replacement, the RE delimiter itself can be used as
+a literal character if it is preceded by a backslash.
+.Pp
+An ampersand
+.Pq Dq &
+appearing in the replacement is replaced by the string matching the RE.
+The special meaning of
+.Dq &
+in this context can be suppressed by preceding it by a backslash.
+The string
+.Dq \e# ,
+where
+.Dq #
+is a digit, is replaced by the text matched
+by the corresponding backreference expression (see
+.Xr re_format 7 ) .
+.Pp
+A line can be split by substituting a newline character into it.
+To specify a newline character in the replacement string, precede it with
+a backslash.
+.Pp
+The value of
+.Em flags
+in the substitute function is zero or more of the following:
+.Bl -tag -width "XXXXXX" -offset indent
+.It Ar N
+Make the substitution only for the
+.Ar N Ns 'th
+occurrence of the regular expression in the pattern space.
+.It g
+Make the substitution for all non-overlapping matches of the
+regular expression, not just the first one.
+.It p
+Write the pattern space to standard output if a replacement was made.
+If the replacement string is identical to that which it replaces, it
+is still considered to have been a replacement.
+.It w Em file
+Append the pattern space to
+.Em file
+if a replacement was made.
+If the replacement string is identical to that which it replaces, it
+is still considered to have been a replacement.
+.It i or I
+Match the regular expression in a case-insensitive way.
+.El
+.Pp
+.It [2addr]t [label]
+Branch to the
+.Dq \&:
+function bearing the label if any substitutions have been made since the
+most recent reading of an input line or execution of a
+.Dq t
+function.
+If no label is specified, branch to the end of the script.
+.Pp
+.It [2addr]w Em file
+Append the pattern space to the
+.Em file .
+.Pp
+.It [2addr]x
+Swap the contents of the pattern and hold spaces.
+.Pp
+.It [2addr]y/string1/string2/
+Replace all occurrences of characters in
+.Em string1
+in the pattern space with the corresponding characters from
+.Em string2 .
+Any character other than a backslash or newline can be used instead of
+a slash to delimit the strings.
+Within
+.Em string1
+and
+.Em string2 ,
+a backslash followed by any character other than a newline is that literal
+character, and a backslash followed by an ``n'' is replaced by a newline
+character.
+.Pp
+.It [2addr]!function
+.It [2addr]!function-list
+Apply the function or function-list only to the lines that are
+.Em not
+selected by the address(es).
+.Pp
+.It [0addr]:label
+This function does nothing; it bears a label to which the
+.Dq b
+and
+.Dq t
+commands may branch.
+.Pp
+.It [1addr]=
+Write the line number to the standard output followed by a newline
+character.
+.Pp
+.It [0addr]
+Empty lines are ignored.
+.Pp
+.It [0addr]#
+The
+.Dq #
+and the remainder of the line are ignored (treated as a comment), with
+the single exception that if the first two characters in the file are
+.Dq #n ,
+the default output is suppressed.
+This is the same as specifying the
+.Fl n
+option on the command line.
+.El
+.Sh ENVIRONMENT
+The
+.Ev COLUMNS , LANG , LC_ALL , LC_CTYPE
+and
+.Ev LC_COLLATE
+environment variables affect the execution of
+.Nm
+as described in
+.Xr environ 7 .
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr awk 1 ,
+.Xr ed 1 ,
+.Xr grep 1 ,
+.Xr regex 3 ,
+.Xr re_format 7
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be a superset of the
+.St -p1003.2
+specification.
+.Pp
+The
+.Fl a , E , I ,
+and
+.Fl i
+options, the prefixing
+.Dq \&+
+in the second member of an address range,
+as well as the
+.Dq I
+flag to the address regular expression and substitution command are
+non-standard
+.Fx
+extensions and may not be available on other operating systems.
+.Sh HISTORY
+A
+.Nm
+command, written by
+.An L. E. McMahon ,
+appeared in
+.At v7 .
+.Sh AUTHORS
+.An "Diomidis D. Spinellis" Aq dds@FreeBSD.org
+.Sh BUGS
+Multibyte characters containing a byte with value 0x5C
+.Tn ( ASCII
+.Ql \e )
+may be incorrectly treated as line continuation characters in arguments to the
+.Dq a ,
+.Dq c
+and
+.Dq i
+commands.
+Multibyte characters cannot be used as delimiters with the
+.Dq s
+and
+.Dq y
+commands.
diff --git a/usr.bin/sort/append.c b/usr.bin/sort/append.c
new file mode 100644
index 0000000..07e15e1
--- /dev/null
+++ b/usr.bin/sort/append.c
@@ -0,0 +1,94 @@
+/* $NetBSD: append.c,v 1.23 2009/11/06 18:34:22 joerg Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "sort.h"
+
+__RCSID("$NetBSD: append.c,v 1.23 2009/11/06 18:34:22 joerg Exp $");
+
+#include <stdlib.h>
+
+/*
+ * copy sorted lines to output
+ * Ignore duplicates (marked with -ve keylen)
+ */
+void
+append(RECHEADER **keylist, int nelem, FILE *fp, put_func_t put)
+{
+ RECHEADER **cpos, **lastkey;
+ RECHEADER *crec;
+
+ lastkey = keylist + nelem;
+ if (REVERSE) {
+ for (cpos = lastkey; cpos-- > keylist;) {
+ crec = *cpos;
+ if (crec->keylen >= 0)
+ put(crec, fp);
+ }
+ } else {
+ for (cpos = keylist; cpos < lastkey; cpos++) {
+ crec = *cpos;
+ if (crec->keylen >= 0)
+ put(crec, fp);
+ }
+ }
+}
diff --git a/usr.bin/sort/fields.c b/usr.bin/sort/fields.c
new file mode 100644
index 0000000..6208a78
--- /dev/null
+++ b/usr.bin/sort/fields.c
@@ -0,0 +1,380 @@
+/* $NetBSD: fields.c,v 1.33 2013/01/20 10:12:58 apb Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* Subroutines to generate sort keys. */
+
+#include "sort.h"
+
+__RCSID("$NetBSD: fields.c,v 1.33 2013/01/20 10:12:58 apb Exp $");
+
+#define SKIP_BLANKS(ptr) { \
+ if (BLANK & d_mask[*(ptr)]) \
+ while (BLANK & d_mask[*(++(ptr))]); \
+}
+
+#define NEXTCOL(pos) { \
+ if (!SEP_FLAG) \
+ while (BLANK & l_d_mask[*(++pos)]); \
+ while ((*(pos+1) != '\0') && !((FLD_D | REC_D_F) & l_d_mask[*++pos]));\
+}
+
+static u_char *enterfield(u_char *, const u_char *, struct field *, int);
+static u_char *number(u_char *, const u_char *, u_char *, u_char *, int);
+static u_char *length(u_char *, const u_char *, u_char *, u_char *, int);
+
+#define DECIMAL_POINT '.'
+
+/*
+ * constructs sort key with leading recheader, followed by the key,
+ * followed by the original line.
+ */
+length_t
+enterkey(RECHEADER *keybuf, const u_char *keybuf_end, u_char *line_data,
+ size_t line_size, struct field fieldtable[])
+ /* keybuf: pointer to start of key */
+{
+ int i;
+ u_char *l_d_mask;
+ u_char *lineend, *pos;
+ const u_char *endkey;
+ u_char *keypos;
+ struct coldesc *clpos;
+ int col = 1;
+ struct field *ftpos;
+
+ l_d_mask = d_mask;
+ pos = line_data - 1;
+ lineend = line_data + line_size-1;
+ /* don't include rec_delimiter */
+
+ for (i = 0; i < ncols; i++) {
+ clpos = clist + i;
+ for (; (col < clpos->num) && (pos < lineend); col++) {
+ NEXTCOL(pos);
+ }
+ if (pos >= lineend)
+ break;
+ clpos->start = SEP_FLAG ? pos + 1 : pos;
+ NEXTCOL(pos);
+ clpos->end = pos;
+ col++;
+ if (pos >= lineend) {
+ clpos->end = lineend;
+ i++;
+ break;
+ }
+ }
+ for (; i <= ncols; i++)
+ clist[i].start = clist[i].end = lineend;
+ if (clist[0].start < line_data)
+ clist[0].start++;
+
+ /*
+ * We write the sort keys (concatenated) followed by the
+ * original line data (for output) as the 'keybuf' data.
+ * keybuf->length is the number of key bytes + data bytes.
+ * keybuf->offset is the number of key bytes.
+ * We add a record separator weight after the key in case
+ * (as is usual) we need to preserve the order of equal lines,
+ * and for 'sort -u'.
+ * The key itself will have had the correct weight applied.
+ */
+ keypos = keybuf->data;
+ endkey = keybuf_end - line_size - 1;
+ if (endkey <= keypos)
+ /* No room for any key bytes */
+ return 1;
+
+ for (ftpos = fieldtable + 1; ftpos->icol.num; ftpos++) {
+ if ((keypos = enterfield(keypos, endkey, ftpos,
+ fieldtable->flags)) == NULL)
+ return (1);
+ }
+
+ keybuf->offset = keypos - keybuf->data;
+ keybuf->length = keybuf->offset + line_size;
+
+ /*
+ * Posix requires that equal keys be further sorted by the
+ * entire original record.
+ * NetBSD has (at least for some time) kept equal keys in
+ * their original order.
+ * For 'sort -u' posix_sort is unset.
+ */
+ keybuf->keylen = posix_sort ? keybuf->length : keybuf->offset;
+
+ memcpy(keypos, line_data, line_size);
+ return (0);
+}
+
+/*
+ * constructs a field (as defined by -k) within a key
+ */
+static u_char *
+enterfield(u_char *tablepos, const u_char *endkey, struct field *cur_fld,
+ int gflags)
+{
+ u_char *start, *end, *lineend, *mask, *lweight;
+ struct column icol, tcol;
+ u_int flags;
+
+ icol = cur_fld->icol;
+ tcol = cur_fld->tcol;
+ flags = cur_fld->flags;
+ start = icol.p->start;
+ lineend = clist[ncols].end;
+ if (flags & BI)
+ SKIP_BLANKS(start);
+ start += icol.indent;
+ start = min(start, lineend);
+
+ if (!tcol.num)
+ end = lineend;
+ else {
+ if (tcol.indent) {
+ end = tcol.p->start;
+ if (flags & BT)
+ SKIP_BLANKS(end);
+ end += tcol.indent;
+ end = min(end, lineend);
+ } else
+ end = tcol.p->end;
+ }
+
+ if (flags & L)
+ return length(tablepos, endkey, start, end, flags);
+ if (flags & N)
+ return number(tablepos, endkey, start, end, flags);
+
+ /* Bound check space - assuming nothing is skipped */
+ if (tablepos + (end - start) + 1 >= endkey)
+ return NULL;
+
+ mask = cur_fld->mask;
+ lweight = cur_fld->weights;
+ for (; start < end; start++) {
+ if (!mask || mask[*start]) {
+ *tablepos++ = lweight[*start];
+ }
+ }
+ /* Add extra byte (absent from lweight) to sort short keys correctly */
+ *tablepos++ = lweight[REC_D];
+ return tablepos;
+}
+
+/*
+ * Numbers are converted to a floating point format (exponent & mantissa)
+ * so that they compare correctly as sequence of unsigned bytes.
+ * Bytes 0x00 and 0xff are used to terminate positive and negative numbers
+ * to ensure that 0.123 sorts after 0.12 and -0.123 sorts before -0.12.
+ *
+ * The first byte contain the overall sign, exponent sign and some of the
+ * exponent. These have to be ordered (-ve value, decreasing exponent),
+ * zero, (+ve value, increasing exponent).
+ *
+ * The first byte is 0x80 for zero, 0xc0 for +ve with exponent 0.
+ * -ve values are the 1's compliments (so 0x7f isn't used!).
+ *
+ * This only leaves 63 byte values for +ve exponents - which isn't enough.
+ * The largest 4 exponent values are used to hold a byte count of the
+ * number of following bytes that contain 8 exponent bits per byte,
+ * This lets us sort exponents from -2^31 to +2^31.
+ *
+ * The mantissa is stored 2 digits per byte offset by 0x40, for negative
+ * numbers the order must be reversed (they are bit inverted).
+ *
+ * Reverse sorts are done by inverting the sign of the number.
+ */
+#define MAX_EXP_ENC ((int)sizeof(int))
+
+static u_char *
+number(u_char *pos, const u_char *bufend, u_char *line, u_char *lineend,
+ int reverse)
+{
+ int exponent = -1;
+ int had_dp = 0;
+ u_char *tline;
+ char ch;
+ unsigned int val;
+ u_char *last_nz_pos;
+ u_char negate;
+
+ if (reverse & R)
+ negate = 0xff;
+ else
+ negate = 0;
+
+ /* Give ourselves space for the key terminator */
+ bufend--;
+
+ /* Ensure we have enough space for the exponent */
+ if (pos + 1 + MAX_EXP_ENC > bufend)
+ return (NULL);
+
+ SKIP_BLANKS(line);
+ if (*line == '-') { /* set the sign */
+ negate ^= 0xff;
+ line++;
+ } else if (*line == '+') {
+ line++;
+ }
+
+ /* eat initial zeroes */
+ for (; *line == '0' && line < lineend; line++)
+ continue;
+
+ /* calculate exponents */
+ if (*line == DECIMAL_POINT) {
+ /* Decimal fraction */
+ had_dp = 1;
+ while (*++line == '0' && line < lineend)
+ exponent--;
+ } else {
+ /* Large (absolute) value, count digits */
+ for (tline = line; *tline >= '0' &&
+ *tline <= '9' && tline < lineend; tline++)
+ exponent++;
+ }
+
+ /* If the first/next character isn't a digit, value is zero */
+ if (*line < '1' || *line > '9' || line >= lineend) {
+ /* This may be "0", "0.00", "000" or "fubar" but sorts as 0 */
+ /* XXX what about NaN, NAN, inf and INF */
+ *pos++ = 0x80;
+ return pos;
+ }
+
+ /* Maybe here we should allow for e+12 (etc) */
+
+ if (exponent < 0x40 - MAX_EXP_ENC && -exponent < 0x40 - MAX_EXP_ENC) {
+ /* Value ok for simple encoding */
+ /* exponent 0 is 0xc0 for +ve numbers and 0x40 for -ve ones */
+ exponent += 0xc0;
+ *pos++ = negate ^ exponent;
+ } else {
+ /* Out or range for a single byte */
+ int c, t;
+ t = exponent > 0 ? exponent : -exponent;
+ /* Count how many 8-bit bytes are needed */
+ for (c = 0; ; c++) {
+ t >>= 8;
+ if (t == 0)
+ break;
+ }
+ /* 'c' better be 0..3 here - but probably 0..1 */
+ /* Offset just outside valid range */
+ t = c + 0x40 - MAX_EXP_ENC;
+ if (exponent < 0)
+ t = -t;
+ *pos++ = negate ^ (t + 0xc0);
+ /* now add each byte, most significant first */
+ for (; c >= 0; c--)
+ *pos++ = negate ^ (exponent >> (c * 8));
+ }
+
+ /* Finally add mantissa, 2 digits per byte */
+ for (last_nz_pos = pos; line < lineend; ) {
+ if (pos >= bufend)
+ return NULL;
+ ch = *line++;
+ val = (ch - '0') * 10;
+ if (val > 90) {
+ if (ch == DECIMAL_POINT && !had_dp) {
+ had_dp = 1;
+ continue;
+ }
+ break;
+ }
+ while (line < lineend) {
+ ch = *line++;
+ if (ch == DECIMAL_POINT && !had_dp) {
+ had_dp = 1;
+ continue;
+ }
+ if (ch < '0' || ch > '9')
+ line = lineend;
+ else
+ val += ch - '0';
+ break;
+ }
+ *pos++ = negate ^ (val + 0x40);
+ if (val != 0)
+ last_nz_pos = pos;
+ }
+
+ /* Add key terminator, deleting any trailing "00" */
+ *last_nz_pos++ = negate;
+
+ return (last_nz_pos);
+}
+
+static u_char *
+length(u_char *pos, const u_char *bufend, u_char *line, u_char *lineend,
+ int flag)
+{
+ u_char buf[32];
+ int l;
+ SKIP_BLANKS(line);
+ l = snprintf((char *)buf, sizeof(buf), "%td", lineend - line);
+ return number(pos, bufend, buf, buf + l, flag);
+}
diff --git a/usr.bin/sort/files.c b/usr.bin/sort/files.c
new file mode 100644
index 0000000..7cb27c5
--- /dev/null
+++ b/usr.bin/sort/files.c
@@ -0,0 +1,279 @@
+/* $NetBSD: files.c,v 1.42 2015/08/05 07:10:03 mrg Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "sort.h"
+#include "fsort.h"
+
+__RCSID("$NetBSD: files.c,v 1.42 2015/08/05 07:10:03 mrg Exp $");
+
+#include <string.h>
+
+/* Align records in temporary files to avoid misaligned copies */
+#define REC_ROUNDUP(n) (((n) + sizeof (long) - 1) & ~(sizeof (long) - 1))
+
+static ssize_t seq(FILE *, u_char **);
+
+/*
+ * this is called when there is no special key. It's only called
+ * in the first fsort pass.
+ */
+
+static u_char *opos;
+static size_t osz;
+
+void
+makeline_copydown(RECHEADER *recbuf)
+{
+ memmove(recbuf->data, opos, osz);
+}
+
+int
+makeline(FILE *fp, RECHEADER *recbuf, u_char *bufend, struct field *dummy2)
+{
+ u_char *pos;
+ int c;
+
+ pos = recbuf->data;
+ if (osz != 0) {
+ /*
+ * Buffer shortage is solved by either of two ways:
+ * o flush previous buffered data and start using the
+ * buffer from start.
+ * makeline_copydown() above must be called.
+ * o realloc buffer
+ *
+ * This code has relied on realloc changing 'bufend',
+ * but that isn't necessarily true.
+ */
+ pos += osz;
+ osz = 0;
+ }
+
+ while (pos < bufend) {
+ c = getc(fp);
+ if (c == EOF) {
+ if (pos == recbuf->data) {
+ FCLOSE(fp);
+ return EOF;
+ }
+ /* Add terminator to partial line */
+ c = REC_D;
+ }
+ *pos++ = c;
+ if (c == REC_D) {
+ recbuf->offset = 0;
+ recbuf->length = pos - recbuf->data;
+ recbuf->keylen = recbuf->length - 1;
+ return (0);
+ }
+ }
+
+ /* Ran out of buffer space... */
+ if (recbuf->data < bufend) {
+ /* Remember where the partial record is */
+ osz = pos - recbuf->data;
+ opos = recbuf->data;
+ }
+ return (BUFFEND);
+}
+
+/*
+ * This generates keys. It's only called in the first fsort pass
+ */
+int
+makekey(FILE *fp, RECHEADER *recbuf, u_char *bufend, struct field *ftbl)
+{
+ static u_char *line_data;
+ static ssize_t line_size;
+ static int overflow = 0;
+
+ /* We get re-entered after returning BUFFEND - save old data */
+ if (overflow) {
+ overflow = enterkey(recbuf, bufend, line_data, line_size, ftbl);
+ return overflow ? BUFFEND : 0;
+ }
+
+ line_size = seq(fp, &line_data);
+ if (line_size == 0) {
+ FCLOSE(fp);
+ return EOF;
+ }
+
+ if (line_size > bufend - recbuf->data) {
+ overflow = 1;
+ } else {
+ overflow = enterkey(recbuf, bufend, line_data, line_size, ftbl);
+ }
+ return overflow ? BUFFEND : 0;
+}
+
+/*
+ * get a line of input from fp
+ */
+static ssize_t
+seq(FILE *fp, u_char **line)
+{
+ static u_char *buf;
+ static size_t buf_size = DEFLLEN;
+ u_char *end, *pos;
+ int c;
+ u_char *new_buf;
+
+ if (!buf) {
+ /* one-time initialization */
+ buf = malloc(buf_size);
+ if (!buf)
+ err(2, "malloc of linebuf for %zu bytes failed",
+ buf_size);
+ }
+
+ end = buf + buf_size;
+ pos = buf;
+ while ((c = getc(fp)) != EOF) {
+ *pos++ = c;
+ if (c == REC_D) {
+ *line = buf;
+ return pos - buf;
+ }
+ if (pos == end) {
+ /* Long line - double size of buffer */
+ /* XXX: Check here for stupidly long lines */
+ buf_size *= 2;
+ new_buf = realloc(buf, buf_size);
+ if (!new_buf)
+ err(2, "realloc of linebuf to %zu bytes failed",
+ buf_size);
+
+ end = new_buf + buf_size;
+ pos = new_buf + (pos - buf);
+ buf = new_buf;
+ }
+ }
+
+ if (pos != buf) {
+ /* EOF part way through line - add line terminator */
+ *pos++ = REC_D;
+ *line = buf;
+ return pos - buf;
+ }
+
+ return 0;
+}
+
+/*
+ * write a key/line pair to a temporary file
+ */
+void
+putrec(const RECHEADER *rec, FILE *fp)
+{
+ EWRITE(rec, 1, REC_ROUNDUP(offsetof(RECHEADER, data) + rec->length), fp,
+ "failed to write temp file");
+}
+
+/*
+ * write a line to output
+ */
+void
+putline(const RECHEADER *rec, FILE *fp)
+{
+ EWRITE(rec->data+rec->offset, 1, rec->length - rec->offset, fp,
+ "failed to write");
+}
+
+/*
+ * write dump of key to output (for -Dk)
+ */
+void
+putkeydump(const RECHEADER *rec, FILE *fp)
+{
+ EWRITE(rec, 1, REC_ROUNDUP(offsetof(RECHEADER, data) + rec->offset), fp,
+ "failed to write debug key");
+}
+
+/*
+ * get a record from a temporary file. (Used by merge sort.)
+ */
+int
+geteasy(FILE *fp, RECHEADER *rec, u_char *end, struct field *dummy2)
+{
+ length_t file_len;
+ int i;
+
+ (void)sizeof (char[offsetof(RECHEADER, length) == 0 ? 1 : -1]);
+
+ if ((u_char *)(rec + 1) > end)
+ return (BUFFEND);
+ if (!fread(&rec->length, 1, sizeof rec->length, fp)) {
+ fclose(fp);
+ return (EOF);
+ }
+ file_len = REC_ROUNDUP(offsetof(RECHEADER, data) + rec->length);
+ if (end - rec->data < (ptrdiff_t)file_len) {
+ for (i = sizeof rec->length - 1; i >= 0; i--)
+ ungetc(*((char *) rec + i), fp);
+ return (BUFFEND);
+ }
+
+ fread(&rec->length + 1, file_len - sizeof rec->length, 1, fp);
+ return (0);
+}
diff --git a/usr.bin/sort/fsort.c b/usr.bin/sort/fsort.c
new file mode 100644
index 0000000..c229ae2
--- /dev/null
+++ b/usr.bin/sort/fsort.c
@@ -0,0 +1,202 @@
+/* $NetBSD: fsort.c,v 1.47 2010/02/05 21:58:41 enami Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Read in a block of records (until 'enough').
+ * sort, write to temp file.
+ * Merge sort temp files into output file
+ * Small files miss out the temp file stage.
+ * Large files might get multiple merges.
+ */
+#include "sort.h"
+#include "fsort.h"
+
+__RCSID("$NetBSD: fsort.c,v 1.47 2010/02/05 21:58:41 enami Exp $");
+
+#include <stdlib.h>
+#include <string.h>
+
+#define SALIGN(n) ((n+sizeof(length_t)-1) & ~(sizeof(length_t)-1))
+
+void
+fsort(struct filelist *filelist, int nfiles, FILE *outfp, struct field *ftbl)
+{
+ RECHEADER **keylist;
+ RECHEADER **keypos, **keyp;
+ RECHEADER *buffer;
+ size_t bufsize = DEFBUFSIZE;
+ u_char *bufend;
+ int mfct = 0;
+ int c, nelem;
+ get_func_t get;
+ RECHEADER *crec;
+ RECHEADER *nbuffer;
+ FILE *fp, *tmp_fp;
+ int file_no;
+ int max_recs = DEBUG('m') ? 16 : MAXNUM;
+
+ buffer = allocrec(NULL, bufsize);
+ bufend = (u_char *)buffer + bufsize;
+ /* Allocate double length keymap for radix_sort */
+ keylist = malloc(2 * max_recs * sizeof(*keylist));
+ if (buffer == NULL || keylist == NULL)
+ err(2, "failed to malloc initial buffer or keylist");
+
+ if (SINGL_FLD)
+ /* Key and data are one! */
+ get = makeline;
+ else
+ /* Key (merged key fields) added before data */
+ get = makekey;
+
+ file_no = 0;
+ fp = fopen(filelist->names[0], "r");
+ if (fp == NULL)
+ err(2, "%s", filelist->names[0]);
+
+ /* Loop through reads of chunk of input files that get sorted
+ * and then merged together. */
+ for (;;) {
+ keypos = keylist;
+ nelem = 0;
+ crec = buffer;
+ makeline_copydown(crec);
+
+ /* Loop reading records */
+ for (;;) {
+ c = get(fp, crec, bufend, ftbl);
+ /* 'c' is 0, EOF or BUFFEND */
+ if (c == 0) {
+ /* Save start of key in input buffer */
+ *keypos++ = crec;
+ if (++nelem == max_recs) {
+ c = BUFFEND;
+ break;
+ }
+ crec = (RECHEADER *)(crec->data + SALIGN(crec->length));
+ continue;
+ }
+ if (c == EOF) {
+ /* try next file */
+ if (++file_no >= nfiles)
+ /* no more files */
+ break;
+ fp = fopen(filelist->names[file_no], "r");
+ if (fp == NULL)
+ err(2, "%s", filelist->names[file_no]);
+ continue;
+ }
+ if (nelem >= max_recs
+ || (bufsize >= MAXBUFSIZE && nelem > 8))
+ /* Need to sort and save this lot of data */
+ break;
+
+ /* c == BUFFEND, and we can process more data */
+ /* Allocate a larger buffer for this lot of data */
+ bufsize *= 2;
+ nbuffer = allocrec(buffer, bufsize);
+ if (!nbuffer) {
+ err(2, "failed to realloc buffer to %zu bytes",
+ bufsize);
+ }
+
+ /* patch up keylist[] */
+ for (keyp = &keypos[-1]; keyp >= keylist; keyp--)
+ *keyp = nbuffer + (*keyp - buffer);
+
+ crec = nbuffer + (crec - buffer);
+ buffer = nbuffer;
+ bufend = (u_char *)buffer + bufsize;
+ }
+
+ /* Sort this set of records */
+ radix_sort(keylist, keylist + max_recs, nelem);
+
+ if (c == EOF && mfct == 0) {
+ /* all the data is (sorted) in the buffer */
+ append(keylist, nelem, outfp,
+ DEBUG('k') ? putkeydump : putline);
+ break;
+ }
+
+ /* Save current data to a temporary file for a later merge */
+ if (nelem != 0) {
+ tmp_fp = ftmp();
+ append(keylist, nelem, tmp_fp, putrec);
+ save_for_merge(tmp_fp, geteasy, ftbl);
+ }
+ mfct = 1;
+
+ if (c == EOF) {
+ /* merge to output file */
+ merge_sort(outfp,
+ DEBUG('k') ? putkeydump : putline, ftbl);
+ break;
+ }
+ }
+
+ free(keylist);
+ keylist = NULL;
+ free(buffer);
+ buffer = NULL;
+}
diff --git a/usr.bin/sort/fsort.h b/usr.bin/sort/fsort.h
new file mode 100644
index 0000000..3d8ef4c
--- /dev/null
+++ b/usr.bin/sort/fsort.h
@@ -0,0 +1,78 @@
+/* $NetBSD: fsort.h,v 1.17 2009/09/26 21:16:55 dsl Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)fsort.h 8.1 (Berkeley) 6/6/93
+ */
+
+#define BUFSIZE (1<<20)
+#define MAXNUM 131072 /* low guess at average record count */
+#define BUFFEND (EOF-2)
+#define MAXFCT 1000
+#define DEFLLEN 65536
+
+/*
+ * Default (initial) and maximum size of record buffer for fsort().
+ * Note that no more than MAXNUM records are stored in the buffer,
+ * even if the buffer is not full yet.
+ */
+#define DEFBUFSIZE (1 << 20) /* 1MB */
+#define MAXBUFSIZE (8 << 20) /* 10 MB */
diff --git a/usr.bin/sort/init.c b/usr.bin/sort/init.c
new file mode 100644
index 0000000..ff1799b
--- /dev/null
+++ b/usr.bin/sort/init.c
@@ -0,0 +1,447 @@
+/* $NetBSD: init.c,v 1.29 2013/10/18 20:47:06 christos Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "sort.h"
+
+__RCSID("$NetBSD: init.c,v 1.29 2013/10/18 20:47:06 christos Exp $");
+
+#include <ctype.h>
+#include <string.h>
+
+static void insertcol(struct field *);
+static const char *setcolumn(const char *, struct field *);
+
+/*
+ * masks of ignored characters.
+ */
+static u_char dtable[NBINS], itable[NBINS];
+
+/*
+ * parsed key options
+ */
+struct coldesc *clist = NULL;
+int ncols = 0;
+
+/*
+ * clist (list of columns which correspond to one or more icol or tcol)
+ * is in increasing order of columns.
+ * Fields are kept in increasing order of fields.
+ */
+
+/*
+ * keep clist in order--inserts a column in a sorted array
+ */
+static void
+insertcol(struct field *field)
+{
+ int i;
+ struct coldesc *p;
+
+ /* Make space for new item */
+ p = realloc(clist, (ncols + 2) * sizeof(*clist));
+ if (!p)
+ err(1, "realloc");
+ clist = p;
+ memset(&clist[ncols], 0, sizeof(clist[ncols]));
+
+ for (i = 0; i < ncols; i++)
+ if (field->icol.num <= clist[i].num)
+ break;
+ if (field->icol.num != clist[i].num) {
+ memmove(clist+i+1, clist+i, sizeof(COLDESC)*(ncols-i));
+ clist[i].num = field->icol.num;
+ ncols++;
+ }
+ if (field->tcol.num && field->tcol.num != field->icol.num) {
+ for (i = 0; i < ncols; i++)
+ if (field->tcol.num <= clist[i].num)
+ break;
+ if (field->tcol.num != clist[i].num) {
+ memmove(clist+i+1, clist+i,sizeof(COLDESC)*(ncols-i));
+ clist[i].num = field->tcol.num;
+ ncols++;
+ }
+ }
+}
+
+/*
+ * matches fields with the appropriate columns--n^2 but who cares?
+ */
+void
+fldreset(struct field *fldtab)
+{
+ int i;
+
+ fldtab[0].tcol.p = clist + ncols - 1;
+ for (++fldtab; fldtab->icol.num; ++fldtab) {
+ for (i = 0; fldtab->icol.num != clist[i].num; i++)
+ ;
+ fldtab->icol.p = clist + i;
+ if (!fldtab->tcol.num)
+ continue;
+ for (i = 0; fldtab->tcol.num != clist[i].num; i++)
+ ;
+ fldtab->tcol.p = clist + i;
+ }
+}
+
+/*
+ * interprets a column in a -k field
+ */
+static const char *
+setcolumn(const char *pos, struct field *cur_fld)
+{
+ struct column *col;
+ char *npos;
+ int tmp;
+ col = cur_fld->icol.num ? (&cur_fld->tcol) : (&cur_fld->icol);
+ col->num = (int) strtol(pos, &npos, 10);
+ pos = npos;
+ if (col->num <= 0 && !(col->num == 0 && col == &(cur_fld->tcol)))
+ errx(2, "field numbers must be positive");
+ if (*pos == '.') {
+ if (!col->num)
+ errx(2, "cannot indent end of line");
+ ++pos;
+ col->indent = (int) strtol(pos, &npos, 10);
+ pos = npos;
+ if (&cur_fld->icol == col)
+ col->indent--;
+ if (col->indent < 0)
+ errx(2, "illegal offset");
+ }
+ for(; (tmp = optval(*pos, cur_fld->tcol.num)); pos++)
+ cur_fld->flags |= tmp;
+ if (cur_fld->icol.num == 0)
+ cur_fld->icol.num = 1;
+ return (pos);
+}
+
+int
+setfield(const char *pos, struct field *cur_fld, int gflag)
+{
+ cur_fld->mask = NULL;
+
+ pos = setcolumn(pos, cur_fld);
+ if (*pos == '\0') /* key extends to EOL. */
+ cur_fld->tcol.num = 0;
+ else {
+ if (*pos != ',')
+ errx(2, "illegal field descriptor");
+ setcolumn((++pos), cur_fld);
+ }
+ if (!cur_fld->flags)
+ cur_fld->flags = gflag;
+ if (REVERSE)
+ /* A local 'r' doesn't invert the global one */
+ cur_fld->flags &= ~R;
+
+ /* Assign appropriate mask table and weight table. */
+ cur_fld->weights = weight_tables[cur_fld->flags & (R | F)];
+ if (cur_fld->flags & I)
+ cur_fld->mask = itable;
+ else if (cur_fld->flags & D)
+ cur_fld->mask = dtable;
+
+ cur_fld->flags |= (gflag & (BI | BT));
+ if (!cur_fld->tcol.indent) /* BT has no meaning at end of field */
+ cur_fld->flags &= ~BT;
+
+ if (cur_fld->tcol.num
+ && !(!(cur_fld->flags & BI) && cur_fld->flags & BT)
+ && (cur_fld->tcol.num <= cur_fld->icol.num
+ /* indent if 0 -> end of field, i.e. okay */
+ && cur_fld->tcol.indent != 0
+ && cur_fld->tcol.indent < cur_fld->icol.indent))
+ errx(2, "fields out of order");
+
+ insertcol(cur_fld);
+ return (cur_fld->tcol.num);
+}
+
+int
+optval(int desc, int tcolflag)
+{
+ switch(desc) {
+ case 'b':
+ if (!tcolflag)
+ return BI;
+ else
+ return BT;
+ case 'd': return D;
+ case 'f': return F;
+ case 'i': return I;
+ case 'l': return L;
+ case 'n': return N;
+ case 'r': return R;
+ default: return 0;
+ }
+}
+
+/*
+ * Return true if the options found in ARG, according to the getopt
+ * spec in OPTS, require an additional argv word as an option
+ * argument.
+ */
+static int
+options_need_argument(const char *arg, const char *opts)
+{
+ size_t pos;
+ const char *s;
+
+ /*assert(arg[0] == '-');*/
+
+ pos = 1;
+ while (arg[pos]) {
+ s = strchr(opts, arg[pos]);
+ if (s == NULL) {
+ /* invalid option */
+ return 0;
+ }
+ if (s[1] == ':') {
+ /* option requires argument */
+ if (arg[pos+1] == '\0') {
+ /* no argument in this arg */
+ return 1;
+ }
+ else {
+ /* argument is in this arg; no more options */
+ return 0;
+ }
+ }
+ pos++;
+ }
+ return 0;
+}
+
+/*
+ * Replace historic +SPEC arguments with appropriate -kSPEC.
+ *
+ * The form can be either a single +SPEC or a pair +SPEC -SPEC.
+ * The following -SPEC is not recognized unless it follows
+ * immediately.
+ */
+void
+fixit(int *argc, char **argv, const char *opts)
+{
+ int i, j, sawplus;
+ char *vpos, *tpos, spec[20];
+ int col, indent;
+
+ sawplus = 0;
+ for (i = 1; i < *argc; i++) {
+ /*
+ * This loop must stop exactly where getopt will stop.
+ * Otherwise it turns e.g. "sort x +3" into "sort x
+ * -k4.1", which will croak if +3 was in fact really a
+ * file name. In order to do this reliably we need to
+ * be able to identify argv words that are option
+ * arguments.
+ */
+
+ if (!strcmp(argv[i], "--")) {
+ /* End of options; stop. */
+ break;
+ }
+
+ if (argv[i][0] == '+') {
+ /* +POS argument */
+ sawplus = 1;
+ } else if (argv[i][0] == '-' && sawplus &&
+ isdigit((unsigned char)argv[i][1])) {
+ /* -POS argument */
+ sawplus = 0;
+ } else if (argv[i][0] == '-') {
+ /* other option */
+ sawplus = 0;
+ if (options_need_argument(argv[i], opts)) {
+ /* skip over the argument */
+ i++;
+ }
+ continue;
+ } else {
+ /* not an option at all; stop */
+ sawplus = 0;
+ break;
+ }
+
+ /*
+ * At this point argv[i] is an old-style spec. The
+ * sawplus flag used by the above loop logic also
+ * tells us if it's a +SPEC or -SPEC.
+ */
+
+ /* parse spec */
+ tpos = argv[i]+1;
+ col = (int)strtol(tpos, &tpos, 10);
+ if (*tpos == '.') {
+ ++tpos;
+ indent = (int) strtol(tpos, &tpos, 10);
+ } else
+ indent = 0;
+ /* tpos now points to the optional flags */
+
+ /*
+ * In the traditional form, x.0 means beginning of line;
+ * in the new form, x.0 means end of line. Adjust the
+ * value of INDENT accordingly.
+ */
+ if (sawplus) {
+ /* +POS */
+ col += 1;
+ indent += 1;
+ } else {
+ /* -POS */
+ if (indent > 0)
+ col += 1;
+ }
+
+ /* make the new style spec */
+ (void)snprintf(spec, sizeof(spec), "%d.%d%s", col, indent,
+ tpos);
+
+ if (sawplus) {
+ /* Replace the +POS argument with new-style -kSPEC */
+ asprintf(&vpos, "-k%s", spec);
+ argv[i] = vpos;
+ } else {
+ /*
+ * Append the spec to the one from the
+ * preceding +POS argument, and remove the
+ * current argv element entirely.
+ */
+ asprintf(&vpos, "%s,%s", argv[i-1], spec);
+ free(argv[i-1]);
+ argv[i-1] = vpos;
+ for (j=i; j < *argc; j++)
+ argv[j] = argv[j+1];
+ *argc -= 1;
+ i--;
+ }
+ }
+}
+
+/*
+ * ascii, Rascii, Ftable, and RFtable map
+ *
+ * Sorting 'weight' tables.
+ * Convert 'ascii' characters into their sort order.
+ * The 'F' variants fold lower case to upper equivalent
+ * The 'R' variants are for reverse sorting.
+ *
+ * The record separator (REC_D) never needs a weight, this frees one
+ * byte value as an 'end of key' marker. This must be 0 for normal
+ * weight tables, and 0xff for reverse weight tables - and is used
+ * to terminate keys so that short keys sort before (after if reverse)
+ * longer keys.
+ *
+ * The field separator has a normal weight - although it cannot occur
+ * within a key unless it is the default (space+tab).
+ *
+ * All other bytes map to the appropriate value for the sort order.
+ * Numeric sorts don't need any tables, they are reversed by negation.
+ *
+ * Global reverse sorts are done by writing the sorted keys in reverse
+ * order - the sort itself is stil forwards.
+ * This means that weights are only ever used when generating keys, any
+ * sort of the original data bytes is always forwards and unweighted.
+ *
+ * Note: this is only good for ASCII sorting. For different LC 's,
+ * all bets are off.
+ *
+ * itable[] and dtable[] are the masks for -i (ignore non-printables)
+ * and -d (only sort blank and alphanumerics).
+ */
+void
+settables(void)
+{
+ int i;
+ int next_weight = 1;
+ int rev_weight = 254;
+
+ ascii[REC_D] = 0;
+ Rascii[REC_D] = 255;
+ Ftable[REC_D] = 0;
+ RFtable[REC_D] = 255;
+
+ for (i = 0; i < 256; i++) {
+ if (i == REC_D)
+ continue;
+ ascii[i] = next_weight;
+ Rascii[i] = rev_weight;
+ if (Ftable[i] == 0) {
+ Ftable[i] = next_weight;
+ RFtable[i] = rev_weight;
+ Ftable[tolower(i)] = next_weight;
+ RFtable[tolower(i)] = rev_weight;
+ }
+ next_weight++;
+ rev_weight--;
+
+ if (i == '\n' || isprint(i))
+ itable[i] = 1;
+
+ if (i == '\n' || i == '\t' || i == ' ' || isalnum(i))
+ dtable[i] = 1;
+ }
+}
diff --git a/usr.bin/sort/msort.c b/usr.bin/sort/msort.c
new file mode 100644
index 0000000..6cbcf90
--- /dev/null
+++ b/usr.bin/sort/msort.c
@@ -0,0 +1,431 @@
+/* $NetBSD: msort.c,v 1.31 2016/06/01 02:37:55 kre Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "sort.h"
+#include "fsort.h"
+
+__RCSID("$NetBSD: msort.c,v 1.31 2016/06/01 02:37:55 kre Exp $");
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+/* Subroutines using comparisons: merge sort and check order */
+#define DELETE (1)
+
+typedef struct mfile {
+ FILE *fp;
+ get_func_t get;
+ RECHEADER *rec;
+ u_char *end;
+} MFILE;
+
+static int cmp(RECHEADER *, RECHEADER *);
+static int insert(struct mfile **, struct mfile *, int, int);
+static void merge_sort_fstack(FILE *, put_func_t, struct field *);
+
+/*
+ * Number of files merge() can merge in one pass.
+ */
+#define MERGE_FNUM 16
+
+static struct mfile fstack[MERGE_FNUM];
+static struct mfile fstack_1[MERGE_FNUM];
+static struct mfile fstack_2[MERGE_FNUM];
+static int fstack_count, fstack_1_count, fstack_2_count;
+
+void
+save_for_merge(FILE *fp, get_func_t get, struct field *ftbl)
+{
+ FILE *mfp, *mfp1, *mfp2;
+
+ if (fstack_count == MERGE_FNUM) {
+ /* Must reduce the number of temporary files */
+ mfp = ftmp();
+ merge_sort_fstack(mfp, putrec, ftbl);
+ /* Save output in next layer */
+ if (fstack_1_count == MERGE_FNUM) {
+ mfp1 = ftmp();
+ memcpy(fstack, fstack_1, sizeof fstack);
+ merge_sort_fstack(mfp1, putrec, ftbl);
+ if (fstack_2_count == MERGE_FNUM) {
+ /* More than 4096 files! */
+ mfp2 = ftmp();
+ memcpy(fstack, fstack_2, sizeof fstack);
+ merge_sort_fstack(mfp2, putrec, ftbl);
+ fstack_2[0].fp = mfp2;
+ fstack_2_count = 1;
+ }
+ fstack_2[fstack_2_count].fp = mfp1;
+ fstack_2[fstack_2_count].get = geteasy;
+ fstack_2_count++;
+ fstack_1_count = 0;
+ }
+ fstack_1[fstack_1_count].fp = mfp;
+ fstack_1[fstack_1_count].get = geteasy;
+ fstack_1_count++;
+ fstack_count = 0;
+ }
+
+ fstack[fstack_count].fp = fp;
+ fstack[fstack_count++].get = get;
+}
+
+void
+fmerge(struct filelist *filelist, int nfiles, FILE *outfp, struct field *ftbl)
+{
+ get_func_t get = SINGL_FLD ? makeline : makekey;
+ FILE *fp;
+ int i;
+
+ for (i = 0; i < nfiles; i++) {
+ fp = fopen(filelist->names[i], "r");
+ if (fp == NULL)
+ err(2, "%s", filelist->names[i]);
+ save_for_merge(fp, get, ftbl);
+ }
+
+ merge_sort(outfp, putline, ftbl);
+}
+
+void
+merge_sort(FILE *outfp, put_func_t put, struct field *ftbl)
+{
+ int count = fstack_1_count + fstack_2_count;
+ FILE *mfp;
+ int i;
+
+ if (count == 0) {
+ /* All files in initial array */
+ merge_sort_fstack(outfp, put, ftbl);
+ return;
+ }
+
+ count += fstack_count;
+
+ /* Too many files for one merge sort */
+ for (;;) {
+ /* Sort latest 16 files */
+ i = count;
+ if (i > MERGE_FNUM)
+ i = MERGE_FNUM;
+ while (fstack_count > 0)
+ fstack[--i] = fstack[--fstack_count];
+ while (i > 0 && fstack_1_count > 0)
+ fstack[--i] = fstack_1[--fstack_1_count];
+ while (i > 0)
+ fstack[--i] = fstack_2[--fstack_2_count];
+ if (count <= MERGE_FNUM) {
+ /* Got all the data */
+ fstack_count = count;
+ merge_sort_fstack(outfp, put, ftbl);
+ return;
+ }
+ mfp = ftmp();
+ fstack_count = count > MERGE_FNUM ? MERGE_FNUM : count;
+ merge_sort_fstack(mfp, putrec, ftbl);
+ fstack[0].fp = mfp;
+ fstack[0].get = geteasy;
+ fstack_count = 1;
+ count -= MERGE_FNUM - 1;
+ }
+}
+
+static void
+merge_sort_fstack(FILE *outfp, put_func_t put, struct field *ftbl)
+{
+ struct mfile *flistb[MERGE_FNUM], **flist = flistb, *cfile;
+ RECHEADER *new_rec;
+ u_char *new_end;
+ void *tmp;
+ int c, i, nfiles;
+ size_t sz;
+
+ /* Read one record from each file (read again if a duplicate) */
+ for (nfiles = i = 0; i < fstack_count; i++) {
+ cfile = &fstack[i];
+ if (cfile->rec == NULL) {
+ cfile->rec = allocrec(NULL, DEFLLEN);
+ cfile->end = (u_char *)cfile->rec + DEFLLEN;
+ }
+ rewind(cfile->fp);
+
+ for (;;) {
+ c = cfile->get(cfile->fp, cfile->rec, cfile->end, ftbl);
+ if (c == EOF)
+ break;
+
+ if (c == BUFFEND) {
+ /* Double buffer size */
+ sz = (cfile->end - (u_char *)cfile->rec) * 2;
+ cfile->rec = allocrec(cfile->rec, sz);
+ cfile->end = (u_char *)cfile->rec + sz;
+ continue;
+ }
+
+ if (nfiles != 0) {
+ if (insert(flist, cfile, nfiles, !DELETE))
+ /* Duplicate removed */
+ continue;
+ } else
+ flist[0] = cfile;
+ nfiles++;
+ break;
+ }
+ }
+
+ if (nfiles == 0)
+ return;
+
+ /*
+ * We now loop reading a new record from the file with the
+ * 'sorted first' existing record.
+ * As each record is added, the 'first' record is written to the
+ * output file - maintaining one record from each file in the sorted
+ * list.
+ */
+ new_rec = allocrec(NULL, DEFLLEN);
+ new_end = (u_char *)new_rec + DEFLLEN;
+ for (;;) {
+ cfile = flist[0];
+ c = cfile->get(cfile->fp, new_rec, new_end, ftbl);
+ if (c == EOF) {
+ /* Write out last record from now-empty input */
+ put(cfile->rec, outfp);
+ if (--nfiles == 0)
+ break;
+ /* Replace from file with now-first sorted record. */
+ /* (Moving base 'flist' saves copying everything!) */
+ flist++;
+ continue;
+ }
+ if (c == BUFFEND) {
+ /* Buffer not large enough - double in size */
+ sz = (new_end - (u_char *)new_rec) * 2;
+ new_rec = allocrec(new_rec, sz);
+ new_end = (u_char *)new_rec +sz;
+ continue;
+ }
+
+ /* Swap in new buffer, saving old */
+ tmp = cfile->rec;
+ cfile->rec = new_rec;
+ new_rec = tmp;
+ tmp = cfile->end;
+ cfile->end = new_end;
+ new_end = tmp;
+
+ /* Add into sort, removing the original first entry */
+ c = insert(flist, cfile, nfiles, DELETE);
+ if (c != 0 || (UNIQUE && cfile == flist[0]
+ && cmp(new_rec, cfile->rec) == 0)) {
+ /* Was an unwanted duplicate, restore buffer */
+ tmp = cfile->rec;
+ cfile->rec = new_rec;
+ new_rec = tmp;
+ tmp = cfile->end;
+ cfile->end = new_end;
+ new_end = tmp;
+ continue;
+ }
+
+ /* Write out 'old' record */
+ put(new_rec, outfp);
+ }
+
+ free(new_rec);
+}
+
+/*
+ * if delete: inserts rec in flist, deletes flist[0];
+ * otherwise just inserts *rec in flist.
+ * Returns 1 if record is a duplicate to be ignored.
+ */
+static int
+insert(struct mfile **flist, struct mfile *rec, int ttop, int delete)
+{
+ int mid, top = ttop, bot = 0, cmpv = 1;
+
+ for (mid = top / 2; bot + 1 != top; mid = (bot + top) / 2) {
+ cmpv = cmp(rec->rec, flist[mid]->rec);
+ if (cmpv == 0 ) {
+ if (UNIQUE)
+ /* Duplicate key, read another record */
+ /* NB: This doesn't guarantee to keep any
+ * particular record. */
+ return 1;
+ /*
+ * Apply sort by input file order.
+ * We could truncate the sort is the fileno are
+ * adjacent - but that is all too hard!
+ * The fileno cannot be equal, since we only have one
+ * record from each file (+ flist[0] which never
+ * comes here).
+ */
+ cmpv = rec < flist[mid] ? -1 : 1;
+ if (REVERSE)
+ cmpv = -cmpv;
+ }
+ if (cmpv < 0)
+ top = mid;
+ else
+ bot = mid;
+ }
+
+ /* At this point we haven't yet compared against flist[0] */
+
+ if (delete) {
+ /* flist[0] is ourselves, only the caller knows the old data */
+ if (bot != 0) {
+ memmove(flist, flist + 1, bot * sizeof(MFILE *));
+ flist[bot] = rec;
+ }
+ return 0;
+ }
+
+ /* Inserting original set of records */
+
+ if (bot == 0 && cmpv != 0) {
+ /* Doesn't match flist[1], must compare with flist[0] */
+ cmpv = cmp(rec->rec, flist[0]->rec);
+ if (cmpv == 0 && UNIQUE)
+ return 1;
+ /* Add matching keys in file order (ie new is later) */
+ if (cmpv < 0)
+ bot = -1;
+ }
+ bot++;
+ memmove(flist + bot + 1, flist + bot, (ttop - bot) * sizeof(MFILE *));
+ flist[bot] = rec;
+ return 0;
+}
+
+/*
+ * check order on one file
+ */
+void
+order(struct filelist *filelist, struct field *ftbl, int quiet)
+{
+ get_func_t get = SINGL_FLD ? makeline : makekey;
+ RECHEADER *crec, *prec, *trec;
+ u_char *crec_end, *prec_end, *trec_end;
+ FILE *fp;
+ int c;
+
+ fp = fopen(filelist->names[0], "r");
+ if (fp == NULL)
+ err(2, "%s", filelist->names[0]);
+
+ crec = malloc(offsetof(RECHEADER, data[DEFLLEN]));
+ crec_end = crec->data + DEFLLEN;
+ prec = malloc(offsetof(RECHEADER, data[DEFLLEN]));
+ prec_end = prec->data + DEFLLEN;
+
+ /* XXX this does exit(0) for overlong lines */
+ if (get(fp, prec, prec_end, ftbl) != 0)
+ exit(0);
+ while (get(fp, crec, crec_end, ftbl) == 0) {
+ if (0 < (c = cmp(prec, crec))) {
+ if (quiet)
+ exit(1);
+ crec->data[crec->length-1] = 0;
+ errx(1, "found disorder: %s", crec->data+crec->offset);
+ }
+ if (UNIQUE && !c) {
+ if (quiet)
+ exit(1);
+ crec->data[crec->length-1] = 0;
+ errx(1, "found non-uniqueness: %s",
+ crec->data+crec->offset);
+ }
+ /*
+ * Swap pointers so that this record is on place pointed
+ * to by prec and new record is read to place pointed to by
+ * crec.
+ */
+ trec = prec;
+ prec = crec;
+ crec = trec;
+ trec_end = prec_end;
+ prec_end = crec_end;
+ crec_end = trec_end;
+ }
+ exit(0);
+}
+
+static int
+cmp(RECHEADER *rec1, RECHEADER *rec2)
+{
+ int len;
+ int r;
+
+ /* key is weights */
+ len = min(rec1->keylen, rec2->keylen);
+ r = memcmp(rec1->data, rec2->data, len);
+ if (r == 0)
+ r = rec1->keylen - rec2->keylen;
+ if (REVERSE)
+ r = -r;
+ return r;
+}
diff --git a/usr.bin/sort/pathnames.h b/usr.bin/sort/pathnames.h
new file mode 100644
index 0000000..534671e
--- /dev/null
+++ b/usr.bin/sort/pathnames.h
@@ -0,0 +1,66 @@
+/* $NetBSD: pathnames.h,v 1.6 2008/04/28 20:24:15 martin Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 6/6/93
+ */
+
+#define _PATH_STDIN "/dev/stdin"
diff --git a/usr.bin/sort/radix_sort.c b/usr.bin/sort/radix_sort.c
new file mode 100644
index 0000000..4ec5ff8
--- /dev/null
+++ b/usr.bin/sort/radix_sort.c
@@ -0,0 +1,217 @@
+/* $NetBSD: radix_sort.c,v 1.4 2009/09/19 16:18:00 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy and by Dan Bernstein at New York University,
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)radixsort.c 8.2 (Berkeley) 4/28/95";
+#else
+__RCSID("$NetBSD: radix_sort.c,v 1.4 2009/09/19 16:18:00 dsl Exp $");
+#endif
+#endif /* LIBC_SCCS and not lint */
+
+/*
+ * 'stable' radix sort initially from libc/stdlib/radixsort.c
+ */
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <util.h>
+#include "sort.h"
+
+typedef struct {
+ RECHEADER **sa; /* Base of saved area */
+ int sn; /* Number of entries */
+ int si; /* index into data for compare */
+} stack;
+
+static void simplesort(RECHEADER **, int, int);
+
+#define THRESHOLD 20 /* Divert to simplesort(). */
+
+#define empty(s) (s >= sp)
+#define pop(a, n, i) a = (--sp)->sa, n = sp->sn, i = sp->si
+#define push(a, n, i) sp->sa = a, sp->sn = n, (sp++)->si = i
+#define swap(a, b, t) t = a, a = b, b = t
+
+void
+radix_sort(RECHEADER **a, RECHEADER **ta, int n)
+{
+ u_int count[256], nc, bmin;
+ u_int c;
+ RECHEADER **ak, **tai, **lim;
+ RECHEADER *hdr;
+ int stack_size = 512;
+ stack *s, *sp, *sp0, *sp1, temp;
+ RECHEADER **top[256];
+ u_int *cp, bigc;
+ int data_index = 0;
+
+ if (n < THRESHOLD && !DEBUG('r')) {
+ simplesort(a, n, 0);
+ return;
+ }
+
+ s = emalloc(stack_size * sizeof *s);
+ memset(&count, 0, sizeof count);
+ /* Technically 'top' doesn't need zeroing */
+ memset(&top, 0, sizeof top);
+
+ sp = s;
+ push(a, n, data_index);
+ while (!empty(s)) {
+ pop(a, n, data_index);
+ if (n < THRESHOLD && !DEBUG('r')) {
+ simplesort(a, n, data_index);
+ continue;
+ }
+
+ /* Count number of times each 'next byte' occurs */
+ nc = 0;
+ bmin = 255;
+ lim = a + n;
+ for (ak = a, tai = ta; ak < lim; ak++) {
+ hdr = *ak;
+ if (data_index >= hdr->keylen) {
+ /* Short key, copy to start of output */
+ if (UNIQUE && a != sp->sa)
+ /* Stop duplicate being written out */
+ hdr->keylen = -1;
+ *a++ = hdr;
+ n--;
+ continue;
+ }
+ /* Save in temp buffer for distribute */
+ *tai++ = hdr;
+ c = hdr->data[data_index];
+ if (++count[c] == 1) {
+ if (c < bmin)
+ bmin = c;
+ nc++;
+ }
+ }
+ /*
+ * We need save the bounds for each 'next byte' that
+ * occurs more so we can sort each block.
+ */
+ if (sp + nc > s + stack_size) {
+ stack_size *= 2;
+ sp1 = erealloc(s, stack_size * sizeof *s);
+ sp = sp1 + (sp - s);
+ s = sp1;
+ }
+
+ /* Minor optimisation to do the largest set last */
+ sp0 = sp1 = sp;
+ bigc = 2;
+ /* Convert 'counts' positions, saving bounds for later sorts */
+ ak = a;
+ for (cp = count + bmin; nc > 0; cp++) {
+ while (*cp == 0)
+ cp++;
+ if ((c = *cp) > 1) {
+ if (c > bigc) {
+ bigc = c;
+ sp1 = sp;
+ }
+ push(ak, c, data_index+1);
+ }
+ ak += c;
+ top[cp-count] = ak;
+ *cp = 0; /* Reset count[]. */
+ nc--;
+ }
+ swap(*sp0, *sp1, temp);
+
+ for (ak = ta+n; --ak >= ta;) /* Deal to piles. */
+ *--top[(*ak)->data[data_index]] = *ak;
+ }
+
+ free(s);
+}
+
+/* insertion sort, short records are sorted before long ones */
+static void
+simplesort(RECHEADER **a, int n, int data_index)
+{
+ RECHEADER **ak, **ai;
+ RECHEADER *akh;
+ RECHEADER **lim = a + n;
+ const u_char *s, *t;
+ int s_len, t_len;
+ int i;
+ int r;
+
+ if (n <= 1)
+ return;
+
+ for (ak = a+1; ak < lim; ak++) {
+ akh = *ak;
+ s = akh->data;
+ s_len = akh->keylen;
+ for (ai = ak; ;) {
+ ai--;
+ t_len = (*ai)->keylen;
+ if (t_len != -1) {
+ t = (*ai)->data;
+ for (i = data_index; ; i++) {
+ if (i >= s_len || i >= t_len) {
+ r = s_len - t_len;
+ break;
+ }
+ r = s[i] - t[i];
+ if (r != 0)
+ break;
+ }
+ if (r >= 0) {
+ if (r == 0 && UNIQUE) {
+ /* Put record below existing */
+ ai[1] = ai[0];
+ /* Mark as duplicate - ignore */
+ akh->keylen = -1;
+ } else {
+ ai++;
+ }
+ break;
+ }
+ }
+ ai[1] = ai[0];
+ if (ai == a)
+ break;
+ }
+ ai[0] = akh;
+ }
+}
diff --git a/usr.bin/sort/sort.1 b/usr.bin/sort/sort.1
new file mode 100644
index 0000000..548aafd
--- /dev/null
+++ b/usr.bin/sort/sort.1
@@ -0,0 +1,525 @@
+.\" $NetBSD: sort.1,v 1.38 2017/07/03 21:34:21 wiz Exp $
+.\"
+.\" Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Ben Harris and Jaromir Dolecek.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)sort.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 1, 2016
+.Dt SORT 1
+.Os
+.Sh NAME
+.Nm sort
+.Nd sort or merge text files
+.Sh SYNOPSIS
+.Nm
+.Op Fl bdfHilmnrSsu
+.Oo
+.Fl k
+.Ar kstart Ns Op Li \&, Ns Ar kend
+.Oc
+.Op Fl o Ar output
+.Op Fl R Ar char
+.Op Fl T Ar dir
+.Op Fl t Ar char
+.Op Ar
+.Nm
+.Fl C Ns | Ns Fl c
+.Op Fl bdfilnru
+.Oo
+.Fl k
+.Ar kstart Ns Op Li \&, Ns Ar kend
+.Op Fl t Ar char
+.Oc
+.Op Fl R Ar char
+.Op Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility sorts text files by lines.
+Comparisons are based on one or more sort keys extracted
+from each line of input, and are performed lexicographically.
+By default, if keys are not given,
+.Nm
+regards each input line as a single field.
+.Pp
+The following options are available:
+.Bl -tag -width Fl
+.It Fl C
+Identical to
+.Fl c
+without the error messages in the case of unsorted input.
+.It Fl c
+Check that the single input file is sorted.
+If the file is not sorted,
+.Nm
+produces the appropriate error messages and exits with code 1; otherwise,
+.Nm
+returns 0.
+.Nm
+.Fl c
+produces no output.
+See also
+.Fl u .
+.It Fl H
+Ignored for compatibility with earlier versions of
+.Nm .
+.It Fl m
+Merge only; the input files are assumed to be pre-sorted.
+.It Fl o Ar output
+The argument given is the name of an
+.Ar output
+file to be used instead of the standard output.
+This file can be the same as one of the input files.
+.It Fl S
+Don't use stable sort.
+Default is to use stable sort.
+.It Fl s
+Use stable sort, keeps records with equal keys in their original order.
+This is the default.
+Provided for compatibility with other
+.Nm
+implementations only.
+.It Fl T Ar dir
+Use
+.Ar dir
+as the directory for temporary files.
+The default is the value specified in the environment variable
+.Ev TMPDIR or
+.Pa /tmp
+if
+.Ev TMPDIR
+is not defined.
+.It Fl u
+Unique: suppress all but one in each set of lines having equal keys.
+If used with the
+.Fl c
+option, check that there are no lines with duplicate keys.
+.El
+.Pp
+The following options,
+which should be given before any
+.Fl k
+options, override the default ordering rules.
+When ordering options appear independent of,
+and before, key field specifications,
+the requested field ordering rules are
+applied globally to all sort keys.
+When attached to a specific key (see
+.Fl k ) ,
+the ordering options override
+all global ordering options for that key.
+.Bl -tag -width Fl
+.It Fl d
+Only blank space and alphanumeric characters
+.\" according
+.\" to the current setting of LC_CTYPE
+are used
+in making comparisons.
+.It Fl f
+Considers all lowercase characters that have uppercase
+equivalents to be the same for purposes of comparison.
+.It Fl i
+Ignore all non-printable characters.
+.It Fl l
+Sort by the string length of the field, not by the field itself.
+.It Fl n
+An initial numeric string, consisting of optional blank space, optional
+plus or minus sign, and zero or more digits (including decimal point)
+.\" with
+.\" optional radix character and thousands
+.\" separator
+.\" (as defined in the current locale),
+is sorted by arithmetic value.
+(The
+.Fl n
+option no longer implies the
+.Fl b
+option.)
+.It Fl r
+Reverse the sense of comparisons.
+.El
+.Pp
+The treatment of field separators can be altered using these options:
+.Bl -tag -width Fl
+.It Fl b
+Ignores leading blank space when determining the start
+and end of a restricted sort key.
+A
+.Fl b
+option specified before the first
+.Fl k
+option applies globally to all
+.Fl k
+options.
+Otherwise, the
+.Fl b
+option can be attached independently to each
+.Ar field
+argument of the
+.Fl k
+option (see below).
+Note that the
+.Fl b
+option has no effect unless key fields are specified.
+.It Fl k Ar kstart Ns Op Li \&, Ns Ar kend
+Designates the starting position,
+.Ar kstart ,
+and optional ending position,
+.Ar kend ,
+of a key field.
+The
+.Fl k
+option replaces the obsolescent options
+.Cm \(pl Ns Ar pos1
+and
+.Fl Ns Ar pos2 .
+.It Fl R Ar char
+.Ar char
+is used as the record separator character.
+This should be used with discretion;
+.Fl R Aq Ar alphanumeric
+usually produces undesirable results.
+If char is not a single character, then it
+specifies the value of the desired record
+separator as an integer specified in any
+of the normal NNN, 0ooo, or 0xXXX ways,
+or as an octal value preceded by \e.
+Caution: do not attempt to specify Ctl-A
+as
+.Dq -R 1
+which will not do what was intended at all!
+The default record separator is newline.
+.It Fl t Ar char
+.Ar char
+is used as the field separator character.
+The initial
+.Ar char
+is not considered to be part of a field when determining
+key offsets (see below).
+Each occurrence of
+.Ar char
+is significant (for example,
+.Dq Ar charchar
+delimits an empty field).
+If
+.Fl t
+is not specified, the default field separator is a sequence of
+blank-space characters, and consecutive blank spaces do
+.Em not
+delimit an empty field; further, the initial blank space
+.Em is
+considered part of a field when determining key offsets.
+.El
+.Pp
+The following operands are available:
+.Bl -tag -width Ar
+.It Ar file
+The pathname of a file to be sorted, merged, or checked.
+If no
+.Ar file
+operands are specified, or if
+a
+.Ar file
+operand is
+.Fl ,
+the standard input is used.
+.El
+.Pp
+A field is defined as a minimal sequence of characters followed by a
+field separator or a newline character.
+By default, the first
+blank space of a sequence of blank spaces acts as the field separator.
+All blank spaces in a sequence of blank spaces are considered
+as part of the next field; for example, all blank spaces at
+the beginning of a line are considered to be part of the
+first field.
+.Pp
+Fields are specified
+by the
+.Fl k
+.Ar kstart Ns Op \&, Ns Ar kend
+argument.
+A missing
+.Ar kend
+argument defaults to the end of a line.
+.Pp
+The arguments
+.Ar kstart
+and
+.Ar kend
+have the form
+.Ar m Ns Li \&. Ns Ar n
+and can be followed by one or more of the letters
+.Cm b , d , f , i ,
+.Cm l , n ,
+and
+.Cm r ,
+which correspond to the options discussed above.
+A
+.Ar kstart
+position specified by
+.Ar m Ns Li \&. Ns Ar n
+.Pq Ar m , n No > 0
+is interpreted as the
+.Ar n Ns th
+character in the
+.Ar m Ns th
+field.
+A missing
+.Li \&. Ns Ar n
+in
+.Ar kstart
+means
+.Ql \&.1 ,
+indicating the first character of the
+.Ar m Ns th
+field; if the
+.Fl b
+option is in effect,
+.Ar n
+is counted from the first non-blank character in the
+.Ar m Ns th
+field;
+.Ar m Ns Li \&.1b
+refers to the first non-blank character in the
+.Ar m Ns th
+field.
+.Pp
+A
+.Ar kend
+position specified by
+.Ar m Ns Li \&. Ns Ar n
+is interpreted as
+the
+.Ar n Ns th
+character (including separators) of the
+.Ar m Ns th
+field.
+A missing
+.Li \&. Ns Ar n
+indicates the last character of the
+.Ar m Ns th
+field;
+.Ar m
+= \&0
+designates the end of a line.
+Thus the option
+.Fl k
+.Sm off
+.Xo
+.Ar v Li \&. Ar x Li \&,
+.Ar w Li \&. Ar y
+.Xc
+.Sm on
+is synonymous with the obsolescent option
+.Sm off
+.Cm \(pl Ar v-\&1 Li \&. Ar x-\&1
+.Fl Ar w-\&1 Li \&. Ar y ;
+.Sm on
+when
+.Ar y
+is omitted,
+.Fl k
+.Sm off
+.Ar v Li \&. Ar x Li \&, Ar w
+.Sm on
+is synonymous with
+.Sm off
+.Cm \(pl Ar v-\&1 Li \&. Ar x-\&1
+.Fl Ar w+1 Li \&.0 .
+.Sm on
+The obsolescent
+.Cm \(pl Ns Ar pos1
+.Fl Ns Ar pos2
+option is still supported, except for
+.Fl Ns Ar w Ns Li \&.0b ,
+which has no
+.Fl k
+equivalent.
+.Pp
+.Nm
+compares records by comparing the key fields selected by
+.Fl k
+arguments,
+from first given to last,
+until discovering a difference.
+If there are no
+.Fl k
+arguments, the whole record is treated as a single key.
+After exhausting the
+.Fl k
+arguments, if no difference has been found,
+then the result depends upon the
+.Fl u
+and
+.Fl S
+option settings.
+With
+.Fl u
+the records are considered identical, and one is supressed.
+Otherwise with
+.Fl s
+set (default) the records are left in their original order,
+or with
+.Fl S
+(posix mode) the whole record is considered as a tie breaker.
+.\"
+.\" If you fail to understand why it doesn't matter which order
+.\" the records are output when they are wholly identical, there
+.\" is nothing that this man page can say that wll help!
+.\"
+.Sh ENVIRONMENT
+If the following environment variable exists, it is used by
+.Nm .
+.Bl -tag -width Ev
+.It Ev TMPDIR
+.Nm
+uses the contents of the
+.Ev TMPDIR
+environment variable as the path in which to store
+temporary files.
+.El
+.Sh FILES
+.Bl -tag -width outputNUMBER+some -compact
+.It Pa /tmp/sort.*
+Default temporary files.
+.It Ar output Ns NUMBER
+Temporary file which is used for output if
+.Ar output
+already exists.
+Once sorting is finished, this file replaces
+.Ar output
+(via
+.Xr link 2
+and
+.Xr unlink 2 ) .
+.El
+.Sh EXIT STATUS
+Sort exits with one of the following values:
+.Bl -tag -width flag -compact
+.It 0
+Normal behavior.
+.It 1
+On disorder (or non-uniqueness) with the
+.Fl c
+(or
+.Fl C )
+option.
+.It 2
+An error occurred.
+.El
+.Sh SEE ALSO
+.Xr comm 1 ,
+.Xr join 1 ,
+.Xr uniq 1 ,
+.Xr qsort 3 ,
+.Xr radixsort 3
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v5 .
+This
+.Nm
+implementation appeared in
+.Bx 4.4
+and is used since
+.Nx 1.6 .
+.Sh BUGS
+Posix requires the locale's thousands separator be ignored in numbers.
+It may be faster to sort very large files in pieces and then explicitly
+merge them.
+.Sh NOTES
+This
+.Nm
+has no limits on input line length (other than imposed by available
+memory) or any restrictions on bytes allowed within lines.
+.Pp
+To protect data
+.Nm
+.Fl o
+calls
+.Xr link 2
+and
+.Xr unlink 2 ,
+and thus fails on protected directories.
+.Pp
+Input files should be text files.
+If file doesn't end with record separator (which is typically newline), the
+.Nm
+utility silently supplies one.
+.Pp
+The current
+.Nm
+uses lexicographic radix sorting, which requires
+that sort keys be kept in memory (as opposed to previous versions which used quick
+and merge sorts and did not.)
+Thus performance depends highly on efficient choice of sort keys, and the
+.Fl b
+option and the
+.Ar kend
+argument of the
+.Fl k
+option should be used whenever possible.
+Similarly,
+.Nm
+.Fl k1f
+is equivalent to
+.Nm
+.Fl f
+and may take twice as long.
diff --git a/usr.bin/sort/sort.c b/usr.bin/sort/sort.c
new file mode 100644
index 0000000..ee5ffdb
--- /dev/null
+++ b/usr.bin/sort/sort.c
@@ -0,0 +1,419 @@
+/* $NetBSD: sort.c,v 1.64 2017/01/10 21:13:45 christos Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+__RCSID("$NetBSD: sort.c,v 1.64 2017/01/10 21:13:45 christos Exp $");
+
+/* Sort sorts a file using an optional user-defined key.
+ * Sort uses radix sort for internal sorting, and allows
+ * a choice of merge sort and radix sort for external sorting.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/resource.h>
+
+#include <locale.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "sort.h"
+#include "fsort.h"
+#include "pathnames.h"
+
+int REC_D = '\n';
+u_char d_mask[NBINS]; /* flags for rec_d, field_d, <blank> */
+
+/*
+ * weight tables. Gweights is one of ascii, Rascii..
+ * modified to weight rec_d = 0 (or 255)
+ */
+u_char *const weight_tables[4] = { ascii, Rascii, Ftable, RFtable };
+u_char ascii[NBINS], Rascii[NBINS], RFtable[NBINS], Ftable[NBINS];
+
+int SINGL_FLD = 0, SEP_FLAG = 0, UNIQUE = 0;
+int REVERSE = 0;
+int posix_sort;
+
+unsigned int debug_flags = 0;
+
+static char toutpath[MAXPATHLEN];
+
+const char *tmpdir; /* where temporary files should be put */
+
+static void cleanup(void);
+static void onsignal(int);
+__dead static void usage(const char *);
+
+int
+main(int argc, char *argv[])
+{
+ int ch, i, stdinflag = 0;
+ char mode = 0;
+ char *outfile, *outpath = 0;
+ struct field *fldtab;
+ size_t fldtab_sz, fld_cnt;
+ struct filelist filelist;
+ int num_input_files;
+ FILE *outfp = NULL;
+ struct rlimit rl;
+ struct stat st;
+
+ setlocale(LC_ALL, "");
+
+ /* bump RLIMIT_NOFILE to maximum our hard limit allows */
+ if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
+ err(2, "getrlimit");
+ rl.rlim_cur = rl.rlim_max;
+ if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
+ err(2, "setrlimit");
+
+ d_mask[REC_D = '\n'] = REC_D_F;
+ d_mask['\t'] = d_mask[' '] = BLANK | FLD_D;
+
+ /* fldtab[0] is the global options. */
+ fldtab_sz = 3;
+ fld_cnt = 0;
+ fldtab = emalloc(fldtab_sz * sizeof(*fldtab));
+ memset(fldtab, 0, fldtab_sz * sizeof(*fldtab));
+
+#define SORT_OPTS "bcCdD:fHik:lmno:rR:sSt:T:u"
+
+ /* Convert "+field" args to -k format */
+ fixit(&argc, argv, SORT_OPTS);
+
+ if (!(tmpdir = getenv("TMPDIR")))
+ tmpdir = _PATH_TMP;
+
+ while ((ch = getopt(argc, argv, SORT_OPTS)) != -1) {
+ switch (ch) {
+ case 'b':
+ fldtab[0].flags |= BI | BT;
+ break;
+ case 'c': case 'C': case 'm':
+ if (mode)
+ usage("Incompatible operation modes");
+ mode = ch;
+ break;
+ case 'D': /* Debug flags */
+ for (i = 0; optarg[i]; i++)
+ debug_flags |= 1 << (optarg[i] & 31);
+ break;
+ case 'd': case 'f': case 'i': case 'n': case 'l':
+ fldtab[0].flags |= optval(ch, 0);
+ break;
+ case 'H':
+ /* -H was ; use merge sort for blocks of large files' */
+ /* That is now the default. */
+ break;
+ case 'k':
+ fldtab = erealloc(fldtab, (fldtab_sz + 1) * sizeof(*fldtab));
+ memset(&fldtab[fldtab_sz], 0, sizeof(fldtab[0]));
+ fldtab_sz++;
+
+ setfield(optarg, &fldtab[++fld_cnt], fldtab[0].flags);
+ break;
+ case 'o':
+ outpath = optarg;
+ break;
+ case 'r':
+ REVERSE = 1;
+ break;
+ case 'R':
+ if (REC_D != '\n')
+ usage("multiple record delimiters");
+ REC_D = *optarg;
+ if (optarg[1] != '\0') {
+ char *ep;
+ int t = 0;
+
+ if (optarg[0] == '\\')
+ optarg++, t = 8;
+ REC_D = (int)strtol(optarg, &ep, t);
+ if (*ep != '\0' || REC_D < 0 ||
+ REC_D >= (int)__arraycount(d_mask))
+ errx(2, "invalid record delimiter %s",
+ optarg);
+ }
+ if (REC_D == '\n')
+ break;
+ d_mask['\n'] = d_mask[' '];
+ d_mask[REC_D] = REC_D_F;
+ break;
+ case 's':
+ /*
+ * Nominally 'stable sort', keep lines with equal keys
+ * in input file order. (Default for NetBSD)
+ * (-s for GNU sort compatibility.)
+ */
+ posix_sort = 0;
+ break;
+ case 'S':
+ /*
+ * Reverse of -s!
+ * This needs to enforce a POSIX sort where records
+ * with equal keys are then sorted by the raw data.
+ * Currently not implemented!
+ * (using libc radixsort() v sradixsort() doesn't
+ * have the desired effect.)
+ */
+ posix_sort = 1;
+ break;
+ case 't':
+ if (SEP_FLAG)
+ usage("multiple field delimiters");
+ SEP_FLAG = 1;
+ d_mask[' '] &= ~FLD_D;
+ d_mask['\t'] &= ~FLD_D;
+ d_mask['\n'] &= ~FLD_D;
+ d_mask[(u_char)*optarg] |= FLD_D;
+ if (d_mask[(u_char)*optarg] & REC_D_F)
+ errx(2, "record/field delimiter clash");
+ break;
+ case 'T':
+ /* -T tmpdir */
+ tmpdir = optarg;
+ break;
+ case 'u':
+ UNIQUE = 1;
+ break;
+ case '?':
+ default:
+ usage(NULL);
+ }
+ }
+
+ if (UNIQUE)
+ /* Don't sort on raw record if keys match */
+ posix_sort = 0;
+
+ if ((mode == 'c' || mode == 'C') && argc > optind+1)
+ errx(2, "too many input files for -c option");
+ if (argc - 2 > optind && !strcmp(argv[argc-2], "-o")) {
+ outpath = argv[argc-1];
+ argc -= 2;
+ }
+ if (mode == 'm' && argc - optind > (MAXFCT - (16+1))*16)
+ errx(2, "too many input files for -m option");
+
+ for (i = optind; i < argc; i++) {
+ /* allow one occurrence of /dev/stdin */
+ if (!strcmp(argv[i], "-") || !strcmp(argv[i], _PATH_STDIN)) {
+ if (stdinflag)
+ warnx("ignoring extra \"%s\" in file list",
+ argv[i]);
+ else
+ stdinflag = 1;
+
+ /* change to /dev/stdin if '-' */
+ if (argv[i][0] == '-') {
+ static char path_stdin[] = _PATH_STDIN;
+ argv[i] = path_stdin;
+ }
+
+ } else if ((ch = access(argv[i], R_OK)))
+ err(2, "%s", argv[i]);
+ }
+
+ if (fldtab[1].icol.num == 0) {
+ /* No sort key specified */
+ if (fldtab[0].flags & (I|D|F|N|L)) {
+ /* Modified - generate a key that covers the line */
+ fldtab[0].flags &= ~(BI|BT);
+ setfield("1", &fldtab[++fld_cnt], fldtab->flags);
+ fldreset(fldtab);
+ } else {
+ /* Unmodified, just compare the line */
+ SINGL_FLD = 1;
+ fldtab[0].icol.num = 1;
+ }
+ } else {
+ fldreset(fldtab);
+ }
+
+ settables();
+
+ if (optind == argc) {
+ static const char * const names[] = { _PATH_STDIN, NULL };
+ filelist.names = names;
+ num_input_files = 1;
+ } else {
+ filelist.names = (const char * const *) &argv[optind];
+ num_input_files = argc - optind;
+ }
+
+ if (mode == 'c' || mode == 'C') {
+ order(&filelist, fldtab, mode == 'C');
+ /* NOT REACHED */
+ }
+
+ if (!outpath) {
+ toutpath[0] = '\0'; /* path not used in this case */
+ outfile = outpath = toutpath;
+ outfp = stdout;
+ } else if (lstat(outpath, &st) == 0
+ && !S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) {
+ /* output file exists and isn't character or block device */
+ struct sigaction act;
+ static const int sigtable[] = {SIGHUP, SIGINT, SIGPIPE,
+ SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF, 0};
+ int outfd;
+ errno = 0;
+ if (access(outpath, W_OK))
+ err(2, "%s", outpath);
+ (void)snprintf(toutpath, sizeof(toutpath), "%sXXXXXX",
+ outpath);
+ if ((outfd = mkstemp(toutpath)) == -1)
+ err(2, "Cannot create temporary file `%s'", toutpath);
+ (void)atexit(cleanup);
+ act.sa_handler = onsignal;
+ (void) sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_RESTART | SA_RESETHAND;
+ for (i = 0; sigtable[i]; ++i) /* always unlink toutpath */
+ sigaction(sigtable[i], &act, 0);
+ outfile = toutpath;
+ if ((outfp = fdopen(outfd, "w")) == NULL)
+ err(2, "Cannot open temporary file `%s'", toutpath);
+ } else {
+ outfile = outpath;
+
+ if ((outfp = fopen(outfile, "w")) == NULL)
+ err(2, "output file %s", outfile);
+ }
+
+ if (mode == 'm')
+ fmerge(&filelist, num_input_files, outfp, fldtab);
+ else
+ fsort(&filelist, num_input_files, outfp, fldtab);
+
+ if (outfile != outpath) {
+ if (access(outfile, F_OK))
+ err(2, "%s", outfile);
+
+ /*
+ * Copy file permissions bits of the original file.
+ * st is initialized above, when we create the
+ * temporary spool file.
+ */
+ if (lchmod(outfile, st.st_mode & ALLPERMS) != 0) {
+ err(2, "cannot chmod %s: output left in %s",
+ outpath, outfile);
+ }
+
+ (void)unlink(outpath);
+ if (link(outfile, outpath))
+ err(2, "cannot link %s: output left in %s",
+ outpath, outfile);
+ (void)unlink(outfile);
+ toutpath[0] = 0;
+ }
+ exit(0);
+}
+
+static void
+onsignal(int sig)
+{
+ cleanup();
+}
+
+static void
+cleanup(void)
+{
+ if (toutpath[0])
+ (void)unlink(toutpath);
+}
+
+static void
+usage(const char *msg)
+{
+ const char *pn = getprogname();
+
+ if (msg != NULL)
+ (void)fprintf(stderr, "%s: %s\n", getprogname(), msg);
+ (void)fprintf(stderr,
+ "usage: %s [-bdfHilmnrSsu] [-k kstart[,kend]] [-o output]"
+ " [-R char] [-T dir]\n", pn);
+ (void)fprintf(stderr,
+ " [-t char] [file ...]\n");
+ (void)fprintf(stderr,
+ " or: %s -C|-c [-bdfilnru] [-k kstart[,kend]] [-o output]"
+ " [-R char]\n", pn);
+ (void)fprintf(stderr,
+ " [-t char] [file]\n");
+ exit(2);
+}
+
+RECHEADER *
+allocrec(RECHEADER *rec, size_t size)
+{
+
+ return (erealloc(rec, size + sizeof(long) - 1));
+}
diff --git a/usr.bin/sort/sort.h b/usr.bin/sort/sort.h
new file mode 100644
index 0000000..d7a3144
--- /dev/null
+++ b/usr.bin/sort/sort.h
@@ -0,0 +1,201 @@
+/* $NetBSD: sort.h,v 1.36 2016/06/01 02:37:55 kre Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)sort.h 8.1 (Berkeley) 6/6/93
+ */
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define NBINS 256
+
+/* values for masks, weights, and other flags. */
+/* R and F get used to index weight_tables[] */
+#define R 0x01 /* Field is reversed */
+#define F 0x02 /* weight lower and upper case the same */
+#define I 0x04 /* mask out non-printable characters */
+#define D 0x08 /* sort alphanumeric characters only */
+#define N 0x10 /* Field is a number */
+#define BI 0x20 /* ignore blanks in icol */
+#define BT 0x40 /* ignore blanks in tcol */
+#define L 0x80 /* Sort by field length */
+
+/* masks for delimiters: blanks, fields, and termination. */
+#define BLANK 1 /* ' ', '\t'; '\n' if -R is invoked */
+#define FLD_D 2 /* ' ', '\t' default; from -t otherwise */
+#define REC_D_F 4 /* '\n' default; from -R otherwise */
+
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+#define FCLOSE(file) { \
+ if (EOF == fclose(file)) \
+ err(2, "%p", file); \
+}
+
+#define EWRITE(ptr, size, n, f, fmt) { \
+ if (!fwrite(ptr, size, n, f)) \
+ err(2, fmt); \
+}
+
+/* Records are limited to MAXBUFSIZE (8MB) and less if you want to sort
+ * in a sane way.
+ * Anyone who wants to sort data records longer than 2GB definitely needs a
+ * different program! */
+typedef unsigned int length_t;
+
+/* A record is a key/line pair starting at rec.data. It has a total length
+ * and an offset to the start of the line half of the pair.
+ */
+typedef struct recheader {
+ length_t length; /* total length of key and line */
+ length_t offset; /* to line */
+ int keylen; /* length of key */
+ u_char data[]; /* key then line */
+} RECHEADER;
+
+/* This is the column as seen by struct field. It is used by enterfield.
+ * They are matched with corresponding coldescs during initialization.
+ */
+struct column {
+ struct coldesc *p;
+ int num;
+ int indent;
+};
+
+/* a coldesc has a number and pointers to the beginning and end of the
+ * corresponding column in the current line. This is determined in enterkey.
+ */
+typedef struct coldesc {
+ u_char *start;
+ u_char *end;
+ int num;
+} COLDESC;
+
+/* A field has an initial and final column; an omitted final column
+ * implies the end of the line. Flags regulate omission of blanks and
+ * numerical sorts; mask determines which characters are ignored (from -i, -d);
+ * weights determines the sort weights of a character (from -f, -r).
+ *
+ * The first field contain the global flags etc.
+ * The list terminates when icol = 0.
+ */
+struct field {
+ struct column icol;
+ struct column tcol;
+ u_int flags;
+ u_char *mask;
+ u_char *weights;
+};
+
+struct filelist {
+ const char * const * names;
+};
+
+typedef int (*get_func_t)(FILE *, RECHEADER *, u_char *, struct field *);
+typedef void (*put_func_t)(const RECHEADER *, FILE *);
+
+extern u_char ascii[NBINS], Rascii[NBINS], Ftable[NBINS], RFtable[NBINS];
+extern u_char *const weight_tables[4]; /* ascii, Rascii, Ftable, RFtable */
+extern u_char d_mask[NBINS];
+extern int SINGL_FLD, SEP_FLAG, UNIQUE, REVERSE;
+extern int posix_sort;
+extern int REC_D;
+extern const char *tmpdir;
+extern struct coldesc *clist;
+extern int ncols;
+
+#define DEBUG(ch) (debug_flags & (1 << ((ch) & 31)))
+extern unsigned int debug_flags;
+
+RECHEADER *allocrec(RECHEADER *, size_t);
+void append(RECHEADER **, int, FILE *, void (*)(const RECHEADER *, FILE *));
+void concat(FILE *, FILE *);
+length_t enterkey(RECHEADER *, const u_char *, u_char *, size_t, struct field *);
+void fixit(int *, char **, const char *);
+void fldreset(struct field *);
+FILE *ftmp(void);
+void fmerge(struct filelist *, int, FILE *, struct field *);
+void save_for_merge(FILE *, get_func_t, struct field *);
+void merge_sort(FILE *, put_func_t, struct field *);
+void fsort(struct filelist *, int, FILE *, struct field *);
+int geteasy(FILE *, RECHEADER *, u_char *, struct field *);
+int makekey(FILE *, RECHEADER *, u_char *, struct field *);
+int makeline(FILE *, RECHEADER *, u_char *, struct field *);
+void makeline_copydown(RECHEADER *);
+int optval(int, int);
+__dead void order(struct filelist *, struct field *, int);
+void putline(const RECHEADER *, FILE *);
+void putrec(const RECHEADER *, FILE *);
+void putkeydump(const RECHEADER *, FILE *);
+void rd_append(int, int, int, FILE *, u_char *, u_char *);
+void radix_sort(RECHEADER **, RECHEADER **, int);
+int setfield(const char *, struct field *, int);
+void settables(void);
diff --git a/usr.bin/sort/tmp.c b/usr.bin/sort/tmp.c
new file mode 100644
index 0000000..155b3f5
--- /dev/null
+++ b/usr.bin/sort/tmp.c
@@ -0,0 +1,106 @@
+/* $NetBSD: tmp.c,v 1.16 2009/11/06 18:34:22 joerg Exp $ */
+
+/*-
+ * Copyright (c) 2000-2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ben Harris and Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+__RCSID("$NetBSD: tmp.c,v 1.16 2009/11/06 18:34:22 joerg Exp $");
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "sort.h"
+#include "pathnames.h"
+
+#define _NAME_TMP "sort.XXXXXXXX"
+
+FILE *
+ftmp(void)
+{
+ sigset_t set, oset;
+ FILE *fp;
+ int fd;
+ char path[MAXPATHLEN];
+
+ (void)snprintf(path, sizeof(path), "%s%s%s", tmpdir,
+ (tmpdir[strlen(tmpdir)-1] != '/') ? "/" : "", _NAME_TMP);
+
+ sigfillset(&set);
+ (void)sigprocmask(SIG_BLOCK, &set, &oset);
+ if ((fd = mkstemp(path)) < 0)
+ err(2, "ftmp: mkstemp(\"%s\")", path);
+ if (!(fp = fdopen(fd, "w+")))
+ err(2, "ftmp: fdopen(\"%s\")", path);
+ if (!DEBUG('t'))
+ (void)unlink(path);
+
+ (void)sigprocmask(SIG_SETMASK, &oset, NULL);
+ return (fp);
+}
diff --git a/usr.bin/split/split.1 b/usr.bin/split/split.1
new file mode 100644
index 0000000..a25e3c6
--- /dev/null
+++ b/usr.bin/split/split.1
@@ -0,0 +1,132 @@
+.\" $NetBSD: split.1,v 1.15 2007/05/31 01:35:35 jschauma Exp $
+.\"
+.\" Copyright (c) 1990, 1991, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)split.1 8.3 (Berkeley) 4/16/94
+.\"
+.Dd May 28, 2007
+.Dt SPLIT 1
+.Os
+.Sh NAME
+.Nm split
+.Nd split a file into pieces
+.Sh SYNOPSIS
+.Nm
+.Op Fl a Ar suffix_length
+.Oo
+.Fl b Ar byte_count Ns Oo Li k|m Oc |
+.Fl l Ar line_count
+.Fl n Ar chunk_count
+.Oc
+.Op Ar file Op Ar name
+.Sh DESCRIPTION
+The
+.Nm
+utility reads the given
+.Ar file
+and breaks it up into files of 1000 lines each.
+If
+.Ar file
+is a single dash or absent,
+.Nm
+reads from the standard input.
+.Ar file
+itself is not altered.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Use
+.Ar suffix_length
+letters to form the suffix of the file name.
+.It Fl b
+Create smaller files
+.Ar byte_count
+bytes in length.
+If
+.Ql k
+is appended to the number, the file is split into
+.Ar byte_count
+kilobyte pieces.
+If
+.Ql m
+is appended to the number, the file is split into
+.Ar byte_count
+megabyte pieces.
+.It Fl l
+Create smaller files
+.Ar line_count
+lines in length.
+.It Fl n
+Split file into
+.Ar chunk_count
+smaller files.
+.El
+.Pp
+If additional arguments are specified, the first is used as the name
+of the input file which is to be split.
+If a second additional argument is specified, it is used as a prefix
+for the names of the files into which the file is split.
+In this case, each file into which the file is split is named by the
+prefix followed by a lexically ordered suffix using
+.Ar suffix_length
+characters in the range
+.Dq Li a-z .
+If
+.Fl a
+is not specified, two letters are used as the suffix.
+.Pp
+If the
+.Ar name
+argument is not specified,
+.Ql x
+is used.
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.1-2001 .
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v6 .
+.Pp
+The
+.Fl a
+option was introduced in
+.Nx 2.0 .
+Before that, if
+.Ar name
+was not specified,
+.Nm
+would vary the first letter of the filename
+to increase the number of possible output files.
+The
+.Fl a
+option makes this unnecessary.
diff --git a/usr.bin/split/split.c b/usr.bin/split/split.c
new file mode 100644
index 0000000..e538da1
--- /dev/null
+++ b/usr.bin/split/split.c
@@ -0,0 +1,362 @@
+/* $NetBSD: split.c,v 1.27 2017/01/10 21:14:13 christos Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1987, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)split.c 8.3 (Berkeley) 4/25/94";
+#endif
+__RCSID("$NetBSD: split.c,v 1.27 2017/01/10 21:14:13 christos Exp $");
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define DEFLINE 1000 /* Default num lines per file. */
+
+static int file_open; /* If a file open. */
+static int ifd = STDIN_FILENO, ofd = -1; /* Input/output file descriptors. */
+static char *fname; /* File name prefix. */
+static size_t sfxlen = 2; /* suffix length. */
+
+static void newfile(void);
+static void split1(off_t, int) __dead;
+static void split2(off_t) __dead;
+static void split3(off_t) __dead;
+static void usage(void) __dead;
+static size_t bigwrite(int, void const *, size_t);
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ char *ep, *p;
+ char const *base;
+ off_t bytecnt = 0; /* Byte count to split on. */
+ off_t numlines = 0; /* Line count to split on. */
+ off_t chunks = 0; /* Number of chunks to split into. */
+
+ while ((ch = getopt(argc, argv, "0123456789b:l:a:n:")) != -1)
+ switch (ch) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ /*
+ * Undocumented kludge: split was originally designed
+ * to take a number after a dash.
+ */
+ if (numlines == 0) {
+ p = argv[optind - 1];
+ if (p[0] == '-' && p[1] == ch && !p[2])
+ p++;
+ else
+ p = argv[optind] + 1;
+ numlines = strtoull(p, &ep, 10);
+ if (numlines == 0 || *ep != '\0')
+ errx(1, "%s: illegal line count.", p);
+ }
+ break;
+ case 'b': /* Byte count. */
+ if (!isdigit((unsigned char)optarg[0]) ||
+ (bytecnt = strtoull(optarg, &ep, 10)) == 0 ||
+ (*ep != '\0' && *ep != 'k' && *ep != 'm'))
+ errx(1, "%s: illegal byte count.", optarg);
+ if (*ep == 'k')
+ bytecnt *= 1024;
+ else if (*ep == 'm')
+ bytecnt *= 1024 * 1024;
+ break;
+ case 'l': /* Line count. */
+ if (numlines != 0)
+ usage();
+ if (!isdigit((unsigned char)optarg[0]) ||
+ (numlines = strtoull(optarg, &ep, 10)) == 0 ||
+ *ep != '\0')
+ errx(1, "%s: illegal line count.", optarg);
+ break;
+ case 'a': /* Suffix length. */
+ if (!isdigit((unsigned char)optarg[0]) ||
+ (sfxlen = (size_t)strtoul(optarg, &ep, 10)) == 0 ||
+ *ep != '\0')
+ errx(1, "%s: illegal suffix length.", optarg);
+ break;
+ case 'n': /* Chunks. */
+ if (!isdigit((unsigned char)optarg[0]) ||
+ (chunks = (size_t)strtoul(optarg, &ep, 10)) == 0 ||
+ *ep != '\0')
+ errx(1, "%s: illegal number of chunks.", optarg);
+ break;
+ default:
+ usage();
+ }
+ argv += optind;
+ argc -= optind;
+
+ if (*argv != NULL) {
+ if (strcmp(*argv, "-") != 0 &&
+ (ifd = open(*argv, O_RDONLY, 0)) < 0)
+ err(1, "%s", *argv);
+ ++argv;
+ }
+
+
+ base = (*argv != NULL) ? *argv++ : "x";
+ if ((fname = malloc(strlen(base) + sfxlen + 1)) == NULL)
+ err(EXIT_FAILURE, NULL);
+ (void)strcpy(fname, base); /* File name prefix. */
+
+ if (*argv != NULL)
+ usage();
+
+ if (numlines == 0)
+ numlines = DEFLINE;
+ else if (bytecnt || chunks)
+ usage();
+
+ if (bytecnt && chunks)
+ usage();
+
+ if (bytecnt)
+ split1(bytecnt, 0);
+ else if (chunks)
+ split3(chunks);
+ else
+ split2(numlines);
+
+ return 0;
+}
+
+/*
+ * split1 --
+ * Split the input by bytes.
+ */
+static void
+split1(off_t bytecnt, int maxcnt)
+{
+ off_t bcnt;
+ ssize_t dist, len;
+ char *C;
+ char bfr[MAXBSIZE];
+ int nfiles;
+
+ nfiles = 0;
+
+ for (bcnt = 0;;)
+ switch (len = read(ifd, bfr, MAXBSIZE)) {
+ case 0:
+ exit(0);
+ /* NOTREACHED */
+ case -1:
+ err(1, "read");
+ /* NOTREACHED */
+ default:
+ if (!file_open) {
+ if (!maxcnt || (nfiles < maxcnt)) {
+ newfile();
+ nfiles++;
+ file_open = 1;
+ }
+ }
+ if (bcnt + len >= bytecnt) {
+ /* LINTED: bytecnt - bcnt <= len */
+ dist = bytecnt - bcnt;
+ if (bigwrite(ofd, bfr, dist) != (size_t)dist)
+ err(1, "write");
+ len -= dist;
+ for (C = bfr + dist; len >= bytecnt;
+ /* LINTED: bytecnt <= len */
+ len -= bytecnt, C += bytecnt) {
+ if (!maxcnt || (nfiles < maxcnt)) {
+ newfile();
+ nfiles++;
+ }
+ /* LINTED: as above */
+ if (bigwrite(ofd,
+ C, bytecnt) != (size_t)bytecnt)
+ err(1, "write");
+ }
+ if (len) {
+ if (!maxcnt || (nfiles < maxcnt)) {
+ newfile();
+ nfiles++;
+ }
+ /* LINTED: len >= 0 */
+ if (bigwrite(ofd, C, len) != (size_t)len)
+ err(1, "write");
+ } else
+ file_open = 0;
+ bcnt = len;
+ } else {
+ bcnt += len;
+ /* LINTED: len >= 0 */
+ if (bigwrite(ofd, bfr, len) != (size_t)len)
+ err(1, "write");
+ }
+ }
+}
+
+/*
+ * split2 --
+ * Split the input by lines.
+ */
+static void
+split2(off_t numlines)
+{
+ off_t lcnt;
+ size_t bcnt;
+ ssize_t len;
+ char *Ce, *Cs;
+ char bfr[MAXBSIZE];
+
+ for (lcnt = 0;;)
+ switch (len = read(ifd, bfr, MAXBSIZE)) {
+ case 0:
+ exit(0);
+ /* NOTREACHED */
+ case -1:
+ err(1, "read");
+ /* NOTREACHED */
+ default:
+ if (!file_open) {
+ newfile();
+ file_open = 1;
+ }
+ for (Cs = Ce = bfr; len--; Ce++)
+ if (*Ce == '\n' && ++lcnt == numlines) {
+ bcnt = Ce - Cs + 1;
+ if (bigwrite(ofd, Cs, bcnt) != (size_t)bcnt)
+ err(1, "write");
+ lcnt = 0;
+ Cs = Ce + 1;
+ if (len)
+ newfile();
+ else
+ file_open = 0;
+ }
+ if (Cs < Ce) {
+ bcnt = Ce - Cs;
+ if (bigwrite(ofd, Cs, bcnt) != (size_t)bcnt)
+ err(1, "write");
+ }
+ }
+}
+
+/*
+ * split3 --
+ * Split the input into specified number of chunks
+ */
+static void
+split3(off_t chunks)
+{
+ struct stat sb;
+
+ if (fstat(ifd, &sb) == -1) {
+ err(1, "stat");
+ /* NOTREACHED */
+ }
+
+ if (chunks > sb.st_size) {
+ errx(1, "can't split into more than %d files",
+ (int)sb.st_size);
+ /* NOTREACHED */
+ }
+
+ split1(sb.st_size/chunks, chunks);
+}
+
+/*
+ * newfile --
+ * Open a new output file.
+ */
+static void
+newfile(void)
+{
+ static int fnum;
+ static char *fpnt;
+ int quot, i;
+
+ if (ofd == -1) {
+ fpnt = fname + strlen(fname);
+ fpnt[sfxlen] = '\0';
+ } else if (close(ofd) != 0)
+ err(1, "%s", fname);
+
+ quot = fnum;
+ for (i = sfxlen - 1; i >= 0; i--) {
+ fpnt[i] = quot % 26 + 'a';
+ quot = quot / 26;
+ }
+ if (quot > 0)
+ errx(1, "too many files.");
+ ++fnum;
+ if ((ofd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE)) < 0)
+ err(1, "%s", fname);
+}
+
+static size_t
+bigwrite(int fd, const void *buf, size_t len)
+{
+ const char *ptr = buf;
+ size_t sofar = 0;
+ ssize_t w;
+
+ while (len != 0) {
+ if ((w = write(fd, ptr, len)) == -1)
+ return sofar;
+ len -= w;
+ ptr += w;
+ sofar += w;
+ }
+ return sofar;
+}
+
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+"usage: %s [-b byte_count] [-l line_count] [-n chunk_count] [-a suffix_length] "
+"[file [prefix]]\n", getprogname());
+ exit(1);
+}
diff --git a/usr.bin/tabs/tabs.1 b/usr.bin/tabs/tabs.1
new file mode 100644
index 0000000..e88b29a
--- /dev/null
+++ b/usr.bin/tabs/tabs.1
@@ -0,0 +1,166 @@
+.\" $NetBSD: tabs.1,v 1.6 2014/03/18 18:20:45 riastradh Exp $
+.\"
+.\" Copyright (c) 2008 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Roy Marples.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd April 5, 2012
+.Dt TABS 1
+.Os
+.Sh NAME
+.Nm tabs
+.Nd set terminal tabs
+.Sh SYNOPSIS
+.Nm
+.Op Fl n Ns | Ns Fl a Ns | Ns Fl a2 Ns | Ns Fl c Ns | Ns Fl c2 \
+Ns | Ns Fl c3 Ns | Ns Fl f Ns | Ns Fl p Ns | Ns Fl s Ns | Ns Fl u
+.Op +m Ns Op n
+.Op Fl T Ar type
+.Nm
+.Op Fl T Ar type
+.Op + Ns Op n
+.Ar n1 Ns Op , Ns Ar n2 , Ns Ar ...
+.Sh DESCRIPTION
+The
+.Nm
+utility displays a series of characters that first clears the hardware terminal
+tab settings and then initializes the tab stops at the specified positions
+and optionally adjusts the margin.
+.Pp
+The phrase "tab-stop position N" means that, from the start of a line of
+output, tabbing to position N shall cause the next character output to be in
+the (N+1)th column on that line.
+.Pp
+The following options are supported:
+.Bl -tag -width Fl
+.It Fl Ar n
+Specifies repetitive tab stops separated by a uniform number of columns,
+.Ar n ,
+where
+.Ar n
+is a single digit decimal number.
+The default usage of
+.Nm
+with no arguments is equivalent to
+.Nm
+.Fl 8 .
+When
+.Fl 0
+is used, the tab stops are cleared and no new ones set.
+.It Fl a
+Assembler, applicable to some mainframes.
+Equivalent to
+.Nm
+1,10,16,36,72 .
+.It Fl a2
+Assembler, applicable to some mainframes.
+Equivalent to
+.Nm
+1,10,16,40,72
+.It Fl c
+.Tn COBOL ,
+normal format.
+Equivalent to
+.Nm
+1,8,12,16,20,55
+.It Fl c2
+.Tn COBOL ,
+compact format (columns 1 to 6 omitted).
+Equivalent to
+.Nm
+1,6,10,14,49
+.It Fl c3
+.Tn COBOL ,
+compact format (columns 1 to 6 omitted), with more tabs than
+.Fl c2 .
+Equivalent to
+.Nm
+1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67
+.It Fl f
+.Tn FORTRAN .
+Equivalent to
+.Nm
+1,7,11,15,19,23
+.It Fl p
+.Tn PL/1 .
+Equivalent to
+.Nm
+1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61
+.It Fl s
+.Tn SNOBOL .
+Equivalent to
+.Nm
+1,10,55
+.It Fl T Ar type
+Indicates the type of terminal.
+.It Fl u
+Assembler, applicable to some mainframes.
+Equivalent to
+.Nm
+1,12,20,44
+.El
+.Sh ENVIRONMENT
+The
+.Ev COLUMNS
+and
+.Ev TERM
+environment variables affect the execution of
+.Nm
+as described in
+.Xr environ 7 .
+.Pp
+The
+.Fl T
+option overrides
+.Ev TERM .
+If neither
+.Ev TERM
+nor the
+.Fl T
+option are present,
+.Nm
+will fail.
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr expand 1 ,
+.Xr stty 1 ,
+.Xr tput 1 ,
+.Xr unexpand 1 ,
+.Xr terminfo 5
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.1 .
+.Sh HISTORY
+A
+.Nm
+utility first appeared in PWB UNIX.
+This implementation was introduced in
+.Nx 6.0 .
+.Sh AUTHORS
+.An Roy Marples Aq Mt roy@NetBSD.org
diff --git a/usr.bin/tabs/tabs.c b/usr.bin/tabs/tabs.c
new file mode 100644
index 0000000..9e017a0
--- /dev/null
+++ b/usr.bin/tabs/tabs.c
@@ -0,0 +1,232 @@
+/* $NetBSD: tabs.c,v 1.5 2019/02/01 08:29:04 mrg Exp $ */
+
+/*-
+ * Copyright (c) 2008 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Roy Marples.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 2008 \
+The NetBSD Foundation, inc. All rights reserved.");
+__RCSID("$NetBSD: tabs.c,v 1.5 2019/02/01 08:29:04 mrg Exp $");
+#endif /* not lint */
+
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <term.h>
+#include <unistd.h>
+
+#define NSTOPS 20
+
+struct tabspec {
+ const char *opt;
+ const char *spec;
+};
+static const struct tabspec tabspecs[] = {
+ {"a", "1,10,16,36,72"},
+ {"a2", "1,10,16,40,72"},
+ {"c", "1,8,12,16,20,55"},
+ {"c2", "1,6,10,14,49"},
+ {"c3", "1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67"},
+ {"f", "1,7,11,15,19,23"},
+ {"p", "1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61"},
+ {"s", "1,10,55"},
+ {"u", "1,12,20,44"}
+};
+static const size_t ntabspecs = sizeof(tabspecs) / sizeof(tabspecs[0]);
+
+__dead static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: tabs [-n|-a|-a2|-c|-c2|-c3|-f|-p|-s|-u] [+m[n]]"
+ " [-T type]\n"
+ " tabs [-T type] [+[n]] n1[,n2,...]\n");
+ exit(EXIT_FAILURE);
+ /* NOTREACHED */
+}
+
+int
+main(int argc, char **argv)
+{
+ char *term, *arg, *token, *end, *tabs = NULL, *p;
+ const char *cr, *spec = NULL;
+ int i, n, inc = 8, stops[NSTOPS], nstops, last, cols, margin = 0;
+ size_t j;
+ struct winsize ws;
+
+ term = getenv("TERM");
+ for (i = 1; i < argc; i++) {
+ if (argv[i][0] == '+') {
+ arg = argv[i] + 1;
+ if (arg[0] == 'm')
+ arg++;
+ if (arg[0] == '\0')
+ margin = 10;
+ else {
+ errno = 0;
+ margin = strtol(arg, &end, 10);
+ if (errno != 0 || *end != '\0' || margin < 0)
+ errx(EXIT_FAILURE,
+ "%s: invalid margin", arg);
+ }
+ continue;
+ }
+ if (argv[i][0] != '-') {
+ tabs = argv[i];
+ break;
+ }
+ arg = argv[i] + 1;
+ if (arg[0] == '\0')
+ usage();
+ if (arg[0] == '-' && arg[1] == '\0') {
+ if (argv[i + 1] != NULL)
+ tabs = argv[i + 1];
+ break;
+ }
+ if (arg[0] == 'T' && arg[1] == '\0') {
+ term = argv[++i];
+ if (term == NULL)
+ usage();
+ continue;
+ }
+ if (isdigit((int)arg[0])) {
+ if (arg[1] != '\0')
+ errx(EXIT_FAILURE,
+ "%s: invalid increament", arg);
+ inc = arg[0] - '0';
+ continue;
+ }
+ for (j = 0; j < ntabspecs; j++) {
+ if (arg[0] == tabspecs[j].opt[0] &&
+ arg[1] == tabspecs[j].opt[1]) {
+ spec = tabspecs[j].spec;
+ break;
+ }
+ }
+ if (j == ntabspecs)
+ usage();
+ }
+ if (tabs == NULL && spec != NULL)
+ tabs = strdup(spec);
+
+ if (tabs != NULL)
+ last = nstops = 0;
+ else
+ nstops = -1;
+ p = tabs;
+ while ((token = strsep(&p, ", ")) != NULL) {
+ if (*token == '\0')
+ continue;
+ if (nstops >= NSTOPS)
+ errx(EXIT_FAILURE,
+ "too many tab stops (max %d)", NSTOPS);
+ errno = 0;
+ n = strtol(token, &end, 10);
+ if (errno != 0 || *end != '\0' || n <= 0)
+ errx(EXIT_FAILURE, "%s: invalid tab stop", token);
+ if (*token == '+') {
+ if (nstops == 0)
+ errx(EXIT_FAILURE,
+ "first tab stop may not be relative");
+ n += last;
+ }
+ if (last > n)
+ errx(EXIT_FAILURE, "tab stops may not go backwards");
+ last = stops[nstops++] = n;
+ }
+
+ if (term == NULL)
+ errx(EXIT_FAILURE, "no value for $TERM and -T not given");
+ if (setupterm(term, STDOUT_FILENO, NULL) != 0)
+ err(EXIT_FAILURE, "setupterm:");
+ cr = carriage_return;
+ if (cr == NULL)
+ cr = "\r";
+ if (clear_all_tabs == NULL)
+ errx(EXIT_FAILURE, "terminal cannot clear tabs");
+ if (set_tab == NULL)
+ errx(EXIT_FAILURE, "terminal cannot set tabs");
+
+ /* Clear existing tabs */
+ putp(cr);
+ putp(clear_all_tabs);
+ putp(cr);
+
+ if (set_lr_margin != NULL) {
+ printf("%*s", margin, "");
+ putp(set_lr_margin);
+ } else if (margin != 0)
+ warnx("terminal cannot set left margin");
+
+ if (nstops >= 0) {
+ printf("%*s", stops[0] - 1, "");
+ putp(set_tab);
+ for (i = 1; i < nstops; i++) {
+ printf("%*s", stops[i] - stops[i - 1], "");
+ putp(set_tab);
+ }
+ } else if (inc > 0) {
+ cols = 0;
+ term = getenv("COLUMNS");
+ if (term != NULL) {
+ errno = 0;
+ cols = strtol(term, &end, 10);
+ if (errno != 0 || *end != '\0' || cols < 0)
+ cols = 0;
+ }
+ if (cols == 0) {
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0)
+ cols = ws.ws_col;
+ else {
+ cols = tigetnum("cols");
+ if (cols == 0) {
+ cols = 80;
+ warnx("terminal does not specify number"
+ "columns; defaulting to %d",
+ cols);
+ }
+ }
+ }
+ for (i = 0; i < cols / inc; i++) {
+ printf("%*s", inc, "");
+ putp(set_tab);
+ }
+ }
+ putp(cr);
+
+ exit(EXIT_SUCCESS);
+ /* NOTREACHED */
+}
diff --git a/usr.bin/tail/extern.h b/usr.bin/tail/extern.h
new file mode 100644
index 0000000..24507b7
--- /dev/null
+++ b/usr.bin/tail/extern.h
@@ -0,0 +1,52 @@
+/* $NetBSD: extern.h,v 1.10 2011/09/03 09:02:20 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ */
+
+#define WR(p, size) \
+ if (write(STDOUT_FILENO, p, size) != size) \
+ oerr();
+
+enum STYLE { NOTSET = 0, FBYTES, FLINES, RBYTES, RLINES, REVERSE };
+
+void forward(FILE *, enum STYLE, off_t, struct stat *);
+void reverse(FILE *, enum STYLE, off_t, struct stat *);
+
+int displaybytes(FILE *, off_t);
+int displaylines(FILE *, off_t);
+
+void xerr(int fatal, const char *fmt, ...) __printflike(2, 3);
+void xerrx(int fatal, const char *fmt, ...) __printflike(2, 3);
+void ierr(void);
+void oerr(void);
+
+extern int fflag, rflag, rval;
+extern const char *fname;
diff --git a/usr.bin/tail/forward.c b/usr.bin/tail/forward.c
new file mode 100644
index 0000000..e57e8b5
--- /dev/null
+++ b/usr.bin/tail/forward.c
@@ -0,0 +1,355 @@
+/* $NetBSD: forward.c,v 1.33 2015/10/09 17:51:26 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)forward.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: forward.c,v 1.33 2015/10/09 17:51:26 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/event.h>
+
+#include <limits.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "extern.h"
+
+static int rlines(FILE *, off_t, struct stat *);
+
+/* defines for inner loop actions */
+#define USE_SLEEP 0
+#define USE_KQUEUE 1
+#define ADD_EVENTS 2
+
+/*
+ * forward -- display the file, from an offset, forward.
+ *
+ * There are eight separate cases for this -- regular and non-regular
+ * files, by bytes or lines and from the beginning or end of the file.
+ *
+ * FBYTES byte offset from the beginning of the file
+ * REG seek
+ * NOREG read, counting bytes
+ *
+ * FLINES line offset from the beginning of the file
+ * REG read, counting lines
+ * NOREG read, counting lines
+ *
+ * RBYTES byte offset from the end of the file
+ * REG seek
+ * NOREG cyclically read characters into a wrap-around buffer
+ *
+ * RLINES
+ * REG mmap the file and step back until reach the correct offset.
+ * NOREG cyclically read lines into a wrap-around array of buffers
+ */
+void
+forward(FILE *fp, enum STYLE style, off_t off, struct stat *sbp)
+{
+ int ch, n;
+ int kq=-1, action=USE_SLEEP;
+ struct stat statbuf;
+ struct kevent ev[2];
+
+ switch(style) {
+ case FBYTES:
+ if (off == 0)
+ break;
+ if (S_ISREG(sbp->st_mode)) {
+ if (sbp->st_size < off)
+ off = sbp->st_size;
+ if (fseeko(fp, off, SEEK_SET) == -1) {
+ ierr();
+ return;
+ }
+ } else while (off--)
+ if ((ch = getc(fp)) == EOF) {
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ break;
+ }
+ break;
+ case FLINES:
+ if (off == 0)
+ break;
+ for (;;) {
+ if ((ch = getc(fp)) == EOF) {
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ break;
+ }
+ if (ch == '\n' && !--off)
+ break;
+ }
+ break;
+ case RBYTES:
+ if (S_ISREG(sbp->st_mode)) {
+ if (sbp->st_size >= off &&
+ fseeko(fp, -off, SEEK_END) == -1) {
+ ierr();
+ return;
+ }
+ } else if (off == 0) {
+ while (getc(fp) != EOF);
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ } else {
+ if (displaybytes(fp, off))
+ return;
+ }
+ break;
+ case RLINES:
+ if (S_ISREG(sbp->st_mode)) {
+ if (!off) {
+ if (fseek(fp, 0L, SEEK_END) == -1) {
+ ierr();
+ return;
+ }
+ } else {
+ if (rlines(fp, off, sbp))
+ return;
+ }
+ } else if (off == 0) {
+ while (getc(fp) != EOF);
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ } else {
+ if (displaylines(fp, off))
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (fflag) {
+ kq = kqueue();
+ if (kq < 0)
+ xerr(1, "kqueue");
+ action = ADD_EVENTS;
+ }
+
+ for (;;) {
+ while ((ch = getc(fp)) != EOF) {
+ if (putchar(ch) == EOF)
+ oerr();
+ }
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ (void)fflush(stdout);
+ if (!fflag)
+ break;
+
+ clearerr(fp);
+
+ switch (action) {
+ case ADD_EVENTS:
+ n = 0;
+
+ memset(ev, 0, sizeof(ev));
+ if (fflag == 2 && fp != stdin) {
+ EV_SET(&ev[n], fileno(fp), EVFILT_VNODE,
+ EV_ADD | EV_ENABLE | EV_CLEAR,
+ NOTE_DELETE | NOTE_RENAME, 0, 0);
+ n++;
+ }
+ EV_SET(&ev[n], fileno(fp), EVFILT_READ,
+ EV_ADD | EV_ENABLE, 0, 0, 0);
+ n++;
+
+ if (kevent(kq, ev, n, NULL, 0, NULL) == -1) {
+ close(kq);
+ kq = -1;
+ action = USE_SLEEP;
+ } else {
+ action = USE_KQUEUE;
+ }
+ break;
+
+ case USE_KQUEUE:
+ if (kevent(kq, NULL, 0, ev, 1, NULL) == -1)
+ xerr(1, "kevent");
+
+ if (ev[0].filter == EVFILT_VNODE) {
+ /* file was rotated, wait until it reappears */
+ action = USE_SLEEP;
+ } else if (ev[0].data < 0) {
+ /* file shrank, reposition to end */
+ if (fseek(fp, 0L, SEEK_END) == -1) {
+ ierr();
+ return;
+ }
+ }
+ break;
+
+ case USE_SLEEP:
+ /*
+ * We pause for one second after displaying any data
+ * that has accumulated since we read the file.
+ */
+ (void) sleep(1);
+
+ if (fflag == 2 && fp != stdin &&
+ stat(fname, &statbuf) != -1) {
+ if (statbuf.st_ino != sbp->st_ino ||
+ statbuf.st_dev != sbp->st_dev ||
+ statbuf.st_rdev != sbp->st_rdev ||
+ statbuf.st_nlink == 0) {
+ fp = freopen(fname, "r", fp);
+ if (fp == NULL) {
+ ierr();
+ goto out;
+ }
+ *sbp = statbuf;
+ if (kq != -1)
+ action = ADD_EVENTS;
+ } else if (kq != -1)
+ action = USE_KQUEUE;
+ }
+ break;
+ }
+ }
+out:
+ if (fflag && kq != -1)
+ close(kq);
+}
+
+/*
+ * rlines -- display the last offset lines of the file.
+ *
+ * Non-zero return means than a (non-fatal) error occurred.
+ */
+static int
+rlines(FILE *fp, off_t off, struct stat *sbp)
+{
+ off_t file_size;
+ off_t file_remaining;
+ char *p = NULL;
+ char *start = NULL;
+ off_t mmap_size;
+ off_t mmap_offset;
+ off_t mmap_remaining = 0;
+
+#define MMAP_MAXSIZE (10 * 1024 * 1024)
+
+ if (!(file_size = sbp->st_size))
+ return 0;
+ file_remaining = file_size;
+
+ if (file_remaining > MMAP_MAXSIZE) {
+ mmap_size = MMAP_MAXSIZE;
+ mmap_offset = file_remaining - MMAP_MAXSIZE;
+ } else {
+ mmap_size = file_remaining;
+ mmap_offset = 0;
+ }
+
+ while (off) {
+ start = mmap(NULL, (size_t)mmap_size, PROT_READ,
+ MAP_FILE|MAP_SHARED, fileno(fp), mmap_offset);
+ if (start == MAP_FAILED) {
+ xerr(0, "%s", fname);
+ return 1;
+ }
+
+ mmap_remaining = mmap_size;
+ /* Last char is special, ignore whether newline or not. */
+ for (p = start + mmap_remaining - 1 ; --mmap_remaining ; )
+ if (*--p == '\n' && !--off) {
+ ++p;
+ break;
+ }
+
+ file_remaining -= mmap_size - mmap_remaining;
+
+ if (off == 0)
+ break;
+
+ if (file_remaining == 0)
+ break;
+
+ if (munmap(start, mmap_size)) {
+ xerr(0, "%s", fname);
+ return 1;
+ }
+
+ if (mmap_offset >= MMAP_MAXSIZE) {
+ mmap_offset -= MMAP_MAXSIZE;
+ } else {
+ mmap_offset = 0;
+ mmap_size = file_remaining;
+ }
+ }
+
+ /*
+ * Output the (perhaps partial) data in this mmap'd block.
+ */
+ WR(p, mmap_size - mmap_remaining);
+ file_remaining += mmap_size - mmap_remaining;
+ if (munmap(start, mmap_size)) {
+ xerr(0, "%s", fname);
+ return 1;
+ }
+
+ /*
+ * Set the file pointer to reflect the length displayed.
+ * This will cause the caller to redisplay the data if/when
+ * needed.
+ */
+ if (fseeko(fp, file_remaining, SEEK_SET) == -1) {
+ ierr();
+ return 1;
+ }
+ return 0;
+}
diff --git a/usr.bin/tail/misc.c b/usr.bin/tail/misc.c
new file mode 100644
index 0000000..0af8f68
--- /dev/null
+++ b/usr.bin/tail/misc.c
@@ -0,0 +1,89 @@
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)misc.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: misc.c,v 1.7 2011/09/03 09:02:20 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+
+#include "extern.h"
+
+void
+ierr(void)
+{
+ xerr(0, "%s", fname);
+}
+
+void
+oerr(void)
+{
+ xerr(1, "stdout");
+}
+
+void
+xerr(int fatal, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vwarn(fmt, ap);
+ va_end(ap);
+ if (fatal)
+ exit(1);
+ rval = 1;
+}
+
+void
+xerrx(int fatal, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vwarnx(fmt, ap);
+ va_end(ap);
+ if (fatal)
+ exit(1);
+ rval = 1;
+}
diff --git a/usr.bin/tail/read.c b/usr.bin/tail/read.c
new file mode 100644
index 0000000..4e9374d
--- /dev/null
+++ b/usr.bin/tail/read.c
@@ -0,0 +1,210 @@
+/* $NetBSD: read.c,v 1.17 2011/09/03 10:59:10 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)read.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: read.c,v 1.17 2011/09/03 10:59:10 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "extern.h"
+
+/*
+ * displaybytes -- read bytes to an offset from the end and display.
+ *
+ * This is the function that reads to a byte offset from the end of the input,
+ * storing the data in a wrap-around buffer which is then displayed. If the
+ * rflag is set, the data is displayed in lines in reverse order, and this
+ * routine has the usual nastiness of trying to find the newlines. Otherwise,
+ * it is displayed from the character closest to the beginning of the input to
+ * the end.
+ *
+ * Non-zero return means than a (non-fatal) error occurred.
+ */
+int
+displaybytes(FILE *fp, off_t off)
+{
+ int ch, len, tlen;
+ char *ep, *p, *t;
+ int wrap;
+ char *sp;
+
+ if ((sp = p = malloc(off)) == NULL)
+ xerr(1, "malloc");
+
+ for (wrap = 0, ep = p + off; (ch = getc(fp)) != EOF;) {
+ *p = ch;
+ if (++p == ep) {
+ wrap = 1;
+ p = sp;
+ }
+ }
+ if (ferror(fp)) {
+ ierr();
+ return 1;
+ }
+
+ if (rflag) {
+ for (t = p - 1, len = 0; t >= sp; --t, ++len)
+ if (*t == '\n' && len) {
+ WR(t + 1, len);
+ len = 0;
+ }
+ if (wrap) {
+ tlen = len;
+ for (t = ep - 1, len = 0; t >= p; --t, ++len)
+ if (*t == '\n') {
+ if (len) {
+ WR(t + 1, len);
+ len = 0;
+ }
+ if (tlen) {
+ WR(sp, tlen);
+ tlen = 0;
+ }
+ }
+ if (len)
+ WR(t + 1, len);
+ if (tlen)
+ WR(sp, tlen);
+ }
+ } else {
+ if (wrap && (len = ep - p))
+ WR(p, len);
+ if ((len = p - sp) != 0)
+ WR(sp, len);
+ }
+ return 0;
+}
+
+/*
+ * displaylines -- read lines to an offset from the end and display.
+ *
+ * This is the function that reads to a line offset from the end of the input,
+ * storing the data in an array of buffers which is then displayed. If the
+ * rflag is set, the data is displayed in lines in reverse order, and this
+ * routine has the usual nastiness of trying to find the newlines. Otherwise,
+ * it is displayed from the line closest to the beginning of the input to
+ * the end.
+ *
+ * Non-zero return means than a (non-fatal) error occurred.
+ */
+int
+displaylines(FILE *fp, off_t off)
+{
+ struct {
+ int blen;
+ int len;
+ char *l;
+ } *lines;
+ int ch;
+ char *p;
+ int blen, cnt, recno, wrap;
+ char *sp, *n;
+
+ p = NULL;
+ if ((lines = malloc(off * sizeof(*lines))) == NULL)
+ xerr(1, "malloc");
+
+ memset(lines, 0, sizeof(*lines) * off);
+
+ sp = NULL;
+ blen = cnt = recno = wrap = 0;
+
+ while ((ch = getc(fp)) != EOF) {
+ if (++cnt > blen) {
+ if ((n = realloc(sp, blen + 1024)) == NULL)
+ xerr(1, "realloc");
+ sp = n;
+ blen += 1024;
+ p = sp + cnt - 1;
+ }
+ *p++ = ch;
+ if (ch == '\n') {
+ if (lines[recno].blen < cnt) {
+ if ((n = realloc(lines[recno].l,
+ cnt + 256)) == NULL)
+ xerr(1, "realloc");
+ lines[recno].l = n;
+ lines[recno].blen = cnt + 256;
+ }
+ memmove(lines[recno].l, sp, lines[recno].len = cnt);
+ cnt = 0;
+ p = sp;
+ if (++recno == off) {
+ wrap = 1;
+ recno = 0;
+ }
+ }
+ }
+ if (ferror(fp)) {
+ free(lines);
+ ierr();
+ return 1;
+ }
+ if (cnt) {
+ lines[recno].l = sp;
+ lines[recno].len = cnt;
+ if (++recno == off) {
+ wrap = 1;
+ recno = 0;
+ }
+ }
+
+ if (rflag) {
+ for (cnt = recno - 1; cnt >= 0; --cnt)
+ WR(lines[cnt].l, lines[cnt].len);
+ if (wrap)
+ for (cnt = off - 1; cnt >= recno; --cnt)
+ WR(lines[cnt].l, lines[cnt].len);
+ } else {
+ if (wrap)
+ for (cnt = recno; cnt < off; ++cnt)
+ WR(lines[cnt].l, lines[cnt].len);
+ for (cnt = 0; cnt < recno; ++cnt)
+ WR(lines[cnt].l, lines[cnt].len);
+ }
+ free(lines);
+ return 0;
+}
diff --git a/usr.bin/tail/reverse.c b/usr.bin/tail/reverse.c
new file mode 100644
index 0000000..bb8bcd9
--- /dev/null
+++ b/usr.bin/tail/reverse.c
@@ -0,0 +1,269 @@
+/* $NetBSD: reverse.c,v 1.23 2011/09/03 10:59:11 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)reverse.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: reverse.c,v 1.23 2011/09/03 10:59:11 christos Exp $");
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <limits.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "extern.h"
+
+static void r_buf(FILE *);
+static void r_reg(FILE *, enum STYLE, off_t, struct stat *);
+
+/*
+ * reverse -- display input in reverse order by line.
+ *
+ * There are six separate cases for this -- regular and non-regular
+ * files by bytes, lines or the whole file.
+ *
+ * BYTES display N bytes
+ * REG mmap the file and display the lines
+ * NOREG cyclically read characters into a wrap-around buffer
+ *
+ * LINES display N lines
+ * REG mmap the file and display the lines
+ * NOREG cyclically read lines into a wrap-around array of buffers
+ *
+ * FILE display the entire file
+ * REG mmap the file and display the lines
+ * NOREG cyclically read input into a linked list of buffers
+ */
+void
+reverse(FILE *fp, enum STYLE style, off_t off, struct stat *sbp)
+{
+ if (style != REVERSE && off == 0)
+ return;
+
+ if (S_ISREG(sbp->st_mode))
+ r_reg(fp, style, off, sbp);
+ else
+ switch(style) {
+ case FBYTES:
+ case RBYTES:
+ (void)displaybytes(fp, off);
+ break;
+ case FLINES:
+ case RLINES:
+ (void)displaylines(fp, off);
+ break;
+ case REVERSE:
+ r_buf(fp);
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * r_reg -- display a regular file in reverse order by line.
+ */
+static void
+r_reg(FILE *fp, enum STYLE style, off_t off, struct stat *sbp)
+{
+ off_t size;
+ int llen;
+ char *p;
+ char *start;
+
+ if (!(size = sbp->st_size))
+ return;
+
+ if ((uint64_t)size > SIZE_T_MAX) {
+ /* XXX: need a cleaner way to check this on amd64 */
+ errno = EFBIG;
+ xerr(0, "%s", fname);
+ return;
+ }
+
+ if ((start = mmap(NULL, (size_t)size, PROT_READ,
+ MAP_FILE|MAP_SHARED, fileno(fp), (off_t)0)) == MAP_FAILED) {
+ xerr(0, "%s", fname);
+ return;
+ }
+ p = start + size - 1;
+
+ if (style == RBYTES && off < size)
+ size = off;
+
+ /* Last char is special, ignore whether newline or not. */
+ for (llen = 1; --size; ++llen)
+ if (*--p == '\n') {
+ WR(p + 1, llen);
+ llen = 0;
+ if (style == RLINES && !--off) {
+ ++p;
+ break;
+ }
+ }
+ if (llen)
+ WR(p, llen);
+ if (munmap(start, (size_t)sbp->st_size))
+ xerr(0, "%s", fname);
+}
+
+typedef struct bf {
+ struct bf *next;
+ struct bf *prev;
+ int len;
+ char *l;
+} BF;
+
+/*
+ * r_buf -- display a non-regular file in reverse order by line.
+ *
+ * This is the function that saves the entire input, storing the data in a
+ * doubly linked list of buffers and then displays them in reverse order.
+ * It has the usual nastiness of trying to find the newlines, as there's no
+ * guarantee that a newline occurs anywhere in the file, let alone in any
+ * particular buffer. If we run out of memory, input is discarded (and the
+ * user warned).
+ */
+static void
+r_buf(FILE *fp)
+{
+ BF *mark, *tl, *tr;
+ int ch, len, llen;
+ char *p;
+ off_t enomem;
+
+#define BSZ (128 * 1024)
+ tl = NULL;
+ for (mark = NULL, enomem = 0;;) {
+ /*
+ * Allocate a new block and link it into place in a doubly
+ * linked list. If out of memory, toss the LRU block and
+ * keep going.
+ */
+ if (enomem) {
+ if (!mark) {
+ errno = ENOMEM;
+ xerr(1, NULL);
+ }
+ tl = tl->next;
+ enomem += tl->len;
+ } else if ((tl = malloc(sizeof(*tl))) == NULL ||
+ (tl->l = malloc(BSZ)) == NULL) {
+ if (tl)
+ free(tl);
+ if (!mark) {
+ errno = ENOMEM;
+ xerr(1, NULL);
+ }
+ tl = mark;
+ enomem += tl->len;
+ } else if (mark) {
+ tl->next = mark;
+ tl->prev = mark->prev;
+ mark->prev->next = tl;
+ mark->prev = tl;
+ } else {
+ mark = tl;
+ mark->next = mark->prev = mark;
+ }
+
+ /* Fill the block with input data. */
+ ch = 0;
+ for (p = tl->l, len = 0;
+ len < BSZ && (ch = getc(fp)) != EOF; ++len)
+ *p++ = ch;
+
+ /*
+ * If no input data for this block and we tossed some data,
+ * recover it.
+ */
+ if (!len) {
+ if (enomem)
+ enomem -= tl->len;
+ tl = tl->prev;
+ break;
+ }
+
+ tl->len = len;
+ if (ch == EOF)
+ break;
+ }
+
+ if (enomem) {
+ xerrx(0, "Warning: %lld bytes discarded", (long long)enomem);
+ }
+
+ /*
+ * Step through the blocks in the reverse order read. The last char
+ * is special, ignore whether newline or not.
+ */
+ for (mark = tl;;) {
+ for (p = tl->l + (len = tl->len) - 1, llen = 0; len--;
+ --p, ++llen)
+ if (*p == '\n') {
+ if (llen) {
+ WR(p + 1, llen);
+ llen = 0;
+ }
+ if (tl == mark)
+ continue;
+ for (tr = tl->next; tr->len; tr = tr->next) {
+ WR(tr->l, tr->len);
+ tr->len = 0;
+ if (tr == mark)
+ break;
+ }
+ }
+ tl->len = llen;
+ if ((tl = tl->prev) == mark)
+ break;
+ }
+ tl = tl->next;
+ if (tl->len) {
+ WR(tl->l, tl->len);
+ tl->len = 0;
+ }
+ while ((tl = tl->next)->len) {
+ WR(tl->l, tl->len);
+ tl->len = 0;
+ }
+}
diff --git a/usr.bin/tail/tac.1 b/usr.bin/tail/tac.1
new file mode 100644
index 0000000..d0dd1e8
--- /dev/null
+++ b/usr.bin/tail/tac.1
@@ -0,0 +1,56 @@
+.\" $NetBSD: tac.1,v 1.2 2017/10/01 22:35:23 kre Exp $
+.\"
+.\" Copyright (c) 2017 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Maya Rashish.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in
+.\" the documentation and/or other materials provided with the
+.\" distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+.\" COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+.\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd October 1, 2017
+.Dt TAC 1
+.Os
+.Sh NAME
+.Nm tac
+.Nd display file in reverse
+.Sh SYNOPSIS
+.Nm
+.Op Ar file ...
+.Sh DESCRIPTION
+.Nm
+.Pq cat backwards
+outputs the contents of each of each of the specified files,
+or the standard input if no files are specified,
+in reverse line order to the standard output.
+.Sh EXIT STATUS
+.Ex -std tac
+.Sh COMPATIBILITY
+The
+.Nm
+utility doesn't support any of the options of GNU tac.
+.Sh SEE ALSO
+.Xr cat 1 ,
+.Xr tail 1
diff --git a/usr.bin/tail/tail.1 b/usr.bin/tail/tail.1
new file mode 100644
index 0000000..1a68f88
--- /dev/null
+++ b/usr.bin/tail/tail.1
@@ -0,0 +1,202 @@
+.\" $NetBSD: tail.1,v 1.20 2017/10/15 03:57:47 pgoyette Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tail.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd October 1, 2017
+.Dt TAIL 1
+.Os
+.Sh NAME
+.Nm tail
+.Nd display the last part of a file
+.Sh SYNOPSIS
+.Nm
+.Fl qv
+.Oo
+.Fl f |
+.Fl F |
+.Fl r
+.Oc
+.Oo
+.Fl b Ar number |
+.Fl c Ar number |
+.Fl n Ar number
+.Oc
+.Op Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility displays the contents of
+.Ar file
+or, by default, its standard input, to the standard output.
+.Pp
+The display begins at a byte, line or 512-byte block location in the
+input.
+Numbers having a leading plus (``+'') sign are relative to the beginning
+of the input, for example,
+.Dq -c +2
+starts the display at the second
+byte of the input.
+Numbers having a leading minus (``-'') sign or no explicit sign are
+relative to the end of the input, for example,
+.Dq -n 2
+displays the last two lines of the input.
+The default starting location is
+.Dq -n 10 ,
+or the last 10 lines of the input.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl b Ar number
+The location is
+.Ar number
+512-byte blocks.
+.It Fl c Ar number
+The location is
+.Ar number
+bytes.
+.It Fl f
+The
+.Fl f
+option causes
+.Nm
+not to stop when end of file is reached, but rather to wait for additional
+data to be appended to the input.
+The
+.Fl f
+option is ignored if there are no file arguments and the standard
+input is a pipe or a FIFO.
+.It Fl F
+The
+.Fl F
+option is the same as the
+.Fl f
+option, except that every five seconds
+.Nm
+will check to see if the file named on the command line has been
+shortened or moved (it is considered moved if the inode or device
+number changes) and, if so, it will close
+the current file, open the filename given, print out the entire
+contents, and continue to wait for more data to be appended.
+This option is used to follow log files though rotation by
+.Xr newsyslog 8
+or similar programs.
+.It Fl n Ar number
+The location is
+.Ar number
+lines.
+.It Fl q
+Do not prepend a header for each file, even if multiple files
+are specified.
+.It Fl r
+The
+.Fl r
+option causes the input to be displayed in reverse order, by line.
+Additionally, this option changes the meaning of the
+.Fl b ,
+.Fl c
+and
+.Fl n
+options.
+When the
+.Fl r
+option is specified, these options specify the number of bytes, lines
+or 512-byte blocks to display, instead of the bytes, lines or blocks
+from the beginning or end of the input from which to begin the display.
+The default for the
+.Fl r
+option is to display all of the input.
+.It Fl v
+Prepend each file with a header.
+.El
+.Pp
+If more than a single file is specified, or the
+.Fl v
+option is used, each file is preceded by a
+header consisting of the string
+.Dq ==> XXX \*[Le]=
+where
+.Dq XXX
+is the name of the file.
+The
+.Fl q
+flag disables the printing of the header in all cases.
+.Sh EXIT STATUS
+.Ex -std tail
+.Sh SEE ALSO
+.Xr cat 1 ,
+.Xr head 1 ,
+.Xr sed 1
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be a superset of the
+.St -p1003.2-92
+specification.
+In particular, the
+.Fl b ,
+.Fl r
+and
+.Fl F
+options are extensions to that standard.
+.Pp
+The historic command line syntax of
+.Nm
+is supported by this implementation.
+The only difference between this implementation and historic versions
+of
+.Nm ,
+once the command line syntax translation has been done, is that the
+.Fl b ,
+.Fl c
+and
+.Fl n
+options modify the
+.Fl r
+option, i.e., ``-r -c 4'' displays the last 4 characters of the last line
+of the input, while the historic tail (using the historic syntax ``-4cr'')
+would ignore the
+.Fl c
+option and display the last 4 lines of the input.
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v7 .
+.Sh BUGS
+When using the
+.Fl F
+option,
+.Nm
+will not detect a file truncation if, between the truncation
+and the next check of the file size, data written to the file make
+it larger than the last known file size.
diff --git a/usr.bin/tail/tail.c b/usr.bin/tail/tail.c
new file mode 100644
index 0000000..dff9559
--- /dev/null
+++ b/usr.bin/tail/tail.c
@@ -0,0 +1,323 @@
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)tail.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: tail.c,v 1.20 2018/03/06 03:33:26 eadler Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "extern.h"
+
+int fflag, rflag, rval;
+const char *fname;
+
+static void obsolete(char **);
+static void usage(void) __dead;
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ FILE *fp;
+ off_t off;
+ enum STYLE style;
+ int ch, first;
+ char *p;
+ int qflag = 0;
+ int vflag = 0;
+
+ setprogname(argv[0]);
+ off = 0;
+ /*
+ * Tail's options are weird. First, -n10 is the same as -n-10, not
+ * -n+10. Second, the number options are 1 based and not offsets,
+ * so -n+1 is the first line, and -c-1 is the last byte. Third, the
+ * number options for the -r option specify the number of things that
+ * get displayed, not the starting point in the file. The one major
+ * incompatibility in this version as compared to historical versions
+ * is that the 'r' option couldn't be modified by the -lbc options,
+ * i.e., it was always done in lines. This version treats -rc as a
+ * number of characters in reverse order. Finally, the default for
+ * -r is the entire file, not 10 lines.
+ */
+#define ARG(units, forward, backward) { \
+ if (style) \
+ usage(); \
+ off = strtoll(optarg, &p, 10) * (units); \
+ if (*p) \
+ xerrx(1, "illegal offset -- %s", optarg); \
+ switch(optarg[0]) { \
+ case '+': \
+ if (off) \
+ off -= (units); \
+ style = (forward); \
+ break; \
+ case '-': \
+ off = -off; \
+ /* FALLTHROUGH */ \
+ default: \
+ style = (backward); \
+ break; \
+ } \
+}
+
+ obsolete(argv);
+ style = NOTSET;
+ if (strcmp(getprogname(), "tac") == 0) {
+ qflag = 1;
+ vflag = 0;
+ rflag = 1;
+ argc -= 1;
+ argv += 1;
+ } else { /* tail */
+ while ((ch = getopt(argc, argv, "Fb:c:fn:rqv")) != -1)
+ switch(ch) {
+ case 'F':
+ fflag = 2;
+ break;
+ case 'b':
+ ARG(512, FBYTES, RBYTES);
+ break;
+ case 'c':
+ ARG(1, FBYTES, RBYTES);
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'n':
+ ARG(1, FLINES, RLINES);
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 'q':
+ qflag = 1;
+ vflag = 0;
+ break;
+ case 'v':
+ qflag = 0;
+ vflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+ }
+
+ if (fflag && argc > 1)
+ xerrx(1,
+ "-f and -F options only appropriate for a single file");
+
+ /*
+ * If displaying in reverse, don't permit follow option, and convert
+ * style values.
+ */
+ if (rflag) {
+ if (fflag)
+ usage();
+ if (style == FBYTES)
+ style = RBYTES;
+ else if (style == FLINES)
+ style = RLINES;
+ }
+
+ /*
+ * If style not specified, the default is the whole file for -r, and
+ * the last 10 lines if not -r.
+ */
+ if (style == NOTSET) {
+ if (rflag) {
+ off = 0;
+ style = REVERSE;
+ } else {
+ off = 10;
+ style = RLINES;
+ }
+ }
+ if (*argv)
+ for (first = 1; (fname = *argv++) != NULL;) {
+ if ((fp = fopen(fname, "r")) == NULL ||
+ fstat(fileno(fp), &sb)) {
+ ierr();
+ continue;
+ }
+ if (vflag || (qflag == 0 && argc > 1)) {
+ (void)printf("%s==> %s <==\n",
+ first ? "" : "\n", fname);
+ first = 0;
+ (void)fflush(stdout);
+ }
+
+ if (rflag)
+ reverse(fp, style, off, &sb);
+ else
+ forward(fp, style, off, &sb);
+ (void)fclose(fp);
+ }
+ else {
+ fname = "stdin";
+
+ if (fstat(fileno(stdin), &sb)) {
+ ierr();
+ exit(1);
+ }
+
+ /*
+ * Determine if input is a pipe. 4.4BSD will set the SOCKET
+ * bit in the st_mode field for pipes. Fix this then.
+ */
+ if (lseek(fileno(stdin), (off_t)0, SEEK_CUR) == -1 &&
+ errno == ESPIPE) {
+ errno = 0;
+ fflag = 0; /* POSIX.2 requires this. */
+ }
+
+ if (rflag)
+ reverse(stdin, style, off, &sb);
+ else
+ forward(stdin, style, off, &sb);
+ }
+ exit(rval);
+}
+
+/*
+ * Convert the obsolete argument form into something that getopt can handle.
+ * This means that anything of the form [+-][0-9][0-9]*[lbc][fr] that isn't
+ * the option argument for a -b, -c or -n option gets converted.
+ */
+static void
+obsolete(char *argv[])
+{
+ char *ap, *p, *t;
+ size_t len;
+ char *start;
+
+ while ((ap = *++argv) != NULL) {
+ /* Return if "--" or not an option of any form. */
+ if (ap[0] != '-') {
+ if (ap[0] != '+')
+ return;
+ } else if (ap[1] == '-')
+ return;
+
+ switch (*++ap) {
+ /* Old-style option. */
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+
+ /* Malloc space for dash, new option and argument. */
+ len = strlen(*argv);
+ if ((start = p = malloc(len + 3)) == NULL)
+ xerr(1, "malloc");
+ *p++ = '-';
+
+ /*
+ * Go to the end of the option argument. Save off any
+ * trailing options (-3lf) and translate any trailing
+ * output style characters.
+ */
+ t = *argv + len - 1;
+ if (*t == 'f' || *t == 'r') {
+ *p++ = *t;
+ *t-- = '\0';
+ }
+ switch(*t) {
+ case 'b':
+ *p++ = 'b';
+ *t = '\0';
+ break;
+ case 'c':
+ *p++ = 'c';
+ *t = '\0';
+ break;
+ case 'l':
+ *t = '\0';
+ /* FALLTHROUGH */
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ *p++ = 'n';
+ break;
+ default:
+ xerrx(1, "illegal option -- %s", *argv);
+ }
+ *p++ = *argv[0];
+ (void)strcpy(p, ap);
+ *argv = start;
+ continue;
+
+ /*
+ * Options w/ arguments, skip the argument and continue
+ * with the next option.
+ */
+ case 'b':
+ case 'c':
+ case 'n':
+ if (!ap[1])
+ ++argv;
+ /* FALLTHROUGH */
+ /* Options w/o arguments, continue with the next option. */
+ case 'f':
+ case 'r':
+ continue;
+
+ /* Illegal option, return and let getopt handle it. */
+ default:
+ return;
+ }
+ }
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "Usage: %s [-qv] [-f | -F | -r] [-b # | -c # | -n #] [file ...]\n",
+ getprogname());
+ exit(1);
+}
diff --git a/usr.bin/talk/ctl.c b/usr.bin/talk/ctl.c
new file mode 100644
index 0000000..ef6f924
--- /dev/null
+++ b/usr.bin/talk/ctl.c
@@ -0,0 +1,125 @@
+/* $NetBSD: ctl.c,v 1.10 2012/03/20 20:34:59 matt Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)ctl.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: ctl.c,v 1.10 2012/03/20 20:34:59 matt Exp $");
+#endif /* not lint */
+
+/*
+ * This file handles haggling with the various talk daemons to
+ * get a socket to talk to. sockt is opened and connected in
+ * the progress
+ */
+
+#include "talk.h"
+#include <arpa/inet.h>
+#include "talk_ctl.h"
+
+struct sockaddr_in daemon_addr = { sizeof(daemon_addr), AF_INET, 0, {0}, {0} };
+struct sockaddr_in ctl_addr = { sizeof(ctl_addr), AF_INET, 0, {0}, {0} };
+struct sockaddr_in my_addr = { sizeof(my_addr), AF_INET, 0, {0}, {0} };
+
+ /* inet addresses of the two machines */
+struct in_addr my_machine_addr;
+struct in_addr his_machine_addr;
+
+u_short daemon_port; /* port number of the talk daemon */
+
+int ctl_sockt;
+int sockt;
+int invitation_waiting = 0;
+
+CTL_MSG msg;
+
+void
+open_sockt(void)
+{
+ socklen_t length;
+
+
+ (void)memset(&my_addr, 0, sizeof(my_addr));
+ my_addr.sin_family = AF_INET;
+#ifdef BSD4_4
+ my_addr.sin_len = sizeof(my_addr);
+#endif
+ my_addr.sin_addr = my_machine_addr;
+ my_addr.sin_port = 0;
+ sockt = socket(AF_INET, SOCK_STREAM, 0);
+ if (sockt == -1)
+ p_error("Bad socket");
+ if (bind(sockt, (struct sockaddr *)&my_addr, sizeof(my_addr)) != 0)
+ p_error("Binding local socket");
+ length = sizeof(my_addr);
+ if (getsockname(sockt, (struct sockaddr *)&my_addr, &length) == -1)
+ p_error("Bad address for socket");
+}
+
+/* open the ctl socket */
+void
+open_ctl(void)
+{
+ socklen_t length;
+
+ (void)memset(&ctl_addr, 0, sizeof(ctl_addr));
+ ctl_addr.sin_family = AF_INET;
+#ifdef BSD4_4
+ ctl_addr.sin_len = sizeof(my_addr);
+#endif
+ ctl_addr.sin_port = 0;
+ ctl_addr.sin_addr = my_machine_addr;
+ ctl_sockt = socket(AF_INET, SOCK_DGRAM, 0);
+ if (ctl_sockt == -1)
+ p_error("Bad socket");
+ if (bind(ctl_sockt,
+ (struct sockaddr *)&ctl_addr, sizeof(ctl_addr)) != 0)
+ p_error("Couldn't bind to control socket");
+ length = sizeof(ctl_addr);
+ if (getsockname(ctl_sockt,
+ (struct sockaddr *)&ctl_addr, &length) == -1)
+ p_error("Bad address for ctl socket");
+}
+
+/* print_addr is a debug print routine */
+void
+print_addr(struct sockaddr_in addr)
+{
+ int i;
+
+ printf("addr = %s, port = %o, family = %o zero = ",
+ inet_ntoa(addr.sin_addr), addr.sin_port, addr.sin_family);
+ for (i = 0; i<8;i++)
+ printf("%o ", (int)addr.sin_zero[i]);
+ putchar('\n');
+}
diff --git a/usr.bin/talk/ctl_transact.c b/usr.bin/talk/ctl_transact.c
new file mode 100644
index 0000000..4d0613f
--- /dev/null
+++ b/usr.bin/talk/ctl_transact.c
@@ -0,0 +1,108 @@
+/* $NetBSD: ctl_transact.c,v 1.11 2011/09/06 18:32:03 joerg Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)ctl_transact.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: ctl_transact.c,v 1.11 2011/09/06 18:32:03 joerg Exp $");
+#endif /* not lint */
+
+#include "talk.h"
+#include <sys/time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <poll.h>
+#include "talk_ctl.h"
+
+#define CTL_WAIT 2 /* time to wait for a response, in seconds */
+
+/*
+ * SOCKDGRAM is unreliable, so we must repeat messages if we have
+ * not received an acknowledgement within a reasonable amount
+ * of time
+ */
+void
+ctl_transact(struct in_addr target, CTL_MSG tmsg, int type, CTL_RESPONSE *rp)
+{
+ struct pollfd set[1];
+ int nready, cc;
+
+ nready = 0;
+ tmsg.type = type;
+ daemon_addr.sin_addr = target;
+ daemon_addr.sin_port = daemon_port;
+ set[0].fd = ctl_sockt;
+ set[0].events = POLLIN;
+
+ /*
+ * Keep sending the message until a response of
+ * the proper type is obtained.
+ */
+ do {
+ /* resend message until a response is obtained */
+ do {
+ cc = sendto(ctl_sockt, (char *)&tmsg, sizeof (tmsg), 0,
+ (struct sockaddr *)&daemon_addr,
+ sizeof (daemon_addr));
+ if (cc != sizeof (tmsg)) {
+ if (errno == EINTR)
+ continue;
+ p_error("Error on write to talk daemon");
+ }
+ nready = poll(set, 1, CTL_WAIT * 1000);
+ if (nready < 0) {
+ if (errno == EINTR)
+ continue;
+ p_error("Error waiting for daemon response");
+ }
+ } while (nready == 0);
+ /*
+ * Keep reading while there are queued messages
+ * (this is not necessary, it just saves extra
+ * request/acknowledgements being sent)
+ */
+ do {
+ cc = recv(ctl_sockt, (char *)rp, sizeof (*rp), 0);
+ if (cc < 0) {
+ if (errno == EINTR)
+ continue;
+ p_error("Error on read from talk daemon");
+ }
+ /* an immediate poll */
+ nready = poll(set, 1, 0);
+ } while (nready > 0 && (rp->vers != TALK_VERSION ||
+ rp->type != type));
+ } while (rp->vers != TALK_VERSION || rp->type != type);
+ rp->id_num = ntohl(rp->id_num);
+ rp->addr.sa_family = ntohs(rp->addr.sa_family);
+}
diff --git a/usr.bin/talk/display.c b/usr.bin/talk/display.c
new file mode 100644
index 0000000..7017f3f
--- /dev/null
+++ b/usr.bin/talk/display.c
@@ -0,0 +1,195 @@
+/* $NetBSD: display.c,v 1.9 2011/09/06 18:32:03 joerg Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)display.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: display.c,v 1.9 2011/09/06 18:32:03 joerg Exp $");
+#endif /* not lint */
+
+/*
+ * The window 'manager', initializes curses and handles the actual
+ * displaying of text
+ */
+#include "talk.h"
+#include <ctype.h>
+
+xwin_t my_win;
+xwin_t his_win;
+WINDOW *line_win;
+
+int curses_initialized = 0;
+
+/*
+ * max HAS to be a function, it is called with
+ * a argument of the form --foo at least once.
+ */
+int
+max(int a, int b)
+{
+
+ return (a > b ? a : b);
+}
+
+/*
+ * Display some text on somebody's window, processing some control
+ * characters while we are at it.
+ */
+void
+display(xwin_t *win, char *text, int size)
+{
+ int i;
+ char cch;
+
+ for (i = 0; i < size; i++) {
+ if (*text == '\n') {
+ xscroll(win, 0);
+ text++;
+ continue;
+ }
+ /* erase character */
+ if (*text == win->cerase) {
+ wmove(win->x_win, win->x_line, max(--win->x_col, 0));
+ getyx(win->x_win, win->x_line, win->x_col);
+ waddch(win->x_win, ' ');
+ wmove(win->x_win, win->x_line, win->x_col);
+ getyx(win->x_win, win->x_line, win->x_col);
+ text++;
+ continue;
+ }
+ /*
+ * On word erase search backwards until we find
+ * the beginning of a word or the beginning of
+ * the line.
+ */
+ if (*text == win->werase) {
+ int endcol, xcol, j, c;
+
+ endcol = win->x_col;
+ xcol = endcol - 1;
+ while (xcol >= 0) {
+ c = readwin(win->x_win, win->x_line, xcol);
+ if (c != ' ')
+ break;
+ xcol--;
+ }
+ while (xcol >= 0) {
+ c = readwin(win->x_win, win->x_line, xcol);
+ if (c == ' ')
+ break;
+ xcol--;
+ }
+ wmove(win->x_win, win->x_line, xcol + 1);
+ for (j = xcol + 1; j < endcol; j++)
+ waddch(win->x_win, ' ');
+ wmove(win->x_win, win->x_line, xcol + 1);
+ getyx(win->x_win, win->x_line, win->x_col);
+ text++;
+ continue;
+ }
+ /* line kill */
+ if (*text == win->kill) {
+ wmove(win->x_win, win->x_line, 0);
+ wclrtoeol(win->x_win);
+ getyx(win->x_win, win->x_line, win->x_col);
+ text++;
+ continue;
+ }
+ if (*text == '\f') {
+ if (win == &my_win)
+ wrefresh(curscr);
+ text++;
+ continue;
+ }
+ if (*text == '\07') {
+ beep();
+ text++;
+ continue;
+ }
+ if (win->x_col == COLS-1) {
+ /* check for wraparound */
+ xscroll(win, 0);
+ }
+ if ( !isprint((u_char)*text) && *text != '\t') {
+ waddch(win->x_win, '^');
+ getyx(win->x_win, win->x_line, win->x_col);
+ if (win->x_col == COLS-1) /* check for wraparound */
+ xscroll(win, 0);
+ cch = (*text & 63) + 64;
+ waddch(win->x_win, cch);
+ } else
+ waddch(win->x_win, *text);
+ getyx(win->x_win, win->x_line, win->x_col);
+ text++;
+ }
+ wrefresh(win->x_win);
+}
+
+/*
+ * Read the character at the indicated position in win
+ */
+int
+readwin(WINDOW *win, int line, int col)
+{
+ int oldline, oldcol;
+ int c;
+
+ getyx(win, oldline, oldcol);
+ wmove(win, line, col);
+ c = winch(win);
+ wmove(win, oldline, oldcol);
+ return (c);
+}
+
+/*
+ * Scroll a window, blanking out the line following the current line
+ * so that the current position is obvious
+ */
+void
+xscroll(xwin_t *win, int flag)
+{
+
+ if (flag == -1) {
+ wmove(win->x_win, 0, 0);
+ win->x_line = 0;
+ win->x_col = 0;
+ return;
+ }
+ win->x_line = (win->x_line + 1) % win->x_nlines;
+ win->x_col = 0;
+ wmove(win->x_win, win->x_line, win->x_col);
+ wclrtoeol(win->x_win);
+ wmove(win->x_win, (win->x_line + 1) % win->x_nlines, win->x_col);
+ wclrtoeol(win->x_win);
+ wmove(win->x_win, win->x_line, win->x_col);
+}
diff --git a/usr.bin/talk/get_addrs.c b/usr.bin/talk/get_addrs.c
new file mode 100644
index 0000000..f381cc7
--- /dev/null
+++ b/usr.bin/talk/get_addrs.c
@@ -0,0 +1,81 @@
+/* $NetBSD: get_addrs.c,v 1.12 2015/06/16 22:54:11 christos Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)get_addrs.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: get_addrs.c,v 1.12 2015/06/16 22:54:11 christos Exp $");
+#endif /* not lint */
+
+#include "talk.h"
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+#include "talk_ctl.h"
+
+void
+get_addrs(const char *my_machine_name, const char *his_machine_name)
+{
+ struct hostent *hp;
+ struct servent *sp;
+
+ msg.pid = htonl(getpid());
+ /*
+ * If the callee is on-machine, just use loopback
+ * otherwise do a lookup...
+ */
+ if (strcmp(his_machine_name, my_machine_name) != 0) {
+ /* look up the address of the local host */
+ hp = gethostbyname(my_machine_name);
+ if (hp == NULL)
+ errx(EXIT_FAILURE, "%s: %s", my_machine_name,
+ hstrerror(h_errno));
+ memcpy(&my_machine_addr, hp->h_addr, sizeof(my_machine_addr));
+ hp = gethostbyname(his_machine_name);
+ if (hp == NULL)
+ errx(EXIT_FAILURE, "%s: %s", his_machine_name,
+ hstrerror(h_errno));
+ memcpy(&his_machine_addr, hp->h_addr, sizeof(his_machine_addr));
+ } else
+ his_machine_addr.s_addr = my_machine_addr.s_addr =
+ htonl(INADDR_LOOPBACK);
+
+ /* find the server's port */
+ sp = getservbyname("ntalk", "udp");
+ if (sp == 0)
+ errx(EXIT_FAILURE, "%s/%s: service is not registered.",
+ "ntalk", "udp");
+ daemon_port = sp->s_port;
+}
diff --git a/usr.bin/talk/get_names.c b/usr.bin/talk/get_names.c
new file mode 100644
index 0000000..5400548
--- /dev/null
+++ b/usr.bin/talk/get_names.c
@@ -0,0 +1,124 @@
+/* $NetBSD: get_names.c,v 1.16 2012/12/29 23:44:23 christos Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)get_names.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: get_names.c,v 1.16 2012/12/29 23:44:23 christos Exp $");
+#endif /* not lint */
+
+#include "talk.h"
+#include <err.h>
+#include <sys/param.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <util.h>
+
+extern CTL_MSG msg;
+
+/*
+ * Determine the local and remote user, tty, and machines
+ */
+void
+get_names(int argc, char *argv[])
+{
+ char hostname[MAXHOSTNAMELEN + 1];
+ const char *his_name, *my_name;
+ const char *my_machine_name, *his_machine_name;
+ const char *his_tty;
+ char *cp;
+ char *names;
+
+ if (argc < 2 ) {
+ fprintf(stderr, "Usage: %s user [ttyname]\n", getprogname());
+ exit(1);
+ }
+ if (!isatty(0))
+ errx(EXIT_FAILURE, "Standard input must be a tty, "
+ "not a pipe or a file");
+ if ((my_name = getlogin()) == NULL) {
+ struct passwd *pw;
+
+ if ((pw = getpwuid(getuid())) == NULL)
+ errx(EXIT_FAILURE, "You don't exist. Go away.");
+ my_name = pw->pw_name;
+ }
+ if ((cp = getenv("TALKHOST")) != NULL)
+ (void)estrlcpy(hostname, cp, sizeof(hostname));
+ else {
+ if (gethostname(hostname, sizeof(hostname)) == -1)
+ err(EXIT_FAILURE, "gethostname");
+ hostname[sizeof(hostname) - 1] = '\0';
+ }
+ my_machine_name = hostname;
+ /* check for, and strip out, the machine name of the target */
+ names = strdup(argv[1]);
+ for (cp = names; *cp && !strchr("@:!.", *cp); cp++)
+ ;
+ if (*cp == '\0') {
+ /* this is a local to local talk */
+ his_name = names;
+ his_machine_name = my_machine_name;
+ } else {
+ if (*cp++ == '@') {
+ /* user@host */
+ his_name = names;
+ his_machine_name = cp;
+ } else {
+ /* host.user or host!user or host:user */
+ his_name = cp;
+ his_machine_name = names;
+ }
+ *--cp = '\0';
+ }
+ if (argc > 2)
+ his_tty = argv[2]; /* tty name is arg 2 */
+ else
+ his_tty = "";
+ get_addrs(my_machine_name, his_machine_name);
+ /*
+ * Initialize the message template.
+ */
+ msg.vers = TALK_VERSION;
+ msg.addr.sa_family = htons(AF_INET);
+ msg.ctl_addr.sa_family = htons(AF_INET);
+ msg.id_num = htonl(0);
+ strncpy(msg.l_name, my_name, NAME_SIZE);
+ msg.l_name[NAME_SIZE - 1] = '\0';
+ strncpy(msg.r_name, his_name, NAME_SIZE);
+ msg.r_name[NAME_SIZE - 1] = '\0';
+ strncpy(msg.r_tty, his_tty, TTY_SIZE);
+ msg.r_tty[TTY_SIZE - 1] = '\0';
+ free(names);
+}
diff --git a/usr.bin/talk/init_disp.c b/usr.bin/talk/init_disp.c
new file mode 100644
index 0000000..a006705
--- /dev/null
+++ b/usr.bin/talk/init_disp.c
@@ -0,0 +1,153 @@
+/* $NetBSD: init_disp.c,v 1.12 2011/09/06 18:32:03 joerg Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)init_disp.c 8.2 (Berkeley) 2/16/94";
+#endif
+__RCSID("$NetBSD: init_disp.c,v 1.12 2011/09/06 18:32:03 joerg Exp $");
+#endif /* not lint */
+
+/*
+ * Initialization code for the display package,
+ * as well as the signal handling routines.
+ */
+
+#include "talk.h"
+#include <sys/ioctl.h>
+#include <sys/ioctl_compat.h>
+#include <err.h>
+#include <signal.h>
+#include <termios.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/*
+ * Set up curses, catch the appropriate signals,
+ * and build the various windows.
+ */
+void
+init_display(void)
+{
+ struct sigaction sa;
+
+ if (initscr() == NULL)
+ errx(1, "Terminal type unset or lacking necessary features.");
+ (void)sigaction(SIGTSTP, NULL, &sa);
+ sigaddset(&sa.sa_mask, SIGALRM);
+ (void)sigaction(SIGTSTP, &sa, NULL);
+ curses_initialized = 1;
+ clear();
+ refresh();
+ noecho();
+ cbreak();
+ signal(SIGINT, sig_sent);
+ signal(SIGPIPE, sig_sent);
+ /* curses takes care of ^Z */
+ my_win.x_nlines = LINES / 2;
+ my_win.x_ncols = COLS;
+ my_win.x_win = newwin(my_win.x_nlines, my_win.x_ncols, 0, 0);
+ scrollok(my_win.x_win, FALSE);
+ wclear(my_win.x_win);
+
+ his_win.x_nlines = LINES / 2 - 1;
+ his_win.x_ncols = COLS;
+ his_win.x_win = newwin(his_win.x_nlines, his_win.x_ncols,
+ my_win.x_nlines+1, 0);
+ scrollok(his_win.x_win, FALSE);
+ wclear(his_win.x_win);
+
+ line_win = newwin(1, COLS, my_win.x_nlines, 0);
+ box(line_win, '-', '-');
+ wrefresh(line_win);
+ /* let them know we are working on it */
+ current_state = "No connection yet";
+}
+
+/*
+ * Trade edit characters with the other talk. By agreement
+ * the first three characters each talk transmits after
+ * connection are the three edit characters.
+ */
+void
+set_edit_chars(void)
+{
+ char buf[3];
+ int cc;
+ struct termios tty;
+
+ tcgetattr(0, &tty);
+ my_win.cerase = tty.c_cc[VERASE];
+ my_win.kill = tty.c_cc[VKILL];
+ if (tty.c_cc[VWERASE] == (unsigned char) -1)
+ my_win.werase = '\027'; /* control W */
+ else
+ my_win.werase = tty.c_cc[VWERASE];
+ buf[0] = my_win.cerase;
+ buf[1] = my_win.kill;
+ buf[2] = my_win.werase;
+ cc = write(sockt, buf, sizeof(buf));
+ if (cc != sizeof(buf) )
+ p_error("Lost the connection");
+ cc = read(sockt, buf, sizeof(buf));
+ if (cc != sizeof(buf) )
+ p_error("Lost the connection");
+ his_win.cerase = buf[0];
+ his_win.kill = buf[1];
+ his_win.werase = buf[2];
+}
+
+void
+sig_sent(int dummy)
+{
+
+ message("Connection closing. Exiting");
+ quit();
+}
+
+/*
+ * All done talking...hang up the phone and reset terminal thingy's
+ */
+void
+quit(void)
+{
+
+ if (curses_initialized) {
+ wmove(his_win.x_win, his_win.x_nlines-1, 0);
+ wclrtoeol(his_win.x_win);
+ wrefresh(his_win.x_win);
+ endwin();
+ }
+ if (invitation_waiting)
+ send_delete();
+ exit(0);
+}
diff --git a/usr.bin/talk/invite.c b/usr.bin/talk/invite.c
new file mode 100644
index 0000000..680744c
--- /dev/null
+++ b/usr.bin/talk/invite.c
@@ -0,0 +1,190 @@
+/* $NetBSD: invite.c,v 1.10 2012/12/29 23:44:23 christos Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)invite.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: invite.c,v 1.10 2012/12/29 23:44:23 christos Exp $");
+#endif /* not lint */
+
+#include "talk.h"
+#include <sys/time.h>
+#include <signal.h>
+#include <errno.h>
+#include <setjmp.h>
+#include <unistd.h>
+#include <err.h>
+#include "talk_ctl.h"
+
+/*
+ * There wasn't an invitation waiting, so send a request containing
+ * our sockt address to the remote talk daemon so it can invite
+ * him
+ */
+
+/*
+ * The msg.id's for the invitations
+ * on the local and remote machines.
+ * These are used to delete the
+ * invitations.
+ */
+static int local_id, remote_id;
+static jmp_buf invitebuf;
+
+void
+invite_remote(void)
+{
+ int new_sockt;
+ struct itimerval itimer;
+ CTL_RESPONSE response;
+
+ itimer.it_value.tv_sec = RING_WAIT;
+ itimer.it_value.tv_usec = 0;
+ itimer.it_interval = itimer.it_value;
+ if (listen(sockt, 5) != 0)
+ p_error("Error on attempt to listen for caller");
+#ifdef MSG_EOR
+ /* copy new style sockaddr to old, swap family (short in old) */
+ msg.addr = *(struct talkd_sockaddr *)(void *)&my_addr;
+ msg.addr.sa_family = htons(my_addr.sin_family);
+#else
+ msg.addr = *(struct sockaddr *)&my_addr;
+#endif
+ msg.id_num = htonl(-1); /* an impossible id_num */
+ invitation_waiting = 1;
+ announce_invite();
+ /*
+ * Shut off the automatic messages for a while,
+ * so we can use the interrupt timer to resend the invitation
+ */
+ end_msgs();
+ setitimer(ITIMER_REAL, &itimer, (struct itimerval *)0);
+ message("Waiting for your party to respond");
+ signal(SIGALRM, re_invite);
+ (void) setjmp(invitebuf);
+ while ((new_sockt = accept(sockt, 0, 0)) < 0) {
+ if (errno == EINTR)
+ continue;
+ p_error("Unable to connect with your party");
+ }
+ close(sockt);
+ sockt = new_sockt;
+
+ /*
+ * Have the daemons delete the invitations now that we
+ * have connected.
+ */
+ current_state = "Waiting for your party to respond";
+ start_msgs();
+
+ msg.id_num = htonl(local_id);
+ ctl_transact(my_machine_addr, msg, DELETE, &response);
+ msg.id_num = htonl(remote_id);
+ ctl_transact(his_machine_addr, msg, DELETE, &response);
+ invitation_waiting = 0;
+}
+
+/*
+ * Routine called on interrupt to re-invite the callee
+ */
+void
+re_invite(int dummy)
+{
+
+ message("Ringing your party again");
+ current_line++;
+ /* force a re-announce */
+ msg.id_num = htonl(remote_id + 1);
+ announce_invite();
+ longjmp(invitebuf, 1);
+}
+
+static const char *answers[] = {
+ "answer #0", /* SUCCESS */
+ "Your party is not logged on", /* NOT_HERE */
+ "Target machine is too confused to talk to us", /* FAILED */
+ "Target machine does not recognize us", /* MACHINE_UNKNOWN */
+ "Your party is refusing messages", /* PERMISSION_REFUSED */
+ "Target machine can not handle remote talk", /* UNKNOWN_REQUEST */
+ "Target machine indicates protocol mismatch", /* BADVERSION */
+ "Target machine indicates protocol botch (addr)",/* BADADDR */
+ "Target machine indicates protocol botch (ctl_addr)",/* BADCTLADDR */
+};
+#define NANSWERS (sizeof (answers) / sizeof (answers[0]))
+
+/*
+ * Transmit the invitation and process the response
+ */
+void
+announce_invite(void)
+{
+ CTL_RESPONSE response;
+
+ current_state = "Trying to connect to your party's talk daemon";
+ ctl_transact(his_machine_addr, msg, ANNOUNCE, &response);
+ remote_id = response.id_num;
+ if (response.answer != SUCCESS) {
+ if (response.answer < NANSWERS)
+ message(answers[response.answer]);
+ quit();
+ }
+ /* leave the actual invitation on my talk daemon */
+ ctl_transact(my_machine_addr, msg, LEAVE_INVITE, &response);
+ local_id = response.id_num;
+}
+
+/*
+ * Tell the daemon to remove your invitation
+ */
+void
+send_delete(void)
+{
+
+ msg.type = DELETE;
+ /*
+ * This is just a extra clean up, so just send it
+ * and don't wait for an answer
+ */
+ msg.id_num = htonl(remote_id);
+ daemon_addr.sin_addr = his_machine_addr;
+ if (sendto(ctl_sockt, &msg, sizeof (msg), 0,
+ (struct sockaddr *)&daemon_addr,
+ sizeof (daemon_addr)) != sizeof(msg))
+ warn("send_delete (remote)");
+ msg.id_num = htonl(local_id);
+ daemon_addr.sin_addr = my_machine_addr;
+ if (sendto(ctl_sockt, &msg, sizeof (msg), 0,
+ (struct sockaddr *)&daemon_addr,
+ sizeof (daemon_addr)) != sizeof (msg))
+ warn("send_delete (local)");
+}
diff --git a/usr.bin/talk/io.c b/usr.bin/talk/io.c
new file mode 100644
index 0000000..f789f57
--- /dev/null
+++ b/usr.bin/talk/io.c
@@ -0,0 +1,139 @@
+/* $NetBSD: io.c,v 1.14 2011/09/06 18:32:03 joerg Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)io.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: io.c,v 1.14 2011/09/06 18:32:03 joerg Exp $");
+#endif /* not lint */
+
+/*
+ * This file contains the I/O handling and the exchange of
+ * edit characters. This connection itself is established in
+ * ctl.c
+ */
+
+#include "talk.h"
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <poll.h>
+
+#define A_LONG_TIME 1000000
+
+/*
+ * The routine to do the actual talking
+ */
+void
+talk(void)
+{
+ struct pollfd set[2];
+ int nb;
+ char buf[BUFSIZ];
+
+ message("Connection established\007\007\007");
+ current_line = 0;
+
+ /*
+ * Wait on both the other process (sockt_mask) and
+ * standard input ( STDIN_MASK )
+ */
+ set[0].fd = sockt;
+ set[0].events = POLLIN;
+ set[1].fd = fileno(stdin);
+ set[1].events = POLLIN;
+ for (;;) {
+ nb = poll(set, 2, A_LONG_TIME * 1000);
+ if (nb <= 0) {
+ if (errno == EINTR)
+ continue;
+ /* panic, we don't know what happened */
+ p_error("Unexpected error from poll");
+ quit();
+ }
+ if (set[0].revents & POLLIN) {
+ /* There is data on sockt */
+ nb = read(sockt, buf, sizeof buf);
+ if (nb <= 0) {
+ message("Connection closed. Exiting");
+ quit();
+ }
+ display(&his_win, buf, nb);
+ }
+ if (set[1].revents & POLLIN) {
+ /*
+ * We can't make the tty non_blocking, because
+ * curses's output routines would screw up
+ */
+ ioctl(0, FIONREAD, (void *) &nb);
+ nb = read(0, buf, nb);
+ display(&my_win, buf, nb);
+ /* might lose data here because sockt is non-blocking */
+ if (nb > 0)
+ write(sockt, buf, nb);
+ }
+ }
+}
+
+/*
+ * p_error prints the system error message on the standard location
+ * on the screen and then exits. (i.e. a curses version of perror)
+ */
+void
+p_error(const char *string)
+{
+ wmove(my_win.x_win, current_line%my_win.x_nlines, 0);
+ wprintw(my_win.x_win, "[%s : %s (%d)]\n",
+ string, strerror(errno), errno);
+ wrefresh(my_win.x_win);
+ move(LINES-1, 0);
+ refresh();
+ quit();
+}
+
+/*
+ * Display string in the standard location
+ */
+void
+message(const char *string)
+{
+ wmove(my_win.x_win, current_line % my_win.x_nlines, 0);
+ wprintw(my_win.x_win, "[%s]", string);
+ wclrtoeol(my_win.x_win);
+ current_line++;
+ wmove(my_win.x_win, current_line % my_win.x_nlines, 0);
+ wrefresh(my_win.x_win);
+}
diff --git a/usr.bin/talk/look_up.c b/usr.bin/talk/look_up.c
new file mode 100644
index 0000000..1ff3d79
--- /dev/null
+++ b/usr.bin/talk/look_up.c
@@ -0,0 +1,119 @@
+/* $NetBSD: look_up.c,v 1.8 2012/03/20 20:34:59 matt Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)look_up.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: look_up.c,v 1.8 2012/03/20 20:34:59 matt Exp $");
+#endif /* not lint */
+
+#include "talk.h"
+#include <errno.h>
+#include <unistd.h>
+#include "talk_ctl.h"
+
+/*
+ * See if the local daemon has an invitation for us.
+ */
+int
+check_local(void)
+{
+ CTL_RESPONSE response;
+ CTL_RESPONSE *rp = &response;
+ struct sockaddr addr;
+
+ /* the rest of msg was set up in get_names */
+#ifdef MSG_EOR
+ /* copy new style sockaddr to old, swap family (short in old) */
+ msg.ctl_addr = *(struct talkd_sockaddr *)(void *)&ctl_addr;
+ msg.ctl_addr.sa_family = htons(ctl_addr.sin_family);
+#else
+ msg.ctl_addr = *(struct sockaddr *)&ctl_addr;
+#endif
+ /* must be initiating a talk */
+ if (!look_for_invite(rp))
+ return (0);
+ /*
+ * There was an invitation waiting for us,
+ * so connect with the other (hopefully waiting) party
+ */
+ current_state = "Waiting to connect with caller";
+ do {
+ if (rp->addr.sa_family != AF_INET)
+ p_error("Response uses invalid network address");
+
+ (void)memcpy(&addr, &rp->addr.sa_family, sizeof(addr));
+ addr.sa_family = rp->addr.sa_family;
+ addr.sa_len = sizeof(addr);
+
+ errno = 0;
+ if (connect(sockt, &addr, sizeof(addr)) != -1)
+ return (1);
+ } while (errno == EINTR);
+ if (errno == ECONNREFUSED) {
+ /*
+ * The caller gave up, but his invitation somehow
+ * was not cleared. Clear it and initiate an
+ * invitation. (We know there are no newer invitations,
+ * the talkd works LIFO.)
+ */
+ ctl_transact(his_machine_addr, msg, DELETE, rp);
+ close(sockt);
+ open_sockt();
+ return (0);
+ }
+ p_error("Unable to connect with initiator");
+ /*NOTREACHED*/
+ return (0);
+}
+
+/*
+ * Look for an invitation on 'machine'
+ */
+int
+look_for_invite(CTL_RESPONSE *rp)
+{
+ current_state = "Checking for invitation on caller's machine";
+ ctl_transact(his_machine_addr, msg, LOOK_UP, rp);
+ /* the switch is for later options, such as multiple invitations */
+ switch (rp->answer) {
+
+ case SUCCESS:
+ msg.id_num = htonl(rp->id_num);
+ return (1);
+
+ default:
+ /* there wasn't an invitation waiting for us */
+ return (0);
+ }
+}
diff --git a/usr.bin/talk/msgs.c b/usr.bin/talk/msgs.c
new file mode 100644
index 0000000..bb84843
--- /dev/null
+++ b/usr.bin/talk/msgs.c
@@ -0,0 +1,82 @@
+/* $NetBSD: msgs.c,v 1.7 2011/09/06 18:32:03 joerg Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)msgs.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: msgs.c,v 1.7 2011/09/06 18:32:03 joerg Exp $");
+#endif /* not lint */
+
+/*
+ * A package to display what is happening every MSG_INTERVAL seconds
+ * if we are slow connecting.
+ */
+
+#include <sys/time.h>
+#include <signal.h>
+#include <stdio.h>
+#include "talk.h"
+
+#define MSG_INTERVAL 4
+
+const char *current_state;
+int current_line = 0;
+
+void
+disp_msg(int dummy)
+{
+ message(current_state);
+}
+
+void
+start_msgs(void)
+{
+ struct itimerval itimer;
+
+ message(current_state);
+ signal(SIGALRM, disp_msg);
+ itimer.it_value.tv_sec = itimer.it_interval.tv_sec = MSG_INTERVAL;
+ itimer.it_value.tv_usec = itimer.it_interval.tv_usec = 0;
+ setitimer(ITIMER_REAL, &itimer, (struct itimerval *)0);
+}
+
+void
+end_msgs(void)
+{
+ struct itimerval itimer;
+
+ timerclear(&itimer.it_value);
+ timerclear(&itimer.it_interval);
+ setitimer(ITIMER_REAL, &itimer, (struct itimerval *)0);
+ signal(SIGALRM, SIG_DFL);
+}
diff --git a/usr.bin/talk/talk.1 b/usr.bin/talk/talk.1
new file mode 100644
index 0000000..20d3ee2
--- /dev/null
+++ b/usr.bin/talk/talk.1
@@ -0,0 +1,143 @@
+.\" $NetBSD: talk.1,v 1.9 2007/01/08 17:10:59 christos Exp $
+.\"
+.\" Copyright (c) 1983, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)talk.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd January 7, 2007
+.Dt TALK 1
+.Os
+.Sh NAME
+.Nm talk
+.Nd talk to another user
+.Sh SYNOPSIS
+.Nm
+.Ar person
+.Op Ar ttyname
+.Sh DESCRIPTION
+.Nm
+is a visual communication program which copies lines from your
+terminal to that of another user.
+.Pp
+Options available:
+.Bl -tag -width ttyname
+.It Ar person
+If you wish to talk to someone on your own machine, then
+.Ar person
+is just the person's login name.
+If you wish to talk to a user on another host, then
+.Ar person
+is of the form
+.Ql user@host .
+.It Ar ttyname
+If you wish to talk to a user who is logged in more than once, the
+.Ar ttyname
+argument may be used to indicate the appropriate terminal
+name, where
+.Ar ttyname
+is of the form
+.Ql ttyXX .
+.El
+.Pp
+When first called,
+.Nm
+sends the message
+.Bd -literal -offset indent -compact
+Message from TalkDaemon@his_machine...
+talk: connection requested by your_name@your_machine.
+talk: respond with: talk your_name@your_machine
+.Ed
+.Pp
+to the user you wish to talk to.
+At this point, the recipient
+of the message should reply by typing
+.Pp
+.Dl talk \ your_name@your_machine
+.Pp
+It doesn't matter from which machine the recipient replies, as
+long as his login-name is the same.
+Once communication is established,
+the two parties may type simultaneously, with their output appearing
+in separate windows.
+Typing control-L
+.Ql ^L
+will cause the screen to
+be reprinted, while your erase, kill, and word kill characters will
+behave normally.
+To exit, just type your interrupt character;
+.Nm
+then moves the cursor to the bottom of the screen and restores the
+terminal to its previous state.
+.Pp
+Permission to talk may be denied or granted by use of the
+.Xr mesg 1
+command.
+At the outset talking is allowed.
+Certain commands, in
+particular
+.Xr nroff 1
+and
+.Xr pr 1 ,
+disallow messages in order to
+prevent messy output.
+.Sh ENVIRONMENT
+If the
+.Ev TALKHOST
+environment variable is set, its value is used as the
+.Ar hostname
+the
+.Nm
+packets appear to be originating from.
+This is useful if you wish to talk to someone on another machine and
+your internal hostname does not resolve to the address of your
+external interface as seen from the other machine.
+.Sh FILES
+.Bl -tag -width /var/run/utmp -compact
+.It Pa /etc/hosts
+to find the recipient's machine
+.It Pa /var/run/utmp
+to find the recipient's tty
+.El
+.Sh SEE ALSO
+.Xr mail 1 ,
+.Xr mesg 1 ,
+.Xr who 1 ,
+.Xr write 1
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.2 .
+.Sh BUGS
+The version of
+.Nm
+released with
+.Bx 4.3
+uses a protocol that
+is incompatible with the protocol used in the version released with
+.Bx 4.2 .
diff --git a/usr.bin/talk/talk.c b/usr.bin/talk/talk.c
new file mode 100644
index 0000000..2d71253
--- /dev/null
+++ b/usr.bin/talk/talk.c
@@ -0,0 +1,79 @@
+/* $NetBSD: talk.c,v 1.8 2011/09/06 18:32:03 joerg Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1983, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)talk.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: talk.c,v 1.8 2011/09/06 18:32:03 joerg Exp $");
+#endif /* not lint */
+
+#include "talk.h"
+#include <locale.h>
+
+/*
+ * talk: A visual form of write. Using sockets, a two way
+ * connection is set up between the two people talking.
+ * With the aid of curses, the screen is split into two
+ * windows, and each users text is added to the window,
+ * one character at a time...
+ *
+ * Written by Kipp Hickman
+ *
+ * Modified to run under 4.1a by Clem Cole and Peter Moore
+ * Modified to run between hosts by Peter Moore, 8/19/82
+ * Modified to run under 4.1c by Peter Moore 3/17/83
+ */
+
+int
+main(int argc, char *argv[])
+{
+ setlocale(LC_ALL, "");
+
+ get_names(argc, argv);
+ init_display();
+ open_ctl();
+ open_sockt();
+ start_msgs();
+ if (!check_local())
+ invite_remote();
+ end_msgs();
+ set_edit_chars();
+ talk();
+ /* NOTREACHED */
+ return (0);
+}
diff --git a/usr.bin/talk/talk.h b/usr.bin/talk/talk.h
new file mode 100644
index 0000000..77cfac7
--- /dev/null
+++ b/usr.bin/talk/talk.h
@@ -0,0 +1,89 @@
+/* $NetBSD: talk.h,v 1.9 2011/09/06 18:32:03 joerg Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)talk.h 8.1 (Berkeley) 6/6/93
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <protocols/talkd.h>
+#include <curses.h>
+#include <string.h>
+
+extern int sockt;
+extern int curses_initialized;
+extern int invitation_waiting;
+
+extern const char *current_state;
+extern int current_line;
+
+typedef struct xwin {
+ WINDOW *x_win;
+ int x_nlines;
+ int x_ncols;
+ int x_line;
+ int x_col;
+ char kill;
+ char cerase;
+ char werase;
+} xwin_t;
+
+extern xwin_t my_win;
+extern xwin_t his_win;
+extern WINDOW *line_win;
+
+void announce_invite(void);
+int check_local(void);
+void ctl_transact(struct in_addr, CTL_MSG, int, CTL_RESPONSE *);
+void display(xwin_t *, char *, int);
+void disp_msg(int);
+void end_msgs(void);
+void get_addrs(const char *, const char *);
+void get_names(int, char **);
+void init_display(void);
+void invite_remote(void);
+int look_for_invite(CTL_RESPONSE *);
+int max(int, int);
+void message(const char *);
+void open_ctl(void);
+void open_sockt(void);
+void print_addr(struct sockaddr_in);
+void p_error(const char *) __dead;
+void quit(void) __dead;
+int readwin(WINDOW *, int, int);
+void re_invite(int) __dead;
+void send_delete(void);
+void set_edit_chars(void);
+void sig_sent(int) __dead;
+void start_msgs(void);
+void talk(void) __dead;
+void xscroll(xwin_t *, int);
diff --git a/usr.bin/talk/talk_ctl.h b/usr.bin/talk/talk_ctl.h
new file mode 100644
index 0000000..e4bab52
--- /dev/null
+++ b/usr.bin/talk/talk_ctl.h
@@ -0,0 +1,41 @@
+/* $NetBSD: talk_ctl.h,v 1.4 2003/08/07 11:16:05 agc Exp $ */
+
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)talk_ctl.h 8.1 (Berkeley) 6/6/93
+ */
+
+extern struct sockaddr_in daemon_addr;
+extern struct sockaddr_in ctl_addr;
+extern struct sockaddr_in my_addr;
+extern struct in_addr my_machine_addr;
+extern struct in_addr his_machine_addr;
+extern u_short daemon_port;
+extern int ctl_sockt;
+extern CTL_MSG msg;
diff --git a/usr.bin/tee/tee.1 b/usr.bin/tee/tee.1
new file mode 100644
index 0000000..5ba9565
--- /dev/null
+++ b/usr.bin/tee/tee.1
@@ -0,0 +1,84 @@
+.\" $NetBSD: tee.1,v 1.9 2017/07/04 07:05:16 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tee.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt TEE 1
+.Os
+.Sh NAME
+.Nm tee
+.Nd pipe fitting
+.Sh SYNOPSIS
+.Nm
+.Op Fl ai
+.Op Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility copies standard input to standard output,
+making a copy in zero or more files.
+The output is unbuffered.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl a
+Append the output to the files rather than
+overwriting them.
+.It Fl i
+Ignore the
+.Dv SIGINT
+signal.
+.El
+.Pp
+The following operands are available:
+.Bl -tag -width file
+.It file
+A pathname of an output
+.Ar file .
+.El
+.Pp
+The
+.Nm
+utility takes the default action for all signals,
+except in the event of the
+.Fl i
+option.
+.Sh EXIT STATUS
+.Ex -std tee
+.Sh STANDARDS
+The
+.Nm
+function is expected to be
+.Tn POSIX
+.St -p1003.2
+compatible.
diff --git a/usr.bin/tee/tee.c b/usr.bin/tee/tee.c
new file mode 100644
index 0000000..f534e7e
--- /dev/null
+++ b/usr.bin/tee/tee.c
@@ -0,0 +1,149 @@
+/* $NetBSD: tee.c,v 1.12 2016/09/05 00:40:30 sevan Exp $ */
+
+/*
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1988, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)tee.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: tee.c,v 1.12 2016/09/05 00:40:30 sevan Exp $");
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#include <err.h>
+
+typedef struct _list {
+ struct _list *next;
+ int fd;
+ const char *name;
+} LIST;
+LIST *head;
+
+void add(int, const char *);
+
+int
+main(int argc, char *argv[])
+{
+ LIST *p;
+ ssize_t rval;
+ int fd;
+ int append, ch, exitval;
+ char *buf;
+#define BSIZE (8 * 1024)
+
+ setlocale(LC_ALL, "");
+
+ append = 0;
+ while ((ch = getopt(argc, argv, "ai")) != -1)
+ switch((char)ch) {
+ case 'a':
+ append = 1;
+ break;
+ case 'i':
+ (void)signal(SIGINT, SIG_IGN);
+ break;
+ case '?':
+ default:
+ (void)fprintf(stderr, "usage: tee [-ai] [file ...]\n");
+ exit(1);
+ }
+ argv += optind;
+ argc -= optind;
+
+ if ((buf = malloc(BSIZE)) == NULL)
+ err(1, "malloc");
+
+ add(STDOUT_FILENO, "stdout");
+
+ for (exitval = 0; *argv; ++argv)
+ if ((fd = open(*argv, append ? O_WRONLY|O_CREAT|O_APPEND :
+ O_WRONLY|O_CREAT|O_TRUNC, DEFFILEMODE)) < 0) {
+ warn("%s", *argv);
+ exitval = 1;
+ } else
+ add(fd, *argv);
+
+ while ((rval = read(STDIN_FILENO, buf, BSIZE)) > 0)
+ for (p = head; p; p = p->next) {
+ const char *bp = buf;
+ size_t n = rval;
+ ssize_t wval;
+
+ do {
+ if ((wval = write(p->fd, bp, n)) == -1) {
+ warn("%s", p->name);
+ exitval = 1;
+ break;
+ }
+ bp += wval;
+ } while (n -= wval);
+ }
+ if (rval < 0) {
+ warn("read");
+ exitval = 1;
+ }
+
+ for (p = head; p; p = p->next) {
+ if (close(p->fd) == -1) {
+ warn("%s", p->name);
+ exitval = 1;
+ }
+ }
+
+ exit(exitval);
+}
+
+void
+add(int fd, const char *name)
+{
+ LIST *p;
+
+ if ((p = malloc(sizeof(LIST))) == NULL)
+ err(1, "malloc");
+ p->fd = fd;
+ p->name = name;
+ p->next = head;
+ head = p;
+}
diff --git a/usr.bin/time/ext.h b/usr.bin/time/ext.h
new file mode 100644
index 0000000..6626a29
--- /dev/null
+++ b/usr.bin/time/ext.h
@@ -0,0 +1,5 @@
+/* $NetBSD: ext.h,v 1.3 2017/07/15 14:34:08 christos Exp $ */
+
+/* borrowed from ../../bin/csh/extern.h */
+void prusage1(FILE *, const char *fmt, struct rusage *, struct rusage *,
+ struct timespec *, struct timespec *);
diff --git a/usr.bin/time/time.1 b/usr.bin/time/time.1
new file mode 100644
index 0000000..3f42310
--- /dev/null
+++ b/usr.bin/time/time.1
@@ -0,0 +1,220 @@
+.\" $NetBSD: time.1,v 1.28 2017/07/15 14:40:36 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)time.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd July 15, 2017
+.Dt TIME 1
+.Os
+.Sh NAME
+.Nm time
+.Nd time command execution
+.Sh SYNOPSIS
+.Nm
+.Op Fl clp
+.Op Fl f Ar fmt
+.Ar command
+.Op Ar argument ...
+.Sh DESCRIPTION
+The
+.Nm
+utility
+executes and
+times
+.Ar command .
+After the command finishes,
+.Nm
+writes the total elapsed time (wall clock time),
+.Pq Dq real ,
+the CPU time spent executing
+.Ar command
+at user level
+.Pq Dq user ,
+and the CPU time spent executing in the operating system kernel
+.Pq Dq sys ,
+to the standard error stream.
+Times are reported in seconds.
+.Pp
+Available options:
+.Bl -tag -width Ds
+.It Fl c
+Displays information in the format used by default the
+.Nm
+builtin of
+.Xr csh 1
+uses (%Uu %Ss %E %P %X+%Dk %I+%Oio %Fpf+%Ww).
+.It Fl f
+Specify a time format using the
+.Xr csh 1
+.Nm
+builtin syntax.
+The following sequences may be used in the format string:
+.Pp
+.Bl -tag -width 4n -compact
+.It \&%U
+The time the process spent in user mode in cpu seconds.
+.It \&%S
+The time the process spent in kernel mode in cpu seconds.
+.It \&%E
+The elapsed (wall clock) time in seconds.
+.It \&%P
+The CPU percentage computed as (%U + %S) / %E.
+.It \&%W
+Number of times the process was swapped.
+.It \&%X
+The average amount in (shared) text space used in Kbytes.
+.It \&%D
+The average amount in (unshared) data/stack space used in Kbytes.
+.It \&%K
+The total space used (%X + %D) in Kbytes.
+.It \&%M
+The maximum memory the process had in use at any time in Kbytes.
+.It \&%F
+The number of major page faults (page needed to be brought from disk).
+.It \&%R
+The number of minor page faults.
+.It \&%I
+The number of input operations.
+.It \&%O
+The number of output operations.
+.It \&%r
+The number of socket messages received.
+.It \&%s
+The number of socket messages sent.
+.It \&%k
+The number of signals received.
+.It \&%w
+The number of voluntary context switches (waits).
+.It \&%c
+The number of involuntary context switches.
+.El
+.It Fl l
+Lists resource utilization information.
+The contents of the
+.Ar command
+process's
+.Em rusage
+structure are printed; see below.
+.It Fl p
+The output is formatted as specified by
+.St -p1003.2-92 .
+.El
+.Pp
+Some shells, such as
+.Xr csh 1
+and
+.Xr ksh 1 ,
+have their own and syntactically different built-in version of
+.Nm .
+The utility described here
+is available as
+.Pa /usr/bin/time
+to users of these shells.
+.Ss Resource Utilization
+If the
+.Fl l
+option is given, the following resource usage
+information is displayed
+in addition to the timing information:
+.Bl -item -offset indent -compact
+.It
+maximum resident set size
+.It
+average shared memory size
+.It
+average unshared data size
+.It
+average unshared stack size
+.It
+page reclaims
+.It
+page faults
+.It
+swaps
+.It
+block input operations
+.It
+block output operations
+.It
+messages sent
+.It
+messages received
+.It
+signals received
+.It
+voluntary context switches
+.It
+involuntary context switches
+.El
+Resource usage is the total for the execution of
+.Ar command
+and any child processes it spawns, as per
+.Xr wait4 2 .
+.Sh FILES
+.Bl -tag -width Xsys/resource.hX -compact
+.It Aq sys/resource.h
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Bl -tag -width indent
+.It 1-125
+An error occurred in the
+.Nm
+utility.
+.It 126
+The
+.Ar command
+was found but could not be invoked.
+.It 127
+The
+.Ar command
+could not be found.
+.El
+.Pp
+Otherwise, the exit status of
+.Nm
+will be that of
+.Ar command .
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr ksh 1 ,
+.Xr clock_gettime 2 ,
+.Xr getrusage 2 ,
+.Xr wait4 2
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
+.Sh BUGS
+The granularity of seconds on microprocessors is crude and
+can result in times being reported for CPU usage which are too large by
+a second.
diff --git a/usr.bin/time/time.c b/usr.bin/time/time.c
new file mode 100644
index 0000000..263db9a
--- /dev/null
+++ b/usr.bin/time/time.c
@@ -0,0 +1,215 @@
+/* $NetBSD: time.c,v 1.23 2017/07/15 14:34:08 christos Exp $ */
+
+/*
+ * Copyright (c) 1987, 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1987, 1988, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)time.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: time.c,v 1.23 2017/07/15 14:34:08 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <err.h>
+#include <locale.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "ext.h"
+
+__dead static void usage(void);
+static void prl(long, const char *);
+static void prts(const char *, const char *, const struct timespec *,
+ const char *);
+static void prtv(const char *, const char *, const struct timeval *,
+ const char *);
+
+int
+main(int argc, char ** volatile argv)
+{
+ int pid;
+ int ch, status;
+ int volatile portableflag;
+ int volatile lflag;
+ const char *decpt;
+ const char *fmt;
+ const struct lconv *lconv;
+ struct timespec before, after;
+ struct rusage ru;
+
+ (void)setlocale(LC_ALL, "");
+
+ lflag = portableflag = 0;
+ fmt = NULL;
+ while ((ch = getopt(argc, argv, "cf:lp")) != -1) {
+ switch (ch) {
+ case 'f':
+ fmt = optarg;
+ portableflag = 0;
+ lflag = 0;
+ break;
+ case 'c':
+ fmt = "%Uu %Ss %E %P %X+%Dk %I+%Oio %Fpf+%Ww";
+ portableflag = 0;
+ lflag = 0;
+ break;
+ case 'p':
+ portableflag = 1;
+ fmt = NULL;
+ lflag = 0;
+ break;
+ case 'l':
+ lflag = 1;
+ portableflag = 0;
+ fmt = NULL;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ (void)clock_gettime(CLOCK_MONOTONIC, &before);
+ switch(pid = vfork()) {
+ case -1: /* error */
+ err(EXIT_FAILURE, "Vfork failed");
+ /* NOTREACHED */
+ case 0: /* child */
+ /* LINTED will return only on failure */
+ execvp(*argv, argv);
+ err((errno == ENOENT) ? 127 : 126, "Can't exec `%s'", *argv);
+ /* NOTREACHED */
+ }
+
+ /* parent */
+ (void)signal(SIGINT, SIG_IGN);
+ (void)signal(SIGQUIT, SIG_IGN);
+ if ((pid = wait4(pid, &status, 0, &ru)) == -1)
+ err(EXIT_FAILURE, "wait4 %d failed", pid);
+ (void)clock_gettime(CLOCK_MONOTONIC, &after);
+ if (!WIFEXITED(status))
+ warnx("Command terminated abnormally.");
+ timespecsub(&after, &before, &after);
+
+ if ((lconv = localeconv()) == NULL ||
+ (decpt = lconv->decimal_point) == NULL)
+ decpt = ".";
+
+ if (fmt) {
+ static struct rusage null_ru;
+ before.tv_sec = 0;
+ before.tv_nsec = 0;
+ prusage1(stderr, fmt, &null_ru, &ru, &after, &before);
+ } else if (portableflag) {
+ prts("real ", decpt, &after, "\n");
+ prtv("user ", decpt, &ru.ru_utime, "\n");
+ prtv("sys ", decpt, &ru.ru_stime, "\n");
+ } else {
+ prts("", decpt, &after, " real ");
+ prtv("", decpt, &ru.ru_utime, " user ");
+ prtv("", decpt, &ru.ru_stime, " sys\n");
+ }
+
+ if (lflag) {
+ int hz = (int)sysconf(_SC_CLK_TCK);
+ unsigned long long ticks;
+#define SCALE(x) (long)(ticks ? x / ticks : 0)
+
+ ticks = hz * (ru.ru_utime.tv_sec + ru.ru_stime.tv_sec) +
+ hz * (ru.ru_utime.tv_usec + ru.ru_stime.tv_usec) / 1000000;
+ prl(ru.ru_maxrss, "maximum resident set size");
+ prl(SCALE(ru.ru_ixrss), "average shared memory size");
+ prl(SCALE(ru.ru_idrss), "average unshared data size");
+ prl(SCALE(ru.ru_isrss), "average unshared stack size");
+ prl(ru.ru_minflt, "page reclaims");
+ prl(ru.ru_majflt, "page faults");
+ prl(ru.ru_nswap, "swaps");
+ prl(ru.ru_inblock, "block input operations");
+ prl(ru.ru_oublock, "block output operations");
+ prl(ru.ru_msgsnd, "messages sent");
+ prl(ru.ru_msgrcv, "messages received");
+ prl(ru.ru_nsignals, "signals received");
+ prl(ru.ru_nvcsw, "voluntary context switches");
+ prl(ru.ru_nivcsw, "involuntary context switches");
+ }
+
+ return (WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "Usage: %s [-clp] [-f <fmt>] utility [argument ...]\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
+
+static void
+prl(long val, const char *expn)
+{
+
+ (void)fprintf(stderr, "%10ld %s\n", val, expn);
+}
+
+static void
+prts(const char *pre, const char *decpt, const struct timespec *ts,
+ const char *post)
+{
+
+ (void)fprintf(stderr, "%s%9lld%s%02ld%s", pre, (long long)ts->tv_sec,
+ decpt, (long)ts->tv_nsec / 10000000, post);
+}
+
+static void
+prtv(const char *pre, const char *decpt, const struct timeval *tv,
+ const char *post)
+{
+
+ (void)fprintf(stderr, "%s%9lld%s%02ld%s", pre, (long long)tv->tv_sec,
+ decpt, (long)tv->tv_usec / 10000, post);
+}
diff --git a/usr.bin/time/xtime.c b/usr.bin/time/xtime.c
new file mode 100644
index 0000000..b685d2d
--- /dev/null
+++ b/usr.bin/time/xtime.c
@@ -0,0 +1,9 @@
+/* $NetBSD: xtime.c,v 1.1 2007/02/24 21:30:27 matt Exp $ */
+
+
+
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include "ext.h"
+#include "csh/time.c"
diff --git a/usr.bin/touch/touch.1 b/usr.bin/touch/touch.1
new file mode 100644
index 0000000..d1d1fb4
--- /dev/null
+++ b/usr.bin/touch/touch.1
@@ -0,0 +1,215 @@
+.\" $NetBSD: touch.1,v 1.26 2016/12/24 15:49:18 abhinav Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)touch.1 8.3 (Berkeley) 4/28/95
+.\"
+.Dd December 24, 2016
+.Dt TOUCH 1
+.Os
+.Sh NAME
+.Nm touch
+.Nd change file access and modification times
+.Sh SYNOPSIS
+.Nm
+.Op Fl acfhm
+.Op Fl d Ar human-datetime
+.Op Fl Fl date Ar human-datetime
+.Op Fl r Ar file
+.Op Fl Fl reference Ar file
+.Op Fl t Ar datetime
+.Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility changes the access and modification times of files to the
+current time of day.
+If the file doesn't exist, it is created with default permissions.
+.Pp
+The following options are available:
+.Bl -tag -width "-d human-datetime"
+.It Fl a
+Change the access time of the file.
+The modification time of the file is not changed unless the
+.Fl m
+flag is also specified.
+.It Fl c
+Do not create the file if it does not exist.
+The
+.Nm
+utility does not treat this as an error.
+No error messages are displayed and the exit value is not affected.
+.It Fl d Ar human-datetime
+.It Fl Fl date Ar human-datetime
+Parse
+.Ar human-datetime
+using the human datetime parser
+.Xr parsedate 3 .
+.It Fl f
+This flag has no effect; it is accepted for compatibility reasons.
+.It Fl h
+If
+.Ar file
+is a symbolic link, access and/or modification time of the link is changed.
+This option implies
+.Fl c .
+.It Fl m
+Change the modification time of the file.
+The access time of the file is not changed unless the
+.Fl a
+flag is also specified.
+.It Fl r Ar file
+.It Fl Fl reference Ar file
+Use the access and modifications times from
+.Ar file
+instead of the current time of day.
+.It Fl t Ar datetime
+Change the access and modification times to the specified time.
+The argument
+.Ar datetime
+should be in the form
+.Dq [[CC]YY]MMDDhhmm[.SS]
+where each pair of letters represents the following:
+.Pp
+.Bl -tag -width Ds -compact -offset indent
+.It Ar CC
+The first two digits of the year (the century).
+.It Ar YY
+The second two digits of the year.
+If
+.Dq YY
+is specified, but
+.Dq CC
+is not, a value for
+.Dq YY
+between 69 and 99 results in a
+.Dq CC
+value of 19.
+Otherwise, a
+.Dq CC
+value of 20 is used.
+.It Ar MM
+The month of the year, from 1 to 12.
+.It Ar DD
+The day of the month, from 1 to 31.
+.It Ar hh
+The hour of the day, from 0 to 23.
+.It Ar mm
+The minute of the hour, from 0 to 59.
+.It Ar SS
+The second of the minute, from 0 to 60 (permitting leap seconds).
+If
+.Ar SS
+is 60 and the resulting time,
+as affected by the
+.Ev TZ
+environment variable,
+does not refer to a leap second,
+the resulting time is one second after a time where
+.Ar SS
+is 59.
+If
+.Ar SS
+is not given a value, it is assumed to be zero.
+.El
+.Pp
+If the
+.Dq CC
+and
+.Dq YY
+letter pairs are not specified, the values default to the current
+year.
+If the
+.Dq SS
+letter pair is not specified, the value defaults to 0.
+.El
+.Pp
+The
+.Fl d ,
+.Fl r ,
+and
+.Fl t
+options are mutually exclusive.
+If more than one of these options is present, the last one is used.
+.Sh ENVIRONMENT
+.Bl -tag -width -iTZ
+.It Ev TZ
+The timezone to be used for interpreting the
+.Ar datetime
+argument of the
+.Fl t
+option.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh COMPATIBILITY
+The obsolescent form of
+.Nm ,
+where a time format is specified as the first argument, is supported.
+When no
+.Fl d ,
+.Fl r ,
+or
+.Fl t
+option is specified, there are at least two arguments, and the first
+argument is a string of digits either eight or ten characters in length,
+the first argument is interpreted as a time specification of the form
+.Dq MMDDhhmm[YY] .
+.Pp
+The
+.Dq MM ,
+.Dq DD ,
+.Dq hh
+and
+.Dq mm
+letter pairs are treated as their counterparts specified to the
+.Fl t
+option.
+If the
+.Dq YY
+letter pair is in the range 69 to 99, the year is set to 1969 to 1999,
+otherwise, the year is set in the 21st century.
+.Sh SEE ALSO
+.Xr utimes 2 ,
+.Xr parsedate 3
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be a superset of the
+.St -p1003.2
+specification.
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v7 .
+.Sh BUGS
+A symbolic link can't be a reference file of access and/or modification time.
diff --git a/usr.bin/touch/touch.c b/usr.bin/touch/touch.c
new file mode 100644
index 0000000..1963815
--- /dev/null
+++ b/usr.bin/touch/touch.c
@@ -0,0 +1,337 @@
+/* $NetBSD: touch.c,v 1.33 2015/03/02 03:17:24 enami Exp $ */
+
+/*
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)touch.c 8.2 (Berkeley) 4/28/95";
+#endif
+__RCSID("$NetBSD: touch.c,v 1.33 2015/03/02 03:17:24 enami Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#include <time.h>
+#include <tzfile.h>
+#include <unistd.h>
+#include <util.h>
+#include <getopt.h>
+
+static void stime_arg0(char *, struct timespec *);
+static void stime_arg1(char *, struct timespec *);
+static void stime_arg2(char *, int, struct timespec *);
+static void stime_file(char *, struct timespec *);
+__dead static void usage(void);
+
+struct option touch_longopts[] = {
+ { "date", required_argument, 0,
+ 'd' },
+ { "reference", required_argument, 0,
+ 'r' },
+ { NULL, 0, 0,
+ 0 },
+};
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ struct timespec ts[2];
+ int aflag, cflag, hflag, mflag, ch, fd, len, rval, timeset;
+ char *p;
+ int (*change_file_times)(const char *, const struct timespec *);
+ int (*get_file_status)(const char *, struct stat *);
+
+ setlocale(LC_ALL, "");
+
+ aflag = cflag = hflag = mflag = timeset = 0;
+ if (clock_gettime(CLOCK_REALTIME, &ts[0]))
+ err(1, "clock_gettime");
+
+ while ((ch = getopt_long(argc, argv, "acd:fhmr:t:", touch_longopts,
+ NULL)) != -1)
+ switch(ch) {
+ case 'a':
+ aflag = 1;
+ break;
+ case 'c':
+ cflag = 1;
+ break;
+ case 'd':
+ timeset = 1;
+ stime_arg0(optarg, ts);
+ break;
+ case 'f':
+ break;
+ case 'h':
+ hflag = 1;
+ break;
+ case 'm':
+ mflag = 1;
+ break;
+ case 'r':
+ timeset = 1;
+ stime_file(optarg, ts);
+ break;
+ case 't':
+ timeset = 1;
+ stime_arg1(optarg, ts);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Default is both -a and -m. */
+ if (aflag == 0 && mflag == 0)
+ aflag = mflag = 1;
+
+ if (hflag) {
+ cflag = 1; /* Don't create new file */
+ change_file_times = lutimens;
+ get_file_status = lstat;
+ } else {
+ change_file_times = utimens;
+ get_file_status = stat;
+ }
+
+ /*
+ * If no -r or -t flag, at least two operands, the first of which
+ * is an 8 or 10 digit number, use the obsolete time specification.
+ */
+ if (!timeset && argc > 1) {
+ (void)strtol(argv[0], &p, 10);
+ len = p - argv[0];
+ if (*p == '\0' && (len == 8 || len == 10)) {
+ timeset = 1;
+ stime_arg2(*argv++, len == 10, ts);
+ }
+ }
+
+ /* Otherwise use the current time of day. */
+ if (!timeset)
+ ts[1] = ts[0];
+
+ if (*argv == NULL)
+ usage();
+
+ for (rval = EXIT_SUCCESS; *argv; ++argv) {
+ /* See if the file exists. */
+ if ((*get_file_status)(*argv, &sb)) {
+ if (!cflag) {
+ /* Create the file. */
+ fd = open(*argv,
+ O_WRONLY | O_CREAT, DEFFILEMODE);
+ if (fd == -1 || fstat(fd, &sb) || close(fd)) {
+ rval = EXIT_FAILURE;
+ warn("%s", *argv);
+ continue;
+ }
+
+ /* If using the current time, we're done. */
+ if (!timeset)
+ continue;
+ } else
+ continue;
+ }
+ if (!aflag)
+ ts[0] = sb.st_atimespec;
+ if (!mflag)
+ ts[1] = sb.st_mtimespec;
+
+ /* Try utimes(2). */
+ if (!(*change_file_times)(*argv, ts))
+ continue;
+
+ /* If the user specified a time, nothing else we can do. */
+ if (timeset) {
+ rval = EXIT_FAILURE;
+ warn("%s", *argv);
+ }
+
+ /*
+ * System V and POSIX 1003.1 require that a NULL argument
+ * set the access/modification times to the current time.
+ * The permission checks are different, too, in that the
+ * ability to write the file is sufficient. Take a shot.
+ */
+ if (!(*change_file_times)(*argv, NULL))
+ continue;
+
+ rval = EXIT_FAILURE;
+ warn("%s", *argv);
+ }
+ exit(rval);
+}
+
+#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
+
+static void
+stime_arg0(char *arg, struct timespec *tsp)
+{
+ tsp[1].tv_sec = tsp[0].tv_sec = parsedate(arg, NULL, NULL);
+ if (tsp[0].tv_sec == -1)
+ errx(EXIT_FAILURE, "Could not parse `%s'", arg);
+ tsp[0].tv_nsec = tsp[1].tv_nsec = 0;
+}
+
+static void
+stime_arg1(char *arg, struct timespec *tsp)
+{
+ struct tm *t;
+ time_t tmptime;
+ int yearset;
+ char *p;
+ /* Start with the current time. */
+ tmptime = tsp[0].tv_sec;
+ if ((t = localtime(&tmptime)) == NULL)
+ err(EXIT_FAILURE, "localtime");
+ /* [[CC]YY]MMDDhhmm[.SS] */
+ if ((p = strchr(arg, '.')) == NULL)
+ t->tm_sec = 0; /* Seconds defaults to 0. */
+ else {
+ if (strlen(p + 1) != 2)
+ goto terr;
+ *p++ = '\0';
+ t->tm_sec = ATOI2(p);
+ }
+
+ yearset = 0;
+ switch (strlen(arg)) {
+ case 12: /* CCYYMMDDhhmm */
+ t->tm_year = ATOI2(arg) * 100 - TM_YEAR_BASE;
+ yearset = 1;
+ /* FALLTHROUGH */
+ case 10: /* YYMMDDhhmm */
+ if (yearset) {
+ t->tm_year += ATOI2(arg);
+ } else {
+ yearset = ATOI2(arg);
+ if (yearset < 69)
+ t->tm_year = yearset + 2000 - TM_YEAR_BASE;
+ else
+ t->tm_year = yearset + 1900 - TM_YEAR_BASE;
+ }
+ /* FALLTHROUGH */
+ case 8: /* MMDDhhmm */
+ t->tm_mon = ATOI2(arg);
+ --t->tm_mon; /* Convert from 01-12 to 00-11 */
+ /* FALLTHROUGH */
+ case 6:
+ t->tm_mday = ATOI2(arg);
+ /* FALLTHROUGH */
+ case 4:
+ t->tm_hour = ATOI2(arg);
+ /* FALLTHROUGH */
+ case 2:
+ t->tm_min = ATOI2(arg);
+ break;
+ default:
+ goto terr;
+ }
+
+ t->tm_isdst = -1; /* Figure out DST. */
+ tsp[0].tv_sec = tsp[1].tv_sec = mktime(t);
+ if (tsp[0].tv_sec == -1)
+terr: errx(EXIT_FAILURE,
+ "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
+
+ tsp[0].tv_nsec = tsp[1].tv_nsec = 0;
+}
+
+static void
+stime_arg2(char *arg, int year, struct timespec *tsp)
+{
+ struct tm *t;
+ time_t tmptime;
+ /* Start with the current time. */
+ tmptime = tsp[0].tv_sec;
+ if ((t = localtime(&tmptime)) == NULL)
+ err(EXIT_FAILURE, "localtime");
+
+ t->tm_mon = ATOI2(arg); /* MMDDhhmm[yy] */
+ --t->tm_mon; /* Convert from 01-12 to 00-11 */
+ t->tm_mday = ATOI2(arg);
+ t->tm_hour = ATOI2(arg);
+ t->tm_min = ATOI2(arg);
+ if (year) {
+ year = ATOI2(arg);
+ if (year < 69)
+ t->tm_year = year + 2000 - TM_YEAR_BASE;
+ else
+ t->tm_year = year + 1900 - TM_YEAR_BASE;
+ }
+ t->tm_sec = 0;
+
+ t->tm_isdst = -1; /* Figure out DST. */
+ tsp[0].tv_sec = tsp[1].tv_sec = mktime(t);
+ if (tsp[0].tv_sec == -1)
+ errx(EXIT_FAILURE,
+ "out of range or illegal time specification: MMDDhhmm[yy]");
+
+ tsp[0].tv_nsec = tsp[1].tv_nsec = 0;
+}
+
+static void
+stime_file(char *fname, struct timespec *tsp)
+{
+ struct stat sb;
+
+ if (stat(fname, &sb))
+ err(1, "%s", fname);
+ tsp[0] = sb.st_atimespec;
+ tsp[1] = sb.st_mtimespec;
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "Usage: %s [-acfhm] [-d|--date datetime] [-r|--reference file] [-t time] file ...\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/tput/clear.sh b/usr.bin/tput/clear.sh
new file mode 100644
index 0000000..5fffaf0
--- /dev/null
+++ b/usr.bin/tput/clear.sh
@@ -0,0 +1,38 @@
+#!/bin/sh -
+# $NetBSD: clear.sh,v 1.2 1994/12/07 08:49:09 jtc Exp $
+#
+# Copyright (c) 1989, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# @(#)clear.sh 8.1 (Berkeley) 6/6/93
+#
+
+exec tput clear
diff --git a/usr.bin/tput/tput.1 b/usr.bin/tput/tput.1
new file mode 100644
index 0000000..1ea0bb4
--- /dev/null
+++ b/usr.bin/tput/tput.1
@@ -0,0 +1,139 @@
+.\" $NetBSD: tput.1,v 1.16 2012/04/21 12:27:30 roy Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tput.1 8.2 (Berkeley) 3/19/94
+.\"
+.Dd April 5, 2012
+.Dt TPUT 1
+.Os
+.Sh NAME
+.Nm tput ,
+.Nm clear
+.Nd terminal capability interface
+.Sh SYNOPSIS
+.Nm
+.Op Fl T Ar term
+.Ar attribute
+.Op Ar attribute-args
+.Ar ...
+.Sh DESCRIPTION
+.Nm
+makes terminal-dependent information available to users or shell
+applications.
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl T
+The terminal name as specified in the
+.Xr terminfo 5
+database, for example,
+.Dq vt100
+or
+.Dq xterm .
+If not specified,
+.Nm
+retrieves the
+.Dq Ev TERM
+variable from the environment.
+.El
+.Pp
+.Nm
+outputs a string if the
+.Ar attribute
+is of type string; a number if it is of type integer.
+Otherwise,
+.Nm
+exits 0 if the terminal has the capability and 1 if it does not,
+without further action.
+.Pp
+If the
+.Ar attribute
+is of type string, and takes arguments (e.g. cursor movement,
+the terminfo
+.Dq cup
+sequence) the arguments are taken from the command line immediately
+following the attribute.
+.Pp
+The following special attributes are available:
+.Bl -tag -width Ar
+.It clear
+Clear the screen (the
+.Xr terminfo 5
+.Dq cl
+sequence).
+.It init
+Initialize the terminal (the
+.Xr terminfo 5
+.Dq is2
+sequence).
+.It longname
+Print the descriptive name of the user's terminal type.
+.It reset
+Reset the terminal (the
+.Xr terminfo 5
+.Dq rs1 , rs2 , rs3
+and
+.Dq rf
+sequence).
+.El
+.Sh EXIT STATUS
+The exit status of
+.Nm
+is based on the last attribute specified.
+If the attribute is of type string or of type integer,
+.Nm
+exits 0 if the attribute is defined for this terminal type and 1
+if it is not.
+If the attribute is of type boolean,
+.Nm
+exits 0 if the terminal has this attribute, and 1 if it does not.
+.Nm
+exits 2 if any error occurred.
+.Sh EXAMPLES
+.Bl -tag -width "tput cm 6 11 DC 6" -compact
+.It Li "tput cl cm 5 10"
+clear the screen and goto line 5 column 10
+.Pp
+.It Li "tput cm 6 11 DC 6"
+goto line 6 column 11 and delete 6 characters
+.El
+.Sh SEE ALSO
+.Xr termcap 3 ,
+.Xr terminfo 3 ,
+.Xr terminfo 5
+.Sh HISTORY
+The
+.Nm
+command appears in
+.Bx 4.4 .
+.Sh BUGS
+.Nm
+can't really distinguish between different types of attributes.
+.Pp
+Not all terminfo entries contain the reset sequence, so using the init
+sequence may be more useful.
diff --git a/usr.bin/tput/tput.c b/usr.bin/tput/tput.c
new file mode 100644
index 0000000..0e8da2f
--- /dev/null
+++ b/usr.bin/tput/tput.c
@@ -0,0 +1,195 @@
+/* $NetBSD: tput.c,v 1.26 2013/02/05 11:31:56 roy Exp $ */
+
+/*-
+ * Copyright (c) 1980, 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1980, 1988, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)tput.c 8.3 (Berkeley) 4/28/95";
+#endif
+__RCSID("$NetBSD: tput.c,v 1.26 2013/02/05 11:31:56 roy Exp $");
+#endif /* not lint */
+
+#include <termios.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <term_private.h>
+#include <term.h>
+#include <unistd.h>
+
+static void usage(void) __dead;
+static char **process(const char *, const char *, char **);
+
+int
+main(int argc, char **argv)
+{
+ int ch, exitval, n;
+ char *term;
+ const char *p, *s;
+ size_t pl;
+
+ term = NULL;
+ while ((ch = getopt(argc, argv, "T:")) != -1)
+ switch(ch) {
+ case 'T':
+ term = optarg;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!term && !(term = getenv("TERM")))
+ errx(2, "No terminal type specified and no TERM "
+ "variable set in the environment.");
+ setupterm(term, 0, NULL);
+ for (exitval = 0; (p = *argv) != NULL; ++argv) {
+ switch (*p) {
+ case 'c':
+ if (!strcmp(p, "clear"))
+ p = "clear";
+ break;
+ case 'i':
+ if (!strcmp(p, "init")) {
+ s = tigetstr("is1");
+ if (s != NULL)
+ putp(s);
+ p = "is2";
+ }
+ break;
+ case 'l':
+ if (!strcmp(p, "longname")) {
+ (void)printf("%s\n", longname());
+ continue;
+ }
+ break;
+ case 'r':
+ if (!strcmp(p, "reset")) {
+ s = tigetstr("rs1");
+ if (s != NULL)
+ putp(s);
+ p = "rs2";
+ }
+ break;
+ }
+ pl = strlen(p);
+ if (((s = tigetstr(p)) != NULL && s != (char *)-1) ||
+ (pl <= 2 && (s = tgetstr(p, NULL)) != NULL))
+ argv = process(p, s, argv);
+ else if ((((n = tigetnum(p)) != -1 && n != -2 ) ||
+ (pl <= 2 && (n = tgetnum(p)) != -1)))
+ (void)printf("%d\n", n);
+ else {
+ exitval = tigetflag(p);
+ if (exitval == -1) {
+ if (pl <= 2)
+ exitval = !tgetflag(p);
+ else
+ exitval = 1;
+ } else
+ exitval = !exitval;
+ }
+
+ if (argv == NULL)
+ break;
+ }
+ return argv ? exitval : 2;
+}
+
+static char **
+process(const char *cap, const char *str, char **argv)
+{
+ static const char errfew[] =
+ "Not enough arguments (%d) for capability `%s'";
+ static const char erresc[] =
+ "Unknown %% escape (%s) for capability `%s'";
+ static const char errnum[] =
+ "Expected a numeric argument [%d] (%s) for capability `%s'";
+ static const char errcharlong[] =
+ "Platform does not fit a string into a long for capability '%s'";
+ int i, nparams, piss[TPARM_MAX];
+ long nums[TPARM_MAX];
+ char *strs[TPARM_MAX], *tmp;
+
+ /* Count how many values we need for this capability. */
+ errno = 0;
+ memset(&piss, 0, sizeof(piss));
+ nparams = _ti_parm_analyse(str, piss, TPARM_MAX);
+ if (errno == EINVAL)
+ errx(2, erresc, str, cap);
+
+ /* Create our arrays of integers and strings */
+ for (i = 0; i < nparams; i++) {
+ if (*++argv == NULL || *argv[0] == '\0')
+ errx(2, errfew, nparams, cap);
+ if (piss[i]) {
+ if (sizeof(char *) > sizeof(long) /* CONSTCOND */)
+ errx(2, errcharlong, cap);
+ strs[i] = *argv;
+ } else {
+ errno = 0;
+ nums[i] = strtol(*argv, &tmp, 0);
+ if ((errno == ERANGE &&
+ (nums[i] == LONG_MIN || nums[i] == LONG_MAX)) ||
+ (errno != 0 && nums[i] == 0) ||
+ tmp == str ||
+ *tmp != '\0')
+ errx(2, errnum, i + 1, *argv, cap);
+ }
+ }
+
+ /* And output */
+#define p(i) (i <= nparams ? \
+ (piss[i - 1] ? (long)strs[i - 1] : nums[i - 1]) : 0)
+ putp(tparm(str, p(1), p(2), p(3), p(4), p(5), p(6), p(7), p(8), p(9)));
+
+ return argv;
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "Usage: %s [-T term] attribute [attribute-args] ...\n",
+ getprogname());
+ exit(2);
+}
diff --git a/usr.bin/tr/extern.h b/usr.bin/tr/extern.h
new file mode 100644
index 0000000..8cb2de9
--- /dev/null
+++ b/usr.bin/tr/extern.h
@@ -0,0 +1,43 @@
+/* $NetBSD: extern.h,v 1.11 2013/08/11 00:39:22 dholland Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ */
+
+struct str;
+typedef struct str STR;
+
+#include <limits.h>
+#define NCHARS (UCHAR_MAX + 1) /* Number of possible characters. */
+#define OOBCH (UCHAR_MAX + 1) /* Out of band character value. */
+
+STR *str_create(int, const char *);
+void str_destroy(STR *);
+int next(STR *, int *);
diff --git a/usr.bin/tr/str.c b/usr.bin/tr/str.c
new file mode 100644
index 0000000..a9dccde
--- /dev/null
+++ b/usr.bin/tr/str.c
@@ -0,0 +1,453 @@
+/* $NetBSD: str.c,v 1.30 2018/05/26 11:20:30 leot Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)str.c 8.2 (Berkeley) 4/28/95";
+#endif
+__RCSID("$NetBSD: str.c,v 1.30 2018/05/26 11:20:30 leot Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "extern.h"
+
+struct str {
+ enum { STRING1, STRING2 } which;
+ enum { EOS, INFINITE, NORMAL, RANGE, SEQUENCE, SET } state;
+ int cnt; /* character count */
+ int lastch; /* last character */
+ int equiv[2]; /* equivalence set */
+ int *set; /* set of characters */
+ const char *str; /* user's string */
+};
+
+static int backslash(STR *);
+static int bracket(STR *);
+static int c_class(const void *, const void *);
+static int *genclass(const char *, size_t);
+static void genequiv(STR *);
+static int genrange(STR *);
+static void genseq(STR *);
+
+STR *
+str_create(int whichstring, const char *txt)
+{
+ STR *s;
+
+ s = malloc(sizeof(*s));
+ if (s == NULL) {
+ err(1, "Out of memory");
+ }
+
+ s->which = whichstring == 2 ? STRING2 : STRING1;
+ s->state = NORMAL;
+ s->cnt = 0;
+ s->lastch = OOBCH;
+ s->equiv[0] = 0;
+ s->equiv[1] = OOBCH;
+ s->set = NULL;
+ s->str = txt;
+
+ return s;
+}
+
+void
+str_destroy(STR *s)
+{
+ if (s->set != NULL && s->set != s->equiv) {
+ free(s->set);
+ }
+ free(s);
+}
+
+int
+next(STR *s, int *ret)
+{
+ int ch;
+
+ switch (s->state) {
+ case EOS:
+ *ret = s->lastch;
+ return 0;
+ case INFINITE:
+ *ret = s->lastch;
+ return 1;
+ case NORMAL:
+ ch = (unsigned char)s->str[0];
+ switch (ch) {
+ case '\0':
+ s->state = EOS;
+ *ret = s->lastch;
+ return 0;
+ case '\\':
+ s->lastch = backslash(s);
+ break;
+ case '[':
+ if (bracket(s)) {
+ return next(s, ret);
+ }
+ /* FALLTHROUGH */
+ default:
+ ++s->str;
+ s->lastch = ch;
+ break;
+ }
+
+ /* We can start a range at any time. */
+ if (s->str[0] == '-' && genrange(s)) {
+ return next(s, ret);
+ }
+ *ret = s->lastch;
+ return 1;
+ case RANGE:
+ if (s->cnt == 0) {
+ s->state = NORMAL;
+ return next(s, ret);
+ }
+ s->cnt--;
+ ++s->lastch;
+ *ret = s->lastch;
+ return 1;
+ case SEQUENCE:
+ if (s->cnt == 0) {
+ s->state = NORMAL;
+ return next(s, ret);
+ }
+ s->cnt--;
+ *ret = s->lastch;
+ return 1;
+ case SET:
+ s->lastch = s->set[s->cnt++];
+ if (s->lastch == OOBCH) {
+ s->state = NORMAL;
+ if (s->set != s->equiv) {
+ free(s->set);
+ }
+ s->set = NULL;
+ return next(s, ret);
+ }
+ *ret = s->lastch;
+ return 1;
+ }
+ /* NOTREACHED */
+ assert(0);
+ *ret = s->lastch;
+ return 0;
+}
+
+static int
+bracket(STR *s)
+{
+ const char *p;
+ int *q;
+
+ switch (s->str[1]) {
+ case ':': /* "[:class:]" */
+ if ((p = strstr(s->str + 2, ":]")) == NULL)
+ return 0;
+ s->str += 2;
+ q = genclass(s->str, p - s->str);
+ s->state = SET;
+ s->set = q;
+ s->cnt = 0;
+ s->str = p + 2;
+ return 1;
+ case '=': /* "[=equiv=]" */
+ if ((p = strstr(s->str + 2, "=]")) == NULL)
+ return 0;
+ s->str += 2;
+ genequiv(s);
+ s->str = p + 2;
+ return 1;
+ default: /* "[\###*n]" or "[#*n]" */
+ if ((p = strpbrk(s->str + 2, "*]")) == NULL)
+ return 0;
+ if (p[0] != '*' || strchr(p, ']') == NULL)
+ return 0;
+ s->str += 1;
+ genseq(s);
+ return 1;
+ }
+ /* NOTREACHED */
+}
+
+typedef struct {
+ const char *name;
+ int (*func)(int);
+} CLASS;
+
+static const CLASS classes[] = {
+ { "alnum", isalnum },
+ { "alpha", isalpha },
+ { "blank", isblank },
+ { "cntrl", iscntrl },
+ { "digit", isdigit },
+ { "graph", isgraph },
+ { "lower", islower },
+ { "print", isprint },
+ { "punct", ispunct },
+ { "space", isspace },
+ { "upper", isupper },
+ { "xdigit", isxdigit },
+};
+
+typedef struct {
+ const char *name;
+ size_t len;
+} CLASSKEY;
+
+static int *
+genclass(const char *class, size_t len)
+{
+ int ch;
+ const CLASS *cp;
+ CLASSKEY key;
+ int *p;
+ unsigned pos, num;
+
+ /* Find the class */
+ key.name = class;
+ key.len = len;
+ cp = bsearch(&key, classes, __arraycount(classes), sizeof(classes[0]),
+ c_class);
+ if (cp == NULL) {
+ errx(1, "unknown class %.*s", (int)len, class);
+ }
+
+ /*
+ * Figure out what characters are in the class
+ */
+
+ num = NCHARS + 1;
+ p = malloc(num * sizeof(*p));
+ if (p == NULL) {
+ err(1, "malloc");
+ }
+
+ pos = 0;
+ for (ch = 0; ch < NCHARS; ch++) {
+ if (cp->func(ch)) {
+ p[pos++] = ch;
+ }
+ }
+
+ p[pos++] = OOBCH;
+ for (; pos < num; pos++) {
+ p[pos] = 0;
+ }
+
+ return p;
+}
+
+static int
+c_class(const void *av, const void *bv)
+{
+ const CLASSKEY *a = av;
+ const CLASS *b = bv;
+ size_t blen;
+ int r;
+
+ blen = strlen(b->name);
+ r = strncmp(a->name, b->name, a->len);
+ if (r != 0) {
+ return r;
+ }
+ if (a->len < blen) {
+ /* someone gave us a prefix of the right name */
+ return -1;
+ }
+ assert(a-> len == blen);
+ return 0;
+}
+
+/*
+ * English doesn't have any equivalence classes, so for now
+ * we just syntax check and grab the character.
+ */
+static void
+genequiv(STR *s)
+{
+ int ch;
+
+ ch = (unsigned char)s->str[0];
+ if (ch == '\\') {
+ s->equiv[0] = backslash(s);
+ } else {
+ s->equiv[0] = ch;
+ s->str++;
+ }
+ if (s->str[0] != '=') {
+ errx(1, "Misplaced equivalence equals sign");
+ }
+ s->str++;
+ if (s->str[0] != ']') {
+ errx(1, "Misplaced equivalence right bracket");
+ }
+ s->str++;
+
+ s->cnt = 0;
+ s->state = SET;
+ s->set = s->equiv;
+}
+
+static int
+genrange(STR *s)
+{
+ int stopval;
+ const char *savestart;
+
+ savestart = s->str++;
+ stopval = s->str[0] == '\\' ? backslash(s) : (unsigned char)*s->str++;
+ if (stopval < (unsigned char)s->lastch) {
+ s->str = savestart;
+ return 0;
+ }
+ s->cnt = stopval - s->lastch + 1;
+ s->state = RANGE;
+ --s->lastch;
+ return 1;
+}
+
+static void
+genseq(STR *s)
+{
+ char *ep;
+
+ if (s->which == STRING1) {
+ errx(1, "Sequences only valid in string2");
+ }
+
+ if (*s->str == '\\') {
+ s->lastch = backslash(s);
+ } else {
+ s->lastch = (unsigned char)*s->str++;
+ }
+ if (*s->str != '*') {
+ errx(1, "Misplaced sequence asterisk");
+ }
+
+ s->str++;
+ switch (s->str[0]) {
+ case '\\':
+ s->cnt = backslash(s);
+ break;
+ case ']':
+ s->cnt = 0;
+ ++s->str;
+ break;
+ default:
+ if (isdigit((unsigned char)s->str[0])) {
+ s->cnt = strtol(s->str, &ep, 0);
+ if (*ep == ']') {
+ s->str = ep + 1;
+ break;
+ }
+ }
+ errx(1, "illegal sequence count");
+ /* NOTREACHED */
+ }
+
+ s->state = s->cnt ? SEQUENCE : INFINITE;
+}
+
+/*
+ * Translate \??? into a character. Up to 3 octal digits, if no digits either
+ * an escape code or a literal character.
+ */
+static int
+backslash(STR *s)
+{
+ int ch, cnt, val;
+
+ cnt = val = 0;
+ for (;;) {
+ /* Consume the character we're already on. */
+ s->str++;
+
+ /* Look at the next character. */
+ ch = (unsigned char)s->str[0];
+ if (!isascii(ch) || !isdigit(ch)) {
+ break;
+ }
+ val = val * 8 + ch - '0';
+ if (++cnt == 3) {
+ /* Enough digits; consume this one and stop */
+ ++s->str;
+ break;
+ }
+ }
+ if (cnt) {
+ /* We saw digits, so return their value */
+ if (val >= OOBCH)
+ errx(1, "Invalid octal character value");
+ return val;
+ }
+ if (ch == '\0') {
+ /* \<end> -> \ */
+ s->state = EOS;
+ return '\\';
+ }
+
+ /* Consume the escaped character */
+ s->str++;
+
+ switch (ch) {
+ case 'a': /* escape characters */
+ return '\7';
+ case 'b':
+ return '\b';
+ case 'e':
+ return '\033';
+ case 'f':
+ return '\f';
+ case 'n':
+ return '\n';
+ case 'r':
+ return '\r';
+ case 't':
+ return '\t';
+ case 'v':
+ return '\13';
+ default: /* \q -> q */
+ return ch;
+ }
+}
diff --git a/usr.bin/tr/tr.1 b/usr.bin/tr/tr.1
new file mode 100644
index 0000000..fa9d121
--- /dev/null
+++ b/usr.bin/tr/tr.1
@@ -0,0 +1,352 @@
+.\" $NetBSD: tr.1,v 1.22 2017/07/03 21:34:22 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tr.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd May 29, 2013
+.Dt TR 1
+.Os
+.Sh NAME
+.Nm tr
+.Nd translate characters
+.Sh SYNOPSIS
+.Nm
+.Op Fl cs
+.Ar string1 string2
+.Nm
+.Op Fl c
+.Fl d
+.Ar string1
+.Nm
+.Op Fl c
+.Fl s
+.Ar string1
+.Nm
+.Op Fl c
+.Fl ds
+.Ar string1 string2
+.Sh DESCRIPTION
+The
+.Nm
+utility copies the standard input to the standard output with substitution
+or deletion of selected characters.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl c
+Complements the set of characters in
+.Ar string1 ;
+that is,
+.Fl c Ar \&ab
+includes every character except for
+.Sq a
+and
+.Sq b .
+.It Fl d
+The
+.Fl d
+option causes characters to be deleted from the input.
+.It Fl s
+The
+.Fl s
+option squeezes multiple occurrences of the characters listed in the last
+operand (either
+.Ar string1
+or
+.Ar string2 )
+in the input into a single instance of the character.
+This occurs after all deletion and translation is completed.
+.El
+.Pp
+In the first synopsis form, the characters in
+.Ar string1
+are translated into the characters in
+.Ar string2
+where the first character in
+.Ar string1
+is translated into the first character in
+.Ar string2
+and so on.
+If
+.Ar string1
+is longer than
+.Ar string2 ,
+the last character found in
+.Ar string2
+is duplicated until
+.Ar string1
+is exhausted.
+.Pp
+In the second synopsis form, the characters in
+.Ar string1
+are deleted from the input.
+.Pp
+In the third synopsis form, the characters in
+.Ar string1
+are compressed as described for the
+.Fl s
+option.
+.Pp
+In the fourth synopsis form, the characters in
+.Ar string1
+are deleted from the input, and the characters in
+.Ar string2
+are compressed as described for the
+.Fl s
+option.
+.Pp
+The following conventions can be used in
+.Ar string1
+and
+.Ar string2
+to specify sets of characters:
+.Bl -tag -width [:equiv:]
+.It character
+Any character not described by one of the following conventions
+represents itself.
+.It \eoctal
+A backslash followed by 1, 2 or 3 octal digits represents a character
+with that encoded value.
+To follow an octal sequence with a digit as a character, left zero-pad
+the octal sequence to the full 3 octal digits.
+.It \echaracter
+A backslash followed by certain special characters maps to special
+values.
+.sp
+.Bl -column cc
+.It \ea <alert character>
+.It \eb <backspace>
+.It \ef <form-feed>
+.It \en <newline>
+.It \er <carriage return>
+.It \et <tab>
+.It \ev <vertical tab>
+.El
+.sp
+A backslash followed by any other character maps to that character.
+.It c-c
+Represents the range of characters between the range endpoints, inclusively.
+.It [:class:]
+Represents all characters belonging to the defined character class.
+Class names are:
+.sp
+.Bl -column xdigit
+.It alnum <alphanumeric characters>
+.It alpha <alphabetic characters>
+.It blank <blank characters>
+.It cntrl <control characters>
+.It digit <numeric characters>
+.It graph <graphic characters>
+.It lower <lower-case alphabetic characters>
+.It print <printable characters>
+.It punct <punctuation characters>
+.It space <space characters>
+.It upper <upper-case characters>
+.It xdigit <hexadecimal characters>
+.El
+.Pp
+.\" All classes may be used in
+.\" .Ar string1 ,
+.\" and in
+.\" .Ar string2
+.\" when both the
+.\" .Fl d
+.\" and
+.\" .Fl s
+.\" options are specified.
+.\" Otherwise, only the classes ``upper'' and ``lower'' may be used in
+.\" .Ar string2
+.\" and then only when the corresponding class (``upper'' for ``lower''
+.\" and vice-versa) is specified in the same relative position in
+.\" .Ar string1 .
+.\" .Pp
+With the exception of the
+.Dq upper
+and
+.Dq lower
+classes, characters in the classes are in unspecified order.
+In the
+.Dq upper
+and
+.Dq lower
+classes, characters are entered in ascending order.
+.Pp
+For specific information as to which ASCII characters are included
+in these classes, see
+.Xr ctype 3
+and related manual pages.
+.It [=equiv=]
+Represents all characters or collating (sorting) elements belonging to
+the same equivalence class as
+.Ar equiv .
+If there is a secondary ordering within the equivalence class, the
+characters are ordered in ascending sequence.
+Otherwise, they are ordered after their encoded values.
+An example of an equivalence class might be
+.Dq \&c
+and
+.Dq \&ch
+in Spanish;
+English has no equivalence classes.
+.It [#*n]
+Represents
+.Ar n
+repeated occurrences of the character represented by
+.Ar # .
+This
+expression is only valid when it occurs in
+.Ar string2 .
+If
+.Ar n
+is omitted or is zero, it is interpreted as large enough to extend the
+.Ar string2
+sequence to the length of
+.Ar string1 .
+If
+.Ar n
+has a leading zero, it is interpreted as an octal value;
+otherwise, it is interpreted as a decimal value.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+The following examples are shown as given to the shell:
+.Pp
+Create a list of the words in
+.Ar file1 ,
+one per line, where a word is taken to be a maximal string of letters:
+.sp
+.D1 Li "tr -cs \*q[:alpha:]\*q \*q\en\*q < file1"
+.sp
+Translate the contents of
+.Ar file1
+to upper-case:
+.sp
+.D1 Li "tr \*q[:lower:]\*q \*q[:upper:]\*q < file1"
+.sp
+Strip out non-printable characters from
+.Ar file1 :
+.sp
+.D1 Li "tr -cd \*q[:print:]\*q < file1"
+.Sh COMPATIBILITY
+.At V
+has historically implemented character ranges using the syntax
+.Dq [c-c]
+instead of the
+.Dq c-c
+used by historic
+.Bx
+implementations and standardized by POSIX.
+.At V
+shell scripts should work under this implementation as long as
+the range is intended to map in another range, i.e. the command
+.Pp
+.Ic "tr [a-z] [A-Z]"
+.Pp
+will work as it will map the
+.Sq \&[
+character in
+.Ar string1
+to the
+.Sq \&[
+character in
+.Ar string2 .
+However, if the shell script is deleting or squeezing characters as in
+the command
+.Pp
+.Ic "tr -d [a-z]"
+.Pp
+the characters
+.Sq \&[
+and
+.Sq \&]
+will be included in the deletion or compression list which would
+not have happened under an historic
+.At V
+implementation.
+Additionally, any scripts that depended on the sequence
+.Dq a-z
+to represent the three characters
+.Sq \&a ,
+.Sq \&- ,
+and
+.Sq \&z
+will have to be rewritten as
+.Dq a\e-z .
+.Pp
+The
+.Nm
+utility has historically not permitted the manipulation of NUL bytes in
+its input and, additionally, stripped NUL's from its input stream.
+This implementation has removed this behavior as a bug.
+.Pp
+The
+.Nm
+utility has historically been extremely forgiving of syntax errors,
+for example, the
+.Fl c
+and
+.Fl s
+options were ignored unless two strings were specified.
+This implementation will not permit illegal syntax.
+.Sh SEE ALSO
+.Xr dd 1 ,
+.Xr sed 1
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
+It should be noted that the feature wherein the last character of
+.Ar string2
+is duplicated if
+.Ar string2
+has less characters than
+.Ar string1
+is permitted by POSIX but is not required.
+Shell scripts attempting to be portable to other POSIX systems should use
+the
+.Dq [#*n]
+convention instead of relying on this behavior.
+.Sh BUGS
+.Nm
+was originally designed to work with
+.Tn US-ASCII .
+Its use with character sets that do not share all the properties of
+.Tn US-ASCII ,
+e.g., a symmetric set of upper and lower case characters
+that can be algorithmically converted one to the other,
+may yield unpredictable results.
+.Pp
+.Nm
+should be internationalized.
diff --git a/usr.bin/tr/tr.c b/usr.bin/tr/tr.c
new file mode 100644
index 0000000..8221880
--- /dev/null
+++ b/usr.bin/tr/tr.c
@@ -0,0 +1,283 @@
+/* $NetBSD: tr.c,v 1.20 2013/08/11 01:54:35 dholland Exp $ */
+
+/*
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1988, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)tr.c 8.2 (Berkeley) 5/4/95";
+#endif
+__RCSID("$NetBSD: tr.c,v 1.20 2013/08/11 01:54:35 dholland Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+static int string1[NCHARS], string2[NCHARS];
+
+static void setup(int *, const char *, int, int);
+__dead static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ int ch, ch2, lastch;
+ int cflag, dflag, sflag, isstring2;
+ STR *s1, *s2;
+
+ cflag = dflag = sflag = 0;
+ while ((ch = getopt(argc, argv, "cds")) != -1)
+ switch (ch) {
+ case 'c':
+ cflag = 1;
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ switch(argc) {
+ case 0:
+ default:
+ usage();
+ /* NOTREACHED */
+ case 1:
+ isstring2 = 0;
+ break;
+ case 2:
+ isstring2 = 1;
+ break;
+ }
+
+ /*
+ * tr -ds [-c] string1 string2
+ * Delete all characters (or complemented characters) in string1.
+ * Squeeze all characters in string2.
+ */
+ if (dflag && sflag) {
+ if (!isstring2)
+ usage();
+
+ setup(string1, argv[0], 1, cflag);
+ setup(string2, argv[1], 2, 0);
+
+ for (lastch = OOBCH; (ch = getchar()) != EOF; )
+ if (!string1[ch] && (!string2[ch] || lastch != ch)) {
+ lastch = ch;
+ (void)putchar(ch);
+ }
+ exit(0);
+ }
+
+ /*
+ * tr -d [-c] string1
+ * Delete all characters (or complemented characters) in string1.
+ */
+ if (dflag) {
+ if (isstring2)
+ usage();
+
+ setup(string1, argv[0], 1, cflag);
+
+ while ((ch = getchar()) != EOF)
+ if (!string1[ch])
+ (void)putchar(ch);
+ exit(0);
+ }
+
+ /*
+ * tr -s [-c] string1
+ * Squeeze all characters (or complemented characters) in string1.
+ */
+ if (sflag && !isstring2) {
+ setup(string1, argv[0], 1, cflag);
+
+ for (lastch = OOBCH; (ch = getchar()) != EOF;)
+ if (!string1[ch] || lastch != ch) {
+ lastch = ch;
+ (void)putchar(ch);
+ }
+ exit(0);
+ }
+
+ /*
+ * tr [-cs] string1 string2
+ * Replace all characters (or complemented characters) in string1 with
+ * the character in the same position in string2. If the -s option is
+ * specified, squeeze all the characters in string2.
+ */
+ if (!isstring2)
+ usage();
+
+ /*
+ * The first and second strings need to be matched up. This
+ * means that if we are doing -c, we need to scan the first
+ * string in advance, complement it, and match *that* against
+ * the second string; otherwise we need to scan them together.
+ */
+
+ if (cflag) {
+ /*
+ * Scan string 1 and complement it. After this,
+ * string1[] contains 0 for chars to leave alone and 1
+ * for chars to translate.
+ */
+ setup(string1, argv[0], 1, cflag);
+ s1 = NULL; /* for safety */
+ /* we will use ch to iterate over string1, so start it */
+ ch = -1;
+ } else {
+ /* Create the scanner for string 1. */
+ s1 = str_create(1, argv[0]);
+ for (ch = 0; ch < NCHARS; ch++) {
+ string1[ch] = ch;
+ }
+ }
+ /* Create the scanner for string 2. */
+ s2 = str_create(2, argv[1]);
+
+ /* Read the first char of string 2 first to make sure there is one. */
+ if (!next(s2, &ch2))
+ errx(1, "empty string2");
+
+ /*
+ * Loop over the chars from string 1. After this loop string1[]
+ * is a mapping from input to output chars.
+ */
+ while (1) {
+ if (cflag) {
+ /*
+ * Try each character in order. For characters we
+ * skip over because we aren't translating them,
+ * set the translation to the identity.
+ */
+ ch++;
+ while (ch < NCHARS && string1[ch] == 0) {
+ if (string1[ch] == 0) {
+ string1[ch] = ch;
+ }
+ ch++;
+ }
+ if (ch == NCHARS) {
+ break;
+ }
+ }
+ else {
+ /* Get the next character from string 1. */
+ if (!next(s1, &ch)) {
+ break;
+ }
+ }
+
+ /* Set the translation to the character from string 2. */
+ string1[ch] = ch2;
+
+ /* Note the characters to squeeze in string2[]. */
+ if (sflag) {
+ string2[ch2] = 1;
+ }
+
+ /*
+ * Get the next character from string 2. If it runs
+ * out, this will keep returning the last character
+ * over and over again.
+ */
+ (void)next(s2, &ch2);
+ }
+
+ /*
+ * Now do it.
+ */
+
+ if (sflag)
+ for (lastch = OOBCH; (ch = getchar()) != EOF;) {
+ ch = string1[ch];
+ if (!string2[ch] || lastch != ch) {
+ lastch = ch;
+ (void)putchar(ch);
+ }
+ }
+ else
+ while ((ch = getchar()) != EOF)
+ (void)putchar(string1[ch]);
+
+ /* Clean up and exit. */
+ if (s1 != NULL) {
+ str_destroy(s1);
+ }
+ str_destroy(s2);
+ exit (0);
+}
+
+static void
+setup(int *string, const char *arg, int whichstring, int cflag)
+{
+ int cnt, *p;
+ int ch;
+ STR *str;
+
+ str = str_create(whichstring, arg);
+ while (next(str, &ch))
+ string[ch] = 1;
+ if (cflag)
+ for (p = string, cnt = NCHARS; cnt--; ++p)
+ *p = !*p;
+ str_destroy(str);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: tr [-cs] string1 string2\n");
+ (void)fprintf(stderr, " tr [-c] -d string1\n");
+ (void)fprintf(stderr, " tr [-c] -s string1\n");
+ (void)fprintf(stderr, " tr [-c] -ds string1 string2\n");
+ exit(1);
+}
diff --git a/usr.bin/true/true.1 b/usr.bin/true/true.1
new file mode 100644
index 0000000..5829560
--- /dev/null
+++ b/usr.bin/true/true.1
@@ -0,0 +1,56 @@
+.\" $NetBSD: true.1,v 1.7 2003/08/07 11:16:48 agc Exp $
+.\"
+.\" Copyright (c) 1983, 1985, 1990 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)true.1 6.4 (Berkeley) 6/27/91
+.\" $NetBSD: true.1,v 1.7 2003/08/07 11:16:48 agc Exp $
+.\"
+.Dd June 27, 1991
+.Dt TRUE 1
+.Os
+.Sh NAME
+.Nm true
+.Nd return true value
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+utility always returns with exit code zero.
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr false 1 ,
+.Xr sh 1
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/true/true.sh b/usr.bin/true/true.sh
new file mode 100644
index 0000000..20f87ef
--- /dev/null
+++ b/usr.bin/true/true.sh
@@ -0,0 +1,2 @@
+#! /bin/sh
+exit 0
diff --git a/usr.bin/tsort/tsort.1 b/usr.bin/tsort/tsort.1
new file mode 100644
index 0000000..061824b
--- /dev/null
+++ b/usr.bin/tsort/tsort.1
@@ -0,0 +1,87 @@
+.\" $NetBSD: tsort.1,v 1.10 2003/08/07 11:16:50 agc Exp $
+.\"
+.\" Copyright (c) 1990, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This manual is derived from one contributed to Berkeley by
+.\" Michael Rendell of Memorial University of Newfoundland.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tsort.1 8.3 (Berkeley) 4/1/94
+.\"
+.Dd April 1, 1994
+.Dt TSORT 1
+.Os
+.Sh NAME
+.Nm tsort
+.Nd topological sort of a directed graph
+.Sh SYNOPSIS
+.Nm
+.Op Fl l
+.Op Fl q
+.Op Ar file
+.Sh DESCRIPTION
+.Nm
+takes a list of pairs of node names representing directed arcs in
+a graph and prints the nodes in topological order on standard output.
+Input is taken from the named
+.Ar file ,
+or from standard input if no file
+is given.
+.Pp
+Node names in the input are separated by white space and there must
+be an even number of node names.
+.Pp
+Presence of a node in a graph can be represented by an arc from the node
+to itself.
+This is useful when a node is not connected to any other nodes.
+.Pp
+If the graph contains a cycle (and therefore cannot be properly sorted),
+one of the arcs in the cycle is ignored and the sort continues.
+Cycles are reported on standard error.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl l
+Search for and display the longest cycle.
+Can take a very long time.
+.It Fl q
+Do not display informational messages about cycles.
+This is primarily
+intended for building libraries, where optimal ordering is not critical,
+and cycles occur often.
+.El
+.Sh SEE ALSO
+.Xr ar 1
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v7 .
+This
+.Nm
+command and manual page are derived from sources contributed to Berkeley by
+Michael Rendell of Memorial University of Newfoundland.
diff --git a/usr.bin/tsort/tsort.c b/usr.bin/tsort/tsort.c
new file mode 100644
index 0000000..3e8d569
--- /dev/null
+++ b/usr.bin/tsort/tsort.c
@@ -0,0 +1,433 @@
+/* $NetBSD: tsort.c,v 1.23 2011/09/06 18:34:37 joerg Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Michael Rendell of Memorial University of Newfoundland.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\
+ The Regents of the University of California. All rights reserved.");
+#if 0
+static char sccsid[] = "@(#)tsort.c 8.3 (Berkeley) 5/4/95";
+#endif
+__RCSID("$NetBSD: tsort.c,v 1.23 2011/09/06 18:34:37 joerg Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <db.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Topological sort. Input is a list of pairs of strings separated by
+ * white space (spaces, tabs, and/or newlines); strings are written to
+ * standard output in sorted order, one per line.
+ *
+ * usage:
+ * tsort [-l] [inputfile]
+ * If no input file is specified, standard input is read.
+ *
+ * Should be compatible with AT&T tsort HOWEVER the output is not identical
+ * (i.e. for most graphs there is more than one sorted order, and this tsort
+ * usually generates a different one then the AT&T tsort). Also, cycle
+ * reporting seems to be more accurate in this version (the AT&T tsort
+ * sometimes says a node is in a cycle when it isn't).
+ *
+ * Michael Rendell, michael@stretch.cs.mun.ca - Feb 26, '90
+ */
+#define HASHSIZE 53 /* doesn't need to be big */
+#define NF_MARK 0x1 /* marker for cycle detection */
+#define NF_ACYCLIC 0x2 /* this node is cycle free */
+#define NF_NODEST 0x4 /* Unreachable */
+
+typedef struct node_str NODE;
+
+struct node_str {
+ NODE **n_prevp; /* pointer to previous node's n_next */
+ NODE *n_next; /* next node in graph */
+ NODE **n_arcs; /* array of arcs to other nodes */
+ int n_narcs; /* number of arcs in n_arcs[] */
+ int n_arcsize; /* size of n_arcs[] array */
+ int n_refcnt; /* # of arcs pointing to this node */
+ int n_flags; /* NF_* */
+ char n_name[1]; /* name of this node */
+};
+
+typedef struct _buf {
+ char *b_buf;
+ int b_bsize;
+} BUF;
+
+static DB *db;
+static NODE *graph, **cycle_buf, **longest_cycle;
+static int debug, longest, quiet;
+
+static void add_arc(char *, char *);
+static void clear_cycle(void);
+static int find_cycle(NODE *, NODE *, int, int);
+static NODE *get_node(char *);
+static void *grow_buf(void *, int);
+static void remove_node(NODE *);
+static void tsort(void);
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ BUF *b;
+ int c, n;
+ FILE *fp;
+ int bsize, ch, nused;
+ BUF bufs[2];
+
+ setprogname(argv[0]);
+
+ fp = NULL;
+ while ((ch = getopt(argc, argv, "dlq")) != -1)
+ switch (ch) {
+ case 'd':
+ debug = 1;
+ break;
+ case 'l':
+ longest = 1;
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ switch (argc) {
+ case 0:
+ fp = stdin;
+ break;
+ case 1:
+ if ((fp = fopen(*argv, "r")) == NULL)
+ err(1, "%s", *argv);
+ break;
+ default:
+ usage();
+ }
+
+ for (b = bufs, n = 2; --n >= 0; b++)
+ b->b_buf = grow_buf(NULL, b->b_bsize = 1024);
+
+ /* parse input and build the graph */
+ for (n = 0, c = getc(fp);;) {
+ while (c != EOF && isspace(c))
+ c = getc(fp);
+ if (c == EOF)
+ break;
+
+ nused = 0;
+ b = &bufs[n];
+ bsize = b->b_bsize;
+ do {
+ b->b_buf[nused++] = c;
+ if (nused == bsize)
+ b->b_buf = grow_buf(b->b_buf, bsize *= 2);
+ c = getc(fp);
+ } while (c != EOF && !isspace(c));
+
+ b->b_buf[nused] = '\0';
+ b->b_bsize = bsize;
+ if (n)
+ add_arc(bufs[0].b_buf, bufs[1].b_buf);
+ n = !n;
+ }
+ (void)fclose(fp);
+ if (n)
+ errx(1, "odd data count");
+
+ /* do the sort */
+ tsort();
+ return(0);
+}
+
+/* double the size of oldbuf and return a pointer to the new buffer. */
+static void *
+grow_buf(void *bp, int size)
+{
+ void *n;
+
+ if ((n = realloc(bp, (u_int)size)) == NULL)
+ err(1, "realloc");
+ bp = n;
+ return (bp);
+}
+
+/*
+ * add an arc from node s1 to node s2 in the graph. If s1 or s2 are not in
+ * the graph, then add them.
+ */
+static void
+add_arc(char *s1, char *s2)
+{
+ NODE *n1;
+ NODE *n2;
+ int bsize, i;
+
+ n1 = get_node(s1);
+
+ if (!strcmp(s1, s2))
+ return;
+
+ n2 = get_node(s2);
+
+ /*
+ * Check if this arc is already here.
+ */
+ for (i = 0; i < n1->n_narcs; i++)
+ if (n1->n_arcs[i] == n2)
+ return;
+ /*
+ * Add it.
+ */
+ if (n1->n_narcs == n1->n_arcsize) {
+ if (!n1->n_arcsize)
+ n1->n_arcsize = 10;
+ bsize = n1->n_arcsize * sizeof(*n1->n_arcs) * 2;
+ n1->n_arcs = grow_buf(n1->n_arcs, bsize);
+ n1->n_arcsize = bsize / sizeof(*n1->n_arcs);
+ }
+ n1->n_arcs[n1->n_narcs++] = n2;
+ ++n2->n_refcnt;
+}
+
+/* Find a node in the graph (insert if not found) and return a pointer to it. */
+static NODE *
+get_node(char *name)
+{
+ DBT data, key;
+ NODE *n;
+
+ if (db == NULL &&
+ (db = dbopen(NULL, O_RDWR, 0, DB_HASH, NULL)) == NULL)
+ err(1, "db: %s", name);
+
+ key.data = name;
+ key.size = strlen(name) + 1;
+
+ switch ((*db->get)(db, &key, &data, 0)) {
+ case 0:
+ (void)memmove(&n, data.data, sizeof(n));
+ return (n);
+ case 1:
+ break;
+ default:
+ case -1:
+ err(1, "db: %s", name);
+ }
+
+ if ((n = malloc(sizeof(NODE) + key.size)) == NULL)
+ err(1, "malloc");
+
+ n->n_narcs = 0;
+ n->n_arcsize = 0;
+ n->n_arcs = NULL;
+ n->n_refcnt = 0;
+ n->n_flags = 0;
+ (void)memmove(n->n_name, name, key.size);
+
+ /* Add to linked list. */
+ if ((n->n_next = graph) != NULL)
+ graph->n_prevp = &n->n_next;
+ n->n_prevp = &graph;
+ graph = n;
+
+ /* Add to hash table. */
+ data.data = &n;
+ data.size = sizeof(n);
+ if ((*db->put)(db, &key, &data, 0))
+ err(1, "db: %s", name);
+ return (n);
+}
+
+
+/*
+ * Clear the NODEST flag from all nodes.
+ */
+static void
+clear_cycle(void)
+{
+ NODE *n;
+
+ for (n = graph; n != NULL; n = n->n_next)
+ n->n_flags &= ~NF_NODEST;
+}
+
+/* do topological sort on graph */
+static void
+tsort(void)
+{
+ NODE *n, *next;
+ int cnt, i;
+
+ while (graph != NULL) {
+ /*
+ * Keep getting rid of simple cases until there are none left,
+ * if there are any nodes still in the graph, then there is
+ * a cycle in it.
+ */
+ do {
+ for (cnt = 0, n = graph; n != NULL; n = next) {
+ next = n->n_next;
+ if (n->n_refcnt == 0) {
+ remove_node(n);
+ ++cnt;
+ }
+ }
+ } while (graph != NULL && cnt);
+
+ if (graph == NULL)
+ break;
+
+ if (!cycle_buf) {
+ /*
+ * Allocate space for two cycle logs - one to be used
+ * as scratch space, the other to save the longest
+ * cycle.
+ */
+ for (cnt = 0, n = graph; n != NULL; n = n->n_next)
+ ++cnt;
+ cycle_buf = malloc((u_int)sizeof(NODE *) * cnt);
+ longest_cycle = malloc((u_int)sizeof(NODE *) * cnt);
+ if (cycle_buf == NULL || longest_cycle == NULL)
+ err(1, "malloc");
+ }
+ for (n = graph; n != NULL; n = n->n_next) {
+ if (!(n->n_flags & NF_ACYCLIC)) {
+ if ((cnt = find_cycle(n, n, 0, 0)) != 0) {
+ if (!quiet) {
+ warnx("cycle in data");
+ for (i = 0; i < cnt; i++)
+ warnx("%s",
+ longest_cycle[i]->n_name);
+ }
+ remove_node(n);
+ clear_cycle();
+ break;
+ } else {
+ /* to avoid further checks */
+ n->n_flags |= NF_ACYCLIC;
+ clear_cycle();
+ }
+ }
+ }
+ if (n == NULL)
+ errx(1, "internal error -- could not find cycle");
+ }
+}
+
+/* print node and remove from graph (does not actually free node) */
+static void
+remove_node(NODE *n)
+{
+ NODE **np;
+ int i;
+
+ (void)printf("%s\n", n->n_name);
+ for (np = n->n_arcs, i = n->n_narcs; --i >= 0; np++)
+ --(*np)->n_refcnt;
+ n->n_narcs = 0;
+ *n->n_prevp = n->n_next;
+ if (n->n_next)
+ n->n_next->n_prevp = n->n_prevp;
+}
+
+
+/* look for the longest? cycle from node from to node to. */
+static int
+find_cycle(NODE *from, NODE *to, int longest_len, int depth)
+{
+ NODE **np;
+ int i, len;
+
+ /*
+ * avoid infinite loops and ignore portions of the graph known
+ * to be acyclic
+ */
+ if (from->n_flags & (NF_NODEST|NF_MARK|NF_ACYCLIC))
+ return (0);
+ from->n_flags |= NF_MARK;
+
+ for (np = from->n_arcs, i = from->n_narcs; --i >= 0; np++) {
+ cycle_buf[depth] = *np;
+ if (*np == to) {
+ if (depth + 1 > longest_len) {
+ longest_len = depth + 1;
+ (void)memcpy(longest_cycle, cycle_buf,
+ longest_len * sizeof(NODE *));
+ }
+ } else {
+ if ((*np)->n_flags & (NF_MARK|NF_ACYCLIC|NF_NODEST))
+ continue;
+ len = find_cycle(*np, to, longest_len, depth + 1);
+
+ if (debug)
+ (void)printf("%*s %s->%s %d\n", depth, "",
+ from->n_name, to->n_name, len);
+
+ if (len == 0)
+ (*np)->n_flags |= NF_NODEST;
+
+ if (len > longest_len)
+ longest_len = len;
+
+ if (len > 0 && !longest)
+ break;
+ }
+ }
+ from->n_flags &= ~NF_MARK;
+ return (longest_len);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: tsort [-lq] [file]\n");
+ exit(1);
+}
diff --git a/usr.bin/tty/tty.1 b/usr.bin/tty/tty.1
new file mode 100644
index 0000000..5b65d66
--- /dev/null
+++ b/usr.bin/tty/tty.1
@@ -0,0 +1,75 @@
+.\" $NetBSD: tty.1,v 1.9 2017/07/03 21:34:22 wiz Exp $
+.\"
+.\" Copyright (c) 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tty.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt TTY 1
+.Os
+.Sh NAME
+.Nm tty
+.Nd return user's terminal name
+.Sh SYNOPSIS
+.Nm
+.Op Fl s
+.Sh DESCRIPTION
+The
+.Nm
+utility writes the name of the terminal attached to standard input
+to standard output.
+The name that is written is the string returned by
+.Xr ttyname 3 .
+If the standard input is not a terminal, the message ``not a tty''
+is written.
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl s
+Don't write the terminal name; only the exit status is affected
+when this option is specified.
+The
+.Fl s
+option is deprecated in favor of the
+.Dq Li "test -t 0"
+command.
+.El
+.Pp
+.Nm
+exits 0 if the standard input is a terminal, 1 if the standard input is
+not a terminal, and >1 if an error occurs.
+.Sh SEE ALSO
+.Xr test 1 ,
+.Xr ttyname 3
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/tty/tty.c b/usr.bin/tty/tty.c
new file mode 100644
index 0000000..c5e5e80
--- /dev/null
+++ b/usr.bin/tty/tty.c
@@ -0,0 +1,81 @@
+/* $NetBSD: tty.c,v 1.8 2011/09/06 18:34:57 joerg Exp $ */
+
+/*
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1988, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)tty.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: tty.c,v 1.8 2011/09/06 18:34:57 joerg Exp $");
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+__dead static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ int ch, sflag;
+ char *t;
+
+ sflag = 0;
+ while ((ch = getopt(argc, argv, "s")) != -1) {
+ switch((char)ch) {
+ case 's':
+ sflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ t = ttyname(STDIN_FILENO);
+ if (!sflag)
+ puts(t ? t : "not a tty");
+ exit(t ? 0 : 1);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: tty [-s]\n");
+ exit(2);
+}
diff --git a/usr.bin/uname/uname.1 b/usr.bin/uname/uname.1
new file mode 100644
index 0000000..8253ea9
--- /dev/null
+++ b/usr.bin/uname/uname.1
@@ -0,0 +1,86 @@
+.\" $NetBSD: uname.1,v 1.12 2005/03/27 18:41:22 peter Exp $
+.\"
+.\" Copyright (c) 1990 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)du.1 6.13 (Berkeley) 6/20/91
+.\" $NetBSD: uname.1,v 1.12 2005/03/27 18:41:22 peter Exp $
+.\"
+.Dd November 9, 1998
+.Dt UNAME 1
+.Os
+.Sh NAME
+.Nm uname
+.Nd Print operating system name
+.Sh SYNOPSIS
+.Nm
+.Op Fl amnprsv
+.Sh DESCRIPTION
+The
+.Nm
+utility writes symbols representing one or more system characteristics
+to the standard output.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl a
+Behave as though all of the options
+.Fl mnrsv
+were specified.
+.It Fl m
+print the machine hardware name.
+.It Fl n
+print the nodename (the nodename may be a name
+that the system is known by to a communications
+network).
+.It Fl p
+print the machine processor architecture name.
+.It Fl r
+print the operating system release.
+.It Fl s
+print the operating system name.
+.It Fl v
+print the operating system version.
+.El
+.Pp
+If no options are specified,
+.Nm
+prints the operating system name as if the
+.Fl s
+option had been specified.
+.Sh SEE ALSO
+.Xr hostname 1 ,
+.Xr machine 1 ,
+.Xr uname 3
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
+The
+.Fl p
+option is an extension to the standard.
diff --git a/usr.bin/uname/uname.c b/usr.bin/uname/uname.c
new file mode 100644
index 0000000..de5e08e
--- /dev/null
+++ b/usr.bin/uname/uname.c
@@ -0,0 +1,159 @@
+/* $NetBSD: uname.c,v 1.11 2011/09/06 18:35:13 joerg Exp $ */
+
+/*
+ * Copyright (c) 1994 Winning Strategies, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Winning Strategies, Inc.
+ * 4. The name of Winning Strategies, Inc. may not be used to endorse or
+ * promote products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: uname.c,v 1.11 2011/09/06 18:35:13 joerg Exp $");
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+
+#include <sys/sysctl.h>
+#include <sys/utsname.h>
+
+__dead static void usage(void);
+
+/* Note that PRINT_MACHINE_ARCH is excluded from PRINT_ALL! */
+#define PRINT_SYSNAME 0x01
+#define PRINT_NODENAME 0x02
+#define PRINT_RELEASE 0x04
+#define PRINT_VERSION 0x08
+#define PRINT_MACHINE 0x10
+#define PRINT_MACHINE_ARCH 0x20
+#define PRINT_ALL \
+ (PRINT_SYSNAME|PRINT_NODENAME|PRINT_RELEASE|PRINT_VERSION|PRINT_MACHINE)
+
+int
+main(int argc, char **argv)
+{
+ struct utsname u;
+ char machine_arch[SYS_NMLN];
+ int c;
+ int space = 0;
+ int print_mask = 0;
+
+ (void)setlocale(LC_ALL, "");
+
+ while ((c = getopt(argc,argv,"amnprsv")) != -1) {
+ switch (c) {
+ case 'a':
+ print_mask |= PRINT_ALL;
+ break;
+ case 'm':
+ print_mask |= PRINT_MACHINE;
+ break;
+ case 'n':
+ print_mask |= PRINT_NODENAME;
+ break;
+ case 'p':
+ print_mask |= PRINT_MACHINE_ARCH;
+ break;
+ case 'r':
+ print_mask |= PRINT_RELEASE;
+ break;
+ case 's':
+ print_mask |= PRINT_SYSNAME;
+ break;
+ case 'v':
+ print_mask |= PRINT_VERSION;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ if (optind != argc) {
+ usage();
+ /* NOTREACHED */
+ }
+
+ if (!print_mask) {
+ print_mask = PRINT_SYSNAME;
+ }
+
+ if (uname(&u) != 0) {
+ err(EXIT_FAILURE, "uname");
+ /* NOTREACHED */
+ }
+ if (print_mask & PRINT_MACHINE_ARCH) {
+ int mib[2] = { CTL_HW, HW_MACHINE_ARCH };
+ size_t len = sizeof (machine_arch);
+
+ if (sysctl(mib, sizeof (mib) / sizeof (mib[0]), machine_arch,
+ &len, NULL, 0) < 0)
+ err(EXIT_FAILURE, "sysctl");
+ }
+
+ if (print_mask & PRINT_SYSNAME) {
+ space++;
+ fputs(u.sysname, stdout);
+ }
+ if (print_mask & PRINT_NODENAME) {
+ if (space++) putchar(' ');
+ fputs(u.nodename, stdout);
+ }
+ if (print_mask & PRINT_RELEASE) {
+ if (space++) putchar(' ');
+ fputs(u.release, stdout);
+ }
+ if (print_mask & PRINT_VERSION) {
+ if (space++) putchar(' ');
+ fputs(u.version, stdout);
+ }
+ if (print_mask & PRINT_MACHINE) {
+ if (space++) putchar(' ');
+ fputs(u.machine, stdout);
+ }
+ if (print_mask & PRINT_MACHINE_ARCH) {
+ if (space++) putchar(' ');
+ fputs(machine_arch, stdout);
+ }
+ putchar('\n');
+
+ exit(EXIT_SUCCESS);
+ /* NOTREACHED */
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: uname [-amnprsv]\n");
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/unexpand/unexpand.c b/usr.bin/unexpand/unexpand.c
new file mode 100644
index 0000000..a9d7279
--- /dev/null
+++ b/usr.bin/unexpand/unexpand.c
@@ -0,0 +1,218 @@
+/* $NetBSD: unexpand.c,v 1.15 2016/02/03 05:32:14 christos Exp $ */
+
+/*-
+ * Copyright (c) 1980, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1980, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)unexpand.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: unexpand.c,v 1.15 2016/02/03 05:32:14 christos Exp $");
+#endif /* not lint */
+
+/*
+ * unexpand - put tabs into a file replacing blanks
+ */
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <err.h>
+#include <util.h>
+
+
+#define DSTOP 8
+static int all;
+static size_t nstops;
+static size_t maxstops;
+static size_t *tabstops;
+
+static void tabify(const char *, size_t);
+static void usage(void) __attribute__((__noreturn__));
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s [-a] [-t tabstop] [file ...]\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ char *ep, *tab;
+ char *line;
+ size_t len;
+ unsigned long i;
+
+ setprogname(argv[0]);
+
+ while ((c = getopt(argc, argv, "at:")) != -1) {
+ switch (c) {
+ case 'a':
+ if (nstops)
+ usage();
+ all++;
+ break;
+ case 't':
+ if (all)
+ usage();
+ while ((tab = strsep(&optarg, ", \t")) != NULL) {
+ if (*tab == '\0')
+ continue;
+ errno = 0;
+ i = strtoul(tab, &ep, 0);
+ if (*ep || (errno == ERANGE && i == ULONG_MAX))
+ errx(EXIT_FAILURE,
+ "Invalid tabstop `%s'", tab);
+ if (nstops >= maxstops) {
+ maxstops += 20;
+ tabstops = erealloc(tabstops, maxstops);
+ }
+ if (nstops && tabstops[nstops - 1] >= (size_t)i)
+ errx(EXIT_FAILURE,
+ "Bad tabstop spec `%s', must be "
+ "greater than the previous `%zu'",
+ tab, tabstops[nstops - 1]);
+ tabstops[nstops++] = i;
+ }
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ for (i = 0; i < nstops; i++)
+ fprintf(stderr, "%lu %zu\n", i, tabstops[i]);
+
+ do {
+ if (argc > 0) {
+ if (freopen(argv[0], "r", stdin) == NULL)
+ err(EXIT_FAILURE, "Cannot open `%s'", argv[0]);
+ argc--, argv++;
+ }
+ while ((line = fgetln(stdin, &len)) != NULL)
+ tabify(line, len);
+ } while (argc > 0);
+ return EXIT_SUCCESS;
+}
+
+static void
+tabify(const char *line, size_t len)
+{
+ const char *e, *p;
+ size_t dcol, ocol, limit, n;
+
+ dcol = ocol = 0;
+ limit = nstops == 0 ? UINT_MAX : tabstops[nstops - 1] - 1;
+ e = line + len;
+ for (p = line; p < e; p++) {
+ if (*p == ' ') {
+ dcol++;
+ continue;
+ } else if (*p == '\t') {
+ if (nstops == 0) {
+ dcol = (1 + dcol / DSTOP) * DSTOP;
+ continue;
+ } else {
+ for (n = 0; n < nstops &&
+ tabstops[n] - 1 < dcol; n++)
+ continue;
+ if (n < nstops - 1 && tabstops[n] - 1 < limit) {
+ dcol = tabstops[n];
+ continue;
+ }
+ }
+ }
+
+ /* Output our tabs */
+ if (nstops == 0) {
+ while (((ocol + DSTOP) / DSTOP) <= (dcol / DSTOP)) {
+ if (dcol - ocol < 2)
+ break;
+ if (putchar('\t') == EOF)
+ goto out;
+ ocol = (1 + ocol / DSTOP) * DSTOP;
+ }
+ } else {
+ for (n = 0; n < nstops && tabstops[n] <= ocol; n++)
+ continue;
+ while (n < nstops && tabstops[n] <= dcol && ocol < dcol
+ && ocol < limit) {
+ if (putchar('\t') == EOF)
+ goto out;
+ ocol = tabstops[n++];
+ }
+ }
+
+ /* Output remaining spaces */
+ while (ocol < dcol && ocol < limit) {
+ if (putchar(' ') == EOF)
+ goto out;
+ ocol++;
+ }
+
+ /* Output our char */
+ if (putchar(*p) == EOF)
+ goto out;
+ if (*p == '\b') {
+ if (ocol > 0) {
+ ocol--;
+ dcol--;
+ }
+ } else {
+ ocol++;
+ dcol++;
+ }
+
+ /* Output remainder of line */
+ if (!all || dcol >= limit) {
+ for (p++; p < e; p++)
+ if (putchar(*p) == EOF)
+ goto out;
+ return;
+ }
+ }
+ return;
+out:
+ err(EXIT_FAILURE, "write failed");
+}
diff --git a/usr.bin/unifdef/unifdef.1 b/usr.bin/unifdef/unifdef.1
new file mode 100644
index 0000000..7319245
--- /dev/null
+++ b/usr.bin/unifdef/unifdef.1
@@ -0,0 +1,347 @@
+.\" $NetBSD: unifdef.1,v 1.13 2017/07/03 21:34:22 wiz Exp $
+.\"
+.\" Copyright (c) 1985, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Dave Yost.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" Portions of this code (support for #if and #elif) are Copyright (c)
+.\" 2002, 2003 Tony Finch.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)unifdef.1 8.2 (Berkeley) 4/1/94
+.\" $dotat: things/unifdef.1,v 1.45 2003/01/20 14:37:08 fanf2 Exp $
+.\" $FreeBSD: src/usr.bin/unifdef/unifdef.1,v 1.19 2003/01/20 12:41:41 fanf Exp $
+.\"
+.Dd June 5, 2009
+.Dt UNIFDEF 1
+.Os
+.Sh NAME
+.Nm unifdef ,
+.Nm unifdefall
+.Nd remove preprocessor conditionals from code
+.Sh SYNOPSIS
+.Nm
+.Op Fl ceklst
+.Op Fl I Ns Ar path
+.Op Fl D Ns Ar sym Ns Op = Ns Ar val
+.Op Fl U Ns Ar sym
+.Op Fl iD Ns Ar sym Ns Op = Ns Ar val
+.Op Fl iU Ns Ar sym
+.Ar ...
+.Op Fl o Ar output
+.Op Ar file
+.Nm unifdefall
+.Op Fl I Ns Ar path
+.Ar ...
+.Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility selectively processes conditional
+.Xr cpp 1
+directives.
+It removes from a file both the directives and any additional text
+that they specify should be removed, while otherwise leaving the
+file alone.
+.Pp
+The
+.Nm
+utility acts on
+.Ic #if , #ifdef , #ifndef , #elif , #else ,
+and
+.Ic #endif
+lines,
+and it understands only the commonly-used subset
+of the expression syntax for
+.Ic #if
+and
+.Ic #elif
+lines.
+It handles
+integer values of symbols defined on the command line,
+the
+.Fn defined
+operator applied to symbols defined or undefined on the command line,
+the operators
+.Ic \&! , < , > , <= , >= , == , != , && , || ,
+and parenthesized expressions.
+Anything that it does not understand is passed through unharmed.
+It only processes
+.Ic #ifdef
+and
+.Ic #ifndef
+directives if the symbol is specified on the command line,
+otherwise they are also passed through unchanged.
+By default, it ignores
+.Ic #if
+and
+.Ic #elif
+lines with constant expressions,
+or they may be processed by specifying the
+.Fl k
+flag on the command line.
+.Pp
+The
+.Nm
+utility also understands just enough about C
+to know when one of the directives is inactive
+because it is inside
+a comment,
+or affected by a backslash-continued line.
+It spots unusually-formatted preprocessor directives
+and knows when the layout is too odd to handle.
+.Pp
+A script called
+.Nm unifdefall
+can be used to remove all conditional
+.Xr cpp 1
+directives from a file.
+It uses
+.Nm Fl s
+and
+.Nm cpp Fl dM
+to get lists of all the controlling symbols
+and their definitions (or lack thereof),
+then invokes
+.Nm
+with appropriate arguments to process the file.
+.Pp
+Available options:
+.Bl -tag -width indent -compact
+.It Fl D Ns Ar sym Ns Op = Ns Ar val
+Specify that a symbol is defined,
+and optionally specify what value to give it
+for the purpose of handling
+.Ic #if
+and
+.Ic #elif
+directives.
+.It Fl U Ns Ar sym
+Specify that a symbol is undefined.
+If the same symbol appears in more than one argument,
+the last occurrence dominates.
+.It Fl c
+If the
+.Fl c
+flag is specified,
+then the operation of
+.Nm
+is complemented,
+i.e., the lines that would have been removed or blanked
+are retained and vice versa.
+.It Fl e
+Because
+.Nm
+processes its input one line at a time,
+it cannot remove preprocessor directives that span more than one line.
+The most common example of this is a directive with a multi-line
+comment hanging off its right hand end.
+By default,
+if
+.Nm
+has to process such a directive,
+it will complain that the line is too obfuscated.
+The
+.Fl e
+option changes the behaviour so that,
+where possible,
+such lines are left unprocessed instead of reporting an error.
+.It Fl k
+Process
+.Ic #if
+and
+.Ic #elif
+lines with constant expressions.
+By default, sections controlled by such lines are passed through unchanged
+because they typically start
+.Dq Li "#if 0"
+and are used as a kind of comment to sketch out future or past development.
+It would be rude to strip them out, just as it would be for normal comments.
+.It Fl l
+Replace removed lines with blank lines
+instead of deleting them.
+.It Fl o Ar output
+The argument given is the name of an
+.Ar output
+file to be used instead of the standard output.
+This file can be the same as the input file.
+.It Fl s
+Instead of processing the input file as usual,
+this option causes
+.Nm
+to produce a list of symbols that appear in expressions
+that
+.Nm
+understands.
+It is useful in conjunction with the
+.Fl dM
+option of
+.Xr cpp 1
+for creating
+.Nm
+command lines.
+.It Fl t
+Disables parsing for C comments
+and line continuations,
+which is useful
+for plain text.
+.It Fl iD Ns Ar sym Ns Op = Ns Ar val
+.It Fl iU Ns Ar sym
+Ignore
+.Ic #ifdef Ns s .
+If your C code uses
+.Ic #ifdef Ns s
+to delimit non-C lines,
+such as comments
+or code which is under construction,
+then you must tell
+.Nm
+which symbols are used for that purpose so that it will not try to parse
+comments
+and line continuations
+inside those
+.Ic #ifdef Ns s .
+One specifies ignored symbols with
+.Fl iD Ns Ar sym Ns Oo = Ns Ar val Oc
+and
+.Fl iU Ns Ar sym
+similar to
+.Fl D Ns Ar sym Ns Op = Ns Ar val
+and
+.Fl U Ns Ar sym
+above.
+.It Fl I Ns Ar path
+Specifies to
+.Nm unifdefall
+an additional place to look for
+.Ic #include
+files.
+This option is ignored by
+.Nm
+for compatibility with
+.Xr cpp 1
+and to simplify the implementation of
+.Nm unifdefall .
+.El
+.Pp
+The
+.Nm
+utility copies its output to
+.Em stdout
+and will take its input from
+.Em stdin
+if no
+.Ar file
+argument is given.
+.Pp
+The
+.Nm
+utility works nicely with the
+.Fl D Ns Ar sym
+option of
+.Xr diff 1 .
+.Sh DIAGNOSTICS
+.Bl -item
+.It
+Too many levels of nesting.
+.It
+Inappropriate
+.Ic #elif ,
+.Ic #else
+or
+.Ic #endif .
+.It
+Obfuscated preprocessor control line.
+.It
+Premature
+.Dv EOF
+(with the line number of the most recent unterminated
+.Ic #if ) .
+.It
+.Dv EOF
+in comment.
+.El
+.Pp
+The
+.Nm
+utility exits 0 if the output is an exact copy of the input,
+1 if not, and 2 if in trouble.
+.Sh SEE ALSO
+.Xr cpp 1 ,
+.Xr diff 1
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.3 .
+.Tn ANSI\~C
+support was added in
+.Fx 4.7 .
+.Sh BUGS
+Expression evaluation is very limited.
+.Pp
+Preprocessor control lines split across more than one physical line
+(because of comments or backslash-newline)
+cannot be handled in every situation.
+.Pp
+Trigraphs are not recognized.
+.Pp
+There is no support for symbols with different definitions at
+different points in the source file.
+.Pp
+The text-mode and ignore functionality does not correspond to modern
+.Xr cpp 1
+behaviour.
diff --git a/usr.bin/unifdef/unifdef.c b/usr.bin/unifdef/unifdef.c
new file mode 100644
index 0000000..702e3ec
--- /dev/null
+++ b/usr.bin/unifdef/unifdef.c
@@ -0,0 +1,1037 @@
+/* $NetBSD: unifdef.c,v 1.22 2012/10/13 18:26:03 christos Exp $ */
+
+/*
+ * Copyright (c) 1985, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Dave Yost. It was rewritten to support ANSI C by Tony Finch.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 2002, 2003 Tony Finch <dot@dotat.at>
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Dave Yost. It was rewritten to support ANSI C by Tony Finch.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#ifndef lint
+#if 0
+static const char copyright[] =
+"@(#) Copyright (c) 1985, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif
+#ifdef __IDSTRING
+__IDSTRING(Berkeley, "@(#)unifdef.c 8.1 (Berkeley) 6/6/93");
+__IDSTRING(NetBSD, "$NetBSD: unifdef.c,v 1.22 2012/10/13 18:26:03 christos Exp $");
+__IDSTRING(dotat, "$dotat: things/unifdef.c,v 1.161 2003/07/01 15:32:48 fanf2 Exp $");
+#endif
+#endif /* not lint */
+#ifdef __FBSDID
+__FBSDID("$FreeBSD: src/usr.bin/unifdef/unifdef.c,v 1.18 2003/07/01 15:30:43 fanf Exp $");
+#endif
+
+/*
+ * unifdef - remove ifdef'ed lines
+ *
+ * Wishlist:
+ * provide an option which will append the name of the
+ * appropriate symbol after #else's and #endif's
+ * provide an option which will check symbols after
+ * #else's and #endif's to see that they match their
+ * corresponding #ifdef or #ifndef
+ * generate #line directives in place of deleted code
+ *
+ * The first two items above require better buffer handling, which would
+ * also make it possible to handle all "dodgy" directives correctly.
+ */
+
+#include <ctype.h>
+#include <err.h>
+#include <libgen.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include "stdbool.h"
+
+/* types of input lines: */
+typedef enum {
+ LT_TRUEI, /* a true #if with ignore flag */
+ LT_FALSEI, /* a false #if with ignore flag */
+ LT_IF, /* an unknown #if */
+ LT_TRUE, /* a true #if */
+ LT_FALSE, /* a false #if */
+ LT_ELIF, /* an unknown #elif */
+ LT_ELTRUE, /* a true #elif */
+ LT_ELFALSE, /* a false #elif */
+ LT_ELSE, /* #else */
+ LT_ENDIF, /* #endif */
+ LT_DODGY, /* flag: directive is not on one line */
+ LT_DODGY_LAST = LT_DODGY + LT_ENDIF,
+ LT_PLAIN, /* ordinary line */
+ LT_EOF, /* end of file */
+ LT_COUNT
+} Linetype;
+
+static char const * const linetype_name[] = {
+ "TRUEI", "FALSEI", "IF", "TRUE", "FALSE",
+ "ELIF", "ELTRUE", "ELFALSE", "ELSE", "ENDIF",
+ "DODGY TRUEI", "DODGY FALSEI",
+ "DODGY IF", "DODGY TRUE", "DODGY FALSE",
+ "DODGY ELIF", "DODGY ELTRUE", "DODGY ELFALSE",
+ "DODGY ELSE", "DODGY ENDIF",
+ "PLAIN", "EOF"
+};
+
+/* state of #if processing */
+typedef enum {
+ IS_OUTSIDE,
+ IS_FALSE_PREFIX, /* false #if followed by false #elifs */
+ IS_TRUE_PREFIX, /* first non-false #(el)if is true */
+ IS_PASS_MIDDLE, /* first non-false #(el)if is unknown */
+ IS_FALSE_MIDDLE, /* a false #elif after a pass state */
+ IS_TRUE_MIDDLE, /* a true #elif after a pass state */
+ IS_PASS_ELSE, /* an else after a pass state */
+ IS_FALSE_ELSE, /* an else after a true state */
+ IS_TRUE_ELSE, /* an else after only false states */
+ IS_FALSE_TRAILER, /* #elifs after a true are false */
+ IS_COUNT
+} Ifstate;
+
+static char const * const ifstate_name[] = {
+ "OUTSIDE", "FALSE_PREFIX", "TRUE_PREFIX",
+ "PASS_MIDDLE", "FALSE_MIDDLE", "TRUE_MIDDLE",
+ "PASS_ELSE", "FALSE_ELSE", "TRUE_ELSE",
+ "FALSE_TRAILER"
+};
+
+/* state of comment parser */
+typedef enum {
+ NO_COMMENT = false, /* outside a comment */
+ C_COMMENT, /* in a comment like this one */
+ CXX_COMMENT, /* between // and end of line */
+ STARTING_COMMENT, /* just after slash-backslash-newline */
+ FINISHING_COMMENT /* star-backslash-newline in a C comment */
+} Comment_state;
+
+static char const * const comment_name[] = {
+ "NO", "C", "CXX", "STARTING", "FINISHING"
+};
+
+/* state of preprocessor line parser */
+typedef enum {
+ LS_START, /* only space and comments on this line */
+ LS_HASH, /* only space, comments, and a hash */
+ LS_DIRTY /* this line can't be a preprocessor line */
+} Line_state;
+
+static char const * const linestate_name[] = {
+ "START", "HASH", "DIRTY"
+};
+
+/*
+ * Minimum translation limits from ISO/IEC 9899:1999 5.2.4.1
+ */
+#define MAXDEPTH 64 /* maximum #if nesting */
+#define MAXLINE 4096 /* maximum length of line */
+#define MAXSYMS 4096 /* maximum number of symbols */
+
+/*
+ * Sometimes when editing a keyword the replacement text is longer, so
+ * we leave some space at the end of the tline buffer to accommodate this.
+ */
+#define EDITSLOP 10
+
+/*
+ * Globals.
+ */
+
+static bool complement; /* -c: do the complement */
+static bool debugging; /* -d: debugging reports */
+static bool iocccok; /* -e: fewer IOCCC errors */
+static bool killconsts; /* -k: eval constant #ifs */
+static bool lnblank; /* -l: blank deleted lines */
+static bool symlist; /* -s: output symbol list */
+static bool text; /* -t: this is a text file */
+
+static const char *symname[MAXSYMS]; /* symbol name */
+static const char *value[MAXSYMS]; /* -Dsym=value */
+static bool ignore[MAXSYMS]; /* -iDsym or -iUsym */
+static int nsyms; /* number of symbols */
+
+static FILE *input; /* input file pointer */
+static FILE *output; /* output file pointer */
+static const char *filename; /* input file name */
+static char *ofilename; /* output file name */
+static char tmpname[MAXPATHLEN]; /* used when overwriting */
+static int linenum; /* current line number */
+static int overwriting; /* output overwrites input */
+
+static char tline[MAXLINE+EDITSLOP];/* input buffer plus space */
+static char *keyword; /* used for editing #elif's */
+
+static Comment_state incomment; /* comment parser state */
+static Line_state linestate; /* #if line parser state */
+static Ifstate ifstate[MAXDEPTH]; /* #if processor state */
+static bool ignoring[MAXDEPTH]; /* ignore comments state */
+static int stifline[MAXDEPTH]; /* start of current #if */
+static int depth; /* current #if nesting */
+static bool keepthis; /* don't delete constant #if */
+
+static int exitstat; /* program exit status */
+
+static void addsym(bool, bool, char *);
+static void debug(const char *, ...) __printflike(1, 2);
+__dead static void done(void);
+__dead static void error(const char *);
+static int findsym(const char *);
+static void flushline(bool);
+static Linetype get_line(void);
+static Linetype ifeval(const char **);
+static void ignoreoff(void);
+static void ignoreon(void);
+static void keywordedit(const char *);
+static void nest(void);
+__dead static void process(void);
+static const char *skipcomment(const char *);
+static const char *skipsym(const char *);
+static void state(Ifstate);
+static int strlcmp(const char *, const char *, size_t);
+__dead static void usage(void);
+
+#define endsym(c) (!isalpha((unsigned char)c) && !isdigit((unsigned char)c) && c != '_')
+
+/*
+ * The main program.
+ */
+int
+main(int argc, char *argv[])
+{
+ int opt;
+ struct stat isb, osb;
+
+ while ((opt = getopt(argc, argv, "i:D:U:I:o:cdeklst")) != -1)
+ switch (opt) {
+ case 'i': /* treat stuff controlled by these symbols as text */
+ /*
+ * For strict backwards-compatibility the U or D
+ * should be immediately after the -i but it doesn't
+ * matter much if we relax that requirement.
+ */
+ opt = *optarg++;
+ if (opt == 'D')
+ addsym(true, true, optarg);
+ else if (opt == 'U')
+ addsym(true, false, optarg);
+ else
+ usage();
+ break;
+ case 'D': /* define a symbol */
+ addsym(false, true, optarg);
+ break;
+ case 'U': /* undef a symbol */
+ addsym(false, false, optarg);
+ break;
+ case 'I':
+ /* no-op for compatibility with cpp */
+ break;
+ case 'c': /* treat -D as -U and vice versa */
+ complement = true;
+ break;
+ case 'd':
+ debugging = true;
+ break;
+ case 'e': /* fewer errors from dodgy lines */
+ iocccok = true;
+ break;
+ case 'k': /* process constant #ifs */
+ killconsts = true;
+ break;
+ case 'l': /* blank deleted lines instead of omitting them */
+ lnblank = true;
+ break;
+ case 'o': /* output to a file */
+ ofilename = optarg;
+ break;
+ case 's': /* only output list of symbols that control #ifs */
+ symlist = true;
+ break;
+ case 't': /* don't parse C comments */
+ text = true;
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+ if (nsyms == 0 && !symlist) {
+ warnx("must -D or -U at least one symbol");
+ usage();
+ }
+ if (argc > 1) {
+ errx(2, "can only do one file");
+ } else if (argc == 1 && strcmp(*argv, "-") != 0) {
+ filename = *argv;
+ input = fopen(filename, "r");
+ if (input == NULL)
+ err(2, "can't open %s", filename);
+ } else {
+ filename = "[stdin]";
+ input = stdin;
+ }
+ if (ofilename == NULL) {
+ output = stdout;
+ } else {
+ if (stat(ofilename, &osb) == 0) {
+ if (fstat(fileno(input), &isb) != 0)
+ err(2, "can't fstat %s", filename);
+
+ overwriting = (osb.st_dev == isb.st_dev &&
+ osb.st_ino == isb.st_ino);
+ }
+ if (overwriting) {
+ int ofd;
+
+ snprintf(tmpname, sizeof(tmpname), "%s/unifdef.XXXXXX",
+ dirname(ofilename));
+ if ((ofd = mkstemp(tmpname)) != -1)
+ output = fdopen(ofd, "w+");
+ if (output == NULL)
+ err(2, "can't create temporary file");
+ fchmod(ofd, isb.st_mode & ACCESSPERMS);
+ } else {
+ output = fopen(ofilename, "w");
+ if (output == NULL)
+ err(2, "can't open %s", ofilename);
+ }
+ }
+ process();
+ abort(); /* bug */
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: unifdef [-cdeklst] [-o output]"
+ " [-Dsym[=val]] [-Usym] [-iDsym[=val]] [-iUsym] ... [file]\n");
+ exit(2);
+}
+
+/*
+ * A state transition function alters the global #if processing state
+ * in a particular way. The table below is indexed by the current
+ * processing state and the type of the current line.
+ *
+ * Nesting is handled by keeping a stack of states; some transition
+ * functions increase or decrease the depth. They also maintain the
+ * ignore state on a stack. In some complicated cases they have to
+ * alter the preprocessor directive, as follows.
+ *
+ * When we have processed a group that starts off with a known-false
+ * #if/#elif sequence (which has therefore been deleted) followed by a
+ * #elif that we don't understand and therefore must keep, we edit the
+ * latter into a #if to keep the nesting correct.
+ *
+ * When we find a true #elif in a group, the following block will
+ * always be kept and the rest of the sequence after the next #elif or
+ * #else will be discarded. We edit the #elif into a #else and the
+ * following directive to #endif since this has the desired behaviour.
+ *
+ * "Dodgy" directives are split across multiple lines, the most common
+ * example being a multi-line comment hanging off the right of the
+ * directive. We can handle them correctly only if there is no change
+ * from printing to dropping (or vice versa) caused by that directive.
+ * If the directive is the first of a group we have a choice between
+ * failing with an error, or passing it through unchanged instead of
+ * evaluating it. The latter is not the default to avoid questions from
+ * users about unifdef unexpectedly leaving behind preprocessor directives.
+ */
+typedef void state_fn(void);
+
+/* report an error */
+__dead static void Eelif (void) { error("Inappropriate #elif"); }
+__dead static void Eelse (void) { error("Inappropriate #else"); }
+__dead static void Eendif(void) { error("Inappropriate #endif"); }
+__dead static void Eeof (void) { error("Premature EOF"); }
+__dead static void Eioccc(void) { error("Obfuscated preprocessor control line"); }
+/* plain line handling */
+static void print (void) { flushline(true); }
+static void drop (void) { flushline(false); }
+/* output lacks group's start line */
+static void Strue (void) { drop(); ignoreoff(); state(IS_TRUE_PREFIX); }
+static void Sfalse(void) { drop(); ignoreoff(); state(IS_FALSE_PREFIX); }
+static void Selse (void) { drop(); state(IS_TRUE_ELSE); }
+/* print/pass this block */
+static void Pelif (void) { print(); ignoreoff(); state(IS_PASS_MIDDLE); }
+static void Pelse (void) { print(); state(IS_PASS_ELSE); }
+static void Pendif(void) { print(); --depth; }
+/* discard this block */
+static void Dfalse(void) { drop(); ignoreoff(); state(IS_FALSE_TRAILER); }
+static void Delif (void) { drop(); ignoreoff(); state(IS_FALSE_MIDDLE); }
+static void Delse (void) { drop(); state(IS_FALSE_ELSE); }
+static void Dendif(void) { drop(); --depth; }
+/* first line of group */
+static void Fdrop (void) { nest(); Dfalse(); }
+static void Fpass (void) { nest(); Pelif(); }
+static void Ftrue (void) { nest(); Strue(); }
+static void Ffalse(void) { nest(); Sfalse(); }
+/* variable pedantry for obfuscated lines */
+static void Oiffy (void) { if (iocccok) Fpass(); else Eioccc(); ignoreon(); }
+static void Oif (void) { if (iocccok) Fpass(); else Eioccc(); }
+static void Oelif (void) { if (iocccok) Pelif(); else Eioccc(); }
+/* ignore comments in this block */
+static void Idrop (void) { Fdrop(); ignoreon(); }
+static void Itrue (void) { Ftrue(); ignoreon(); }
+static void Ifalse(void) { Ffalse(); ignoreon(); }
+/* edit this line */
+static void Mpass (void) { strncpy(keyword, "if ", 4); Pelif(); }
+static void Mtrue (void) { keywordedit("else\n"); state(IS_TRUE_MIDDLE); }
+static void Melif (void) { keywordedit("endif\n"); state(IS_FALSE_TRAILER); }
+static void Melse (void) { keywordedit("endif\n"); state(IS_FALSE_ELSE); }
+
+static state_fn * const trans_table[IS_COUNT][LT_COUNT] = {
+/* IS_OUTSIDE */
+{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Eendif,
+ Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Eendif,
+ print, done },
+/* IS_FALSE_PREFIX */
+{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Strue, Sfalse,Selse, Dendif,
+ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Eioccc,Eioccc,Eioccc,Eioccc,
+ drop, Eeof },
+/* IS_TRUE_PREFIX */
+{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Dfalse,Dfalse,Dfalse,Delse, Dendif,
+ Oiffy, Oiffy, Fpass, Oif, Oif, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc,
+ print, Eeof },
+/* IS_PASS_MIDDLE */
+{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Pelif, Mtrue, Delif, Pelse, Pendif,
+ Oiffy, Oiffy, Fpass, Oif, Oif, Pelif, Oelif, Oelif, Pelse, Pendif,
+ print, Eeof },
+/* IS_FALSE_MIDDLE */
+{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Pelif, Mtrue, Delif, Pelse, Pendif,
+ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc,
+ drop, Eeof },
+/* IS_TRUE_MIDDLE */
+{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Melif, Melif, Melif, Melse, Pendif,
+ Oiffy, Oiffy, Fpass, Oif, Oif, Eioccc,Eioccc,Eioccc,Eioccc,Pendif,
+ print, Eeof },
+/* IS_PASS_ELSE */
+{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Pendif,
+ Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Pendif,
+ print, Eeof },
+/* IS_FALSE_ELSE */
+{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Dendif,
+ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Eioccc,
+ drop, Eeof },
+/* IS_TRUE_ELSE */
+{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Dendif,
+ Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Eioccc,
+ print, Eeof },
+/* IS_FALSE_TRAILER */
+{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Dendif,
+ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Eioccc,
+ drop, Eeof }
+/*TRUEI FALSEI IF TRUE FALSE ELIF ELTRUE ELFALSE ELSE ENDIF
+ TRUEI FALSEI IF TRUE FALSE ELIF ELTRUE ELFALSE ELSE ENDIF (DODGY)
+ PLAIN EOF */
+};
+
+/*
+ * State machine utility functions
+ */
+static void
+done(void)
+{
+ if (incomment)
+ error("EOF in comment");
+ if (fclose(output)) {
+ if (overwriting) {
+ unlink(tmpname);
+ errx(2, "%s unchanged", ofilename);
+ }
+ }
+ if (overwriting && rename(tmpname, ofilename)) {
+ unlink(tmpname);
+ errx(2, "%s unchanged", ofilename);
+ }
+ exit(exitstat);
+}
+static void
+ignoreoff(void)
+{
+ ignoring[depth] = ignoring[depth-1];
+}
+static void
+ignoreon(void)
+{
+ ignoring[depth] = true;
+}
+static void
+keywordedit(const char *replacement)
+{
+ strlcpy(keyword, replacement, tline + sizeof(tline) - keyword);
+ print();
+}
+static void
+nest(void)
+{
+ depth += 1;
+ if (depth >= MAXDEPTH)
+ error("Too many levels of nesting");
+ stifline[depth] = linenum;
+}
+static void
+state(Ifstate is)
+{
+ ifstate[depth] = is;
+}
+
+/*
+ * Write a line to the output or not, according to command line options.
+ */
+static void
+flushline(bool keep)
+{
+ if (symlist)
+ return;
+ if (keep ^ complement)
+ fputs(tline, output);
+ else {
+ if (lnblank)
+ putc('\n', output);
+ exitstat = 1;
+ }
+}
+
+/*
+ * The driver for the state machine.
+ */
+static void
+process(void)
+{
+ Linetype lineval;
+
+ for (;;) {
+ linenum++;
+ lineval = get_line();
+ trans_table[ifstate[depth]][lineval]();
+ debug("process %s -> %s depth %d",
+ linetype_name[lineval],
+ ifstate_name[ifstate[depth]], depth);
+ }
+}
+
+/*
+ * Parse a line and determine its type. We keep the preprocessor line
+ * parser state between calls in the global variable linestate, with
+ * help from skipcomment().
+ */
+static Linetype
+get_line(void)
+{
+ const char *cp;
+ int cursym;
+ int kwlen;
+ Linetype retval;
+ Comment_state wascomment;
+
+ if (fgets(tline, MAXLINE, input) == NULL)
+ return (LT_EOF);
+ retval = LT_PLAIN;
+ wascomment = incomment;
+ cp = skipcomment(tline);
+ if (linestate == LS_START) {
+ if (*cp == '#') {
+ linestate = LS_HASH;
+ cp = skipcomment(cp + 1);
+ } else if (*cp != '\0')
+ linestate = LS_DIRTY;
+ }
+ if (!incomment && linestate == LS_HASH) {
+ keyword = tline + (cp - tline);
+ cp = skipsym(cp);
+ kwlen = cp - keyword;
+ /* no way can we deal with a continuation inside a keyword */
+ if (strncmp(cp, "\\\n", 2) == 0)
+ Eioccc();
+ if (strlcmp("ifdef", keyword, kwlen) == 0 ||
+ strlcmp("ifndef", keyword, kwlen) == 0) {
+ cp = skipcomment(cp);
+ if ((cursym = findsym(cp)) < 0)
+ retval = LT_IF;
+ else {
+ retval = (keyword[2] == 'n')
+ ? LT_FALSE : LT_TRUE;
+ if (value[cursym] == NULL)
+ retval = (retval == LT_TRUE)
+ ? LT_FALSE : LT_TRUE;
+ if (ignore[cursym])
+ retval = (retval == LT_TRUE)
+ ? LT_TRUEI : LT_FALSEI;
+ }
+ cp = skipsym(cp);
+ } else if (strlcmp("if", keyword, kwlen) == 0)
+ retval = ifeval(&cp);
+ else if (strlcmp("elif", keyword, kwlen) == 0)
+ retval = ifeval(&cp) - LT_IF + LT_ELIF;
+ else if (strlcmp("else", keyword, kwlen) == 0)
+ retval = LT_ELSE;
+ else if (strlcmp("endif", keyword, kwlen) == 0)
+ retval = LT_ENDIF;
+ else {
+ linestate = LS_DIRTY;
+ retval = LT_PLAIN;
+ }
+ cp = skipcomment(cp);
+ if (*cp != '\0') {
+ linestate = LS_DIRTY;
+ if (retval == LT_TRUE || retval == LT_FALSE ||
+ retval == LT_TRUEI || retval == LT_FALSEI)
+ retval = LT_IF;
+ if (retval == LT_ELTRUE || retval == LT_ELFALSE)
+ retval = LT_ELIF;
+ }
+ if (retval != LT_PLAIN && (wascomment || incomment)) {
+ retval += LT_DODGY;
+ if (incomment)
+ linestate = LS_DIRTY;
+ }
+ }
+ if (linestate == LS_DIRTY) {
+ while (*cp != '\0')
+ cp = skipcomment(cp + 1);
+ }
+ debug("parser %s comment %s line",
+ comment_name[incomment], linestate_name[linestate]);
+ return (retval);
+}
+
+/*
+ * These are the binary operators that are supported by the expression
+ * evaluator. Note that if support for division is added then we also
+ * need short-circuiting booleans because of divide-by-zero.
+ */
+static int op_lt(int a, int b) { return (a < b); }
+static int op_gt(int a, int b) { return (a > b); }
+static int op_le(int a, int b) { return (a <= b); }
+static int op_ge(int a, int b) { return (a >= b); }
+static int op_eq(int a, int b) { return (a == b); }
+static int op_ne(int a, int b) { return (a != b); }
+static int op_or(int a, int b) { return (a || b); }
+static int op_and(int a, int b) { return (a && b); }
+
+/*
+ * An evaluation function takes three arguments, as follows: (1) a pointer to
+ * an element of the precedence table which lists the operators at the current
+ * level of precedence; (2) a pointer to an integer which will receive the
+ * value of the expression; and (3) a pointer to a char* that points to the
+ * expression to be evaluated and that is updated to the end of the expression
+ * when evaluation is complete. The function returns LT_FALSE if the value of
+ * the expression is zero, LT_TRUE if it is non-zero, or LT_IF if the
+ * expression could not be evaluated.
+ */
+struct ops;
+
+typedef Linetype eval_fn(const struct ops *, int *, const char **);
+
+static eval_fn eval_table, eval_unary;
+
+/*
+ * The precedence table. Expressions involving binary operators are evaluated
+ * in a table-driven way by eval_table. When it evaluates a subexpression it
+ * calls the inner function with its first argument pointing to the next
+ * element of the table. Innermost expressions have special non-table-driven
+ * handling.
+ */
+static const struct ops {
+ eval_fn *inner;
+ struct op {
+ const char *str;
+ int (*fn)(int, int);
+ } op[5];
+} eval_ops[] = {
+ { eval_table, { { "||", op_or } } },
+ { eval_table, { { "&&", op_and } } },
+ { eval_table, { { "==", op_eq },
+ { "!=", op_ne } } },
+ { eval_unary, { { "<=", op_le },
+ { ">=", op_ge },
+ { "<", op_lt },
+ { ">", op_gt } } }
+};
+
+/*
+ * Function for evaluating the innermost parts of expressions,
+ * viz. !expr (expr) defined(symbol) symbol number
+ * We reset the keepthis flag when we find a non-constant subexpression.
+ */
+static Linetype
+eval_unary(const struct ops *ops, int *valp, const char **cpp)
+{
+ const char *cp;
+ char *ep;
+ int sym;
+
+ cp = skipcomment(*cpp);
+ if (*cp == '!') {
+ debug("eval%td !", ops - eval_ops);
+ cp++;
+ if (eval_unary(ops, valp, &cp) == LT_IF)
+ return (LT_IF);
+ *valp = !*valp;
+ } else if (*cp == '(') {
+ cp++;
+ debug("eval%td (", ops - eval_ops);
+ if (eval_table(eval_ops, valp, &cp) == LT_IF)
+ return (LT_IF);
+ cp = skipcomment(cp);
+ if (*cp++ != ')')
+ return (LT_IF);
+ } else if (isdigit((unsigned char)*cp)) {
+ debug("eval%td number", ops - eval_ops);
+ *valp = strtol(cp, &ep, 0);
+ cp = skipsym(cp);
+ } else if (strncmp(cp, "defined", 7) == 0 && endsym(cp[7])) {
+ cp = skipcomment(cp+7);
+ debug("eval%td defined", ops - eval_ops);
+ if (*cp++ != '(')
+ return (LT_IF);
+ cp = skipcomment(cp);
+ sym = findsym(cp);
+ if (sym < 0 || symlist)
+ return (LT_IF);
+ *valp = (value[sym] != NULL);
+ cp = skipsym(cp);
+ cp = skipcomment(cp);
+ if (*cp++ != ')')
+ return (LT_IF);
+ keepthis = false;
+ } else if (!endsym(*cp)) {
+ debug("eval%td symbol", ops - eval_ops);
+ sym = findsym(cp);
+ if (sym < 0 || symlist)
+ return (LT_IF);
+ if (value[sym] == NULL)
+ *valp = 0;
+ else {
+ *valp = strtol(value[sym], &ep, 0);
+ if (*ep != '\0' || ep == value[sym])
+ return (LT_IF);
+ }
+ cp = skipsym(cp);
+ keepthis = false;
+ } else {
+ debug("eval%td bad expr", ops - eval_ops);
+ return (LT_IF);
+ }
+
+ *cpp = cp;
+ debug("eval%td = %d", ops - eval_ops, *valp);
+ return (*valp ? LT_TRUE : LT_FALSE);
+}
+
+/*
+ * Table-driven evaluation of binary operators.
+ */
+static Linetype
+eval_table(const struct ops *ops, int *valp, const char **cpp)
+{
+ const struct op *op;
+ const char *cp;
+ int val;
+
+ debug("eval%td", ops - eval_ops);
+ cp = *cpp;
+ if (ops->inner(ops+1, valp, &cp) == LT_IF)
+ return (LT_IF);
+ for (;;) {
+ cp = skipcomment(cp);
+ for (op = ops->op; op->str != NULL; op++)
+ if (strncmp(cp, op->str, strlen(op->str)) == 0)
+ break;
+ if (op->str == NULL)
+ break;
+ cp += strlen(op->str);
+ debug("eval%td %s", ops - eval_ops, op->str);
+ if (ops->inner(ops+1, &val, &cp) == LT_IF)
+ return (LT_IF);
+ *valp = op->fn(*valp, val);
+ }
+
+ *cpp = cp;
+ debug("eval%td = %d", ops - eval_ops, *valp);
+ return (*valp ? LT_TRUE : LT_FALSE);
+}
+
+/*
+ * Evaluate the expression on a #if or #elif line. If we can work out
+ * the result we return LT_TRUE or LT_FALSE accordingly, otherwise we
+ * return just a generic LT_IF.
+ */
+static Linetype
+ifeval(const char **cpp)
+{
+ int ret;
+ int val;
+
+ debug("eval %s", *cpp);
+ keepthis = killconsts ? false : true;
+ ret = eval_table(eval_ops, &val, cpp);
+ debug("eval = %d", val);
+ return (keepthis ? LT_IF : ret);
+}
+
+/*
+ * Skip over comments and stop at the next character position that is
+ * not whitespace. Between calls we keep the comment state in the
+ * global variable incomment, and we also adjust the global variable
+ * linestate when we see a newline.
+ * XXX: doesn't cope with the buffer splitting inside a state transition.
+ */
+static const char *
+skipcomment(const char *cp)
+{
+ if (text || ignoring[depth]) {
+ for (; isspace((unsigned char)*cp); cp++)
+ if (*cp == '\n')
+ linestate = LS_START;
+ return (cp);
+ }
+ while (*cp != '\0')
+ /* don't reset to LS_START after a line continuation */
+ if (strncmp(cp, "\\\n", 2) == 0)
+ cp += 2;
+ else switch (incomment) {
+ case NO_COMMENT:
+ if (strncmp(cp, "/\\\n", 3) == 0) {
+ incomment = STARTING_COMMENT;
+ cp += 3;
+ } else if (strncmp(cp, "/*", 2) == 0) {
+ incomment = C_COMMENT;
+ cp += 2;
+ } else if (strncmp(cp, "//", 2) == 0) {
+ incomment = CXX_COMMENT;
+ cp += 2;
+ } else if (strncmp(cp, "\n", 1) == 0) {
+ linestate = LS_START;
+ cp += 1;
+ } else if (strchr(" \t", *cp) != NULL) {
+ cp += 1;
+ } else
+ return (cp);
+ continue;
+ case CXX_COMMENT:
+ if (strncmp(cp, "\n", 1) == 0) {
+ incomment = NO_COMMENT;
+ linestate = LS_START;
+ }
+ cp += 1;
+ continue;
+ case C_COMMENT:
+ if (strncmp(cp, "*\\\n", 3) == 0) {
+ incomment = FINISHING_COMMENT;
+ cp += 3;
+ } else if (strncmp(cp, "*/", 2) == 0) {
+ incomment = NO_COMMENT;
+ cp += 2;
+ } else
+ cp += 1;
+ continue;
+ case STARTING_COMMENT:
+ if (*cp == '*') {
+ incomment = C_COMMENT;
+ cp += 1;
+ } else if (*cp == '/') {
+ incomment = CXX_COMMENT;
+ cp += 1;
+ } else {
+ incomment = NO_COMMENT;
+ linestate = LS_DIRTY;
+ }
+ continue;
+ case FINISHING_COMMENT:
+ if (*cp == '/') {
+ incomment = NO_COMMENT;
+ cp += 1;
+ } else
+ incomment = C_COMMENT;
+ continue;
+ default:
+ abort(); /* bug */
+ }
+ return (cp);
+}
+
+/*
+ * Skip over an identifier.
+ */
+static const char *
+skipsym(const char *cp)
+{
+ while (!endsym(*cp))
+ ++cp;
+ return (cp);
+}
+
+/*
+ * Look for the symbol in the symbol table. If it is found, we return
+ * the symbol table index, else we return -1.
+ */
+static int
+findsym(const char *str)
+{
+ const char *cp;
+ int symind;
+
+ cp = skipsym(str);
+ if (cp == str)
+ return (-1);
+ if (symlist)
+ printf("%.*s\n", (int)(cp-str), str);
+ for (symind = 0; symind < nsyms; ++symind) {
+ if (strlcmp(symname[symind], str, cp-str) == 0) {
+ debug("findsym %s %s", symname[symind],
+ value[symind] ? value[symind] : "");
+ return (symind);
+ }
+ }
+ return (-1);
+}
+
+/*
+ * Add a symbol to the symbol table.
+ */
+static void
+addsym(bool ignorethis, bool definethis, char *sym)
+{
+ int symind;
+ char *val;
+
+ symind = findsym(sym);
+ if (symind < 0) {
+ if (nsyms >= MAXSYMS)
+ errx(2, "too many symbols");
+ symind = nsyms++;
+ }
+ symname[symind] = sym;
+ ignore[symind] = ignorethis;
+ val = sym + (skipsym(sym) - sym);
+ if (definethis) {
+ if (*val == '=') {
+ value[symind] = val+1;
+ *val = '\0';
+ } else if (*val == '\0')
+ value[symind] = "";
+ else
+ usage();
+ } else {
+ if (*val != '\0')
+ usage();
+ value[symind] = NULL;
+ }
+}
+
+/*
+ * Compare s with n characters of t.
+ * The same as strncmp() except that it checks that s[n] == '\0'.
+ */
+static int
+strlcmp(const char *s, const char *t, size_t n)
+{
+ while (n-- && *t != '\0')
+ if (*s != *t)
+ return ((unsigned char)*s - (unsigned char)*t);
+ else
+ ++s, ++t;
+ return ((unsigned char)*s);
+}
+
+/*
+ * Diagnostics.
+ */
+static void
+debug(const char *msg, ...)
+{
+ va_list ap;
+
+ if (debugging) {
+ va_start(ap, msg);
+ vwarnx(msg, ap);
+ va_end(ap);
+ }
+}
+
+static void
+error(const char *msg)
+{
+ if (depth == 0)
+ warnx("%s: %d: %s", filename, linenum, msg);
+ else
+ warnx("%s: %d: %s (#if line %d depth %d)",
+ filename, linenum, msg, stifline[depth], depth);
+ fclose(output);
+ if (overwriting) {
+ unlink(tmpname);
+ errx(2, "%s unchanged", ofilename);
+ }
+ errx(2, "output may be truncated");
+}
diff --git a/usr.bin/unifdef/unifdefall.sh b/usr.bin/unifdef/unifdefall.sh
new file mode 100644
index 0000000..7c1d643
--- /dev/null
+++ b/usr.bin/unifdef/unifdefall.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# remove all the #if's from a source file
+#
+# $dotat: things/unifdefall.sh,v 1.10 2002/09/24 19:52:11 fanf2 Exp $
+# $FreeBSD: src/usr.bin/unifdef/unifdefall.sh,v 1.2 2002/09/24 19:50:03 fanf Exp $
+
+set -e
+
+basename=`basename $0`
+tmp=`mktemp -d -t $basename` || exit 2
+
+unifdef -s "$@" | sort | uniq > $tmp/ctrl
+cpp -dM "$@" | sort |
+ sed -Ee 's/^#define[ ]+(.*[^ ])[ ]*$/\1/' > $tmp/hashdefs
+sed -Ee 's/^([A-Za-z0-9_]+).*$/\1/' $tmp/hashdefs > $tmp/alldef
+comm -23 $tmp/ctrl $tmp/alldef > $tmp/undef
+comm -12 $tmp/ctrl $tmp/alldef > $tmp/def
+
+echo unifdef -k \\ > $tmp/cmd
+sed -Ee 's/^(.*)$/-U\1 \\/' $tmp/undef >> $tmp/cmd
+while read sym
+do sed -Ee '/^('"$sym"')([(][^)]*[)])?([ ]+(.*))?$/!d;s//-D\1=\4/' $tmp/hashdefs
+done < $tmp/def |
+ sed -Ee 's/\\/\\\\/g;s/"/\\"/g;s/^/"/;s/$/" \\/' >> $tmp/cmd
+echo '"$@"' >> $tmp/cmd
+sh $tmp/cmd "$@"
+
+rm -r $tmp
diff --git a/usr.bin/uniq/uniq.1 b/usr.bin/uniq/uniq.1
new file mode 100644
index 0000000..bf1a78c
--- /dev/null
+++ b/usr.bin/uniq/uniq.1
@@ -0,0 +1,130 @@
+.\" $NetBSD: uniq.1,v 1.13 2012/08/26 14:13:50 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)uniq.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd August 26, 2012
+.Dt UNIQ 1
+.Os
+.Sh NAME
+.Nm uniq
+.Nd report or filter out repeated lines in a file
+.Sh SYNOPSIS
+.Nm
+.Op Fl cdu
+.Op Fl f Ar fields
+.Op Fl s Ar chars
+.Oo
+.Ar input_file
+.Op Ar output_file
+.Oc
+.Sh DESCRIPTION
+The
+.Nm
+utility reads the standard input comparing adjacent lines, and writes
+a copy of each unique input line to the standard output.
+The second and succeeding copies of identical adjacent input lines are
+not written.
+Repeated lines in the input will not be detected if they are not adjacent,
+so it may be necessary to sort the files first.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl c
+Precede each output line with the count of the number of times the line
+occurred in the input, followed by a single space.
+.It Fl d
+Don't output lines that are not repeated in the input.
+.It Fl f Ar fields
+Ignore the first
+.Ar fields
+in each input line when doing comparisons.
+A field is a string of non-blank characters separated from adjacent fields
+by blanks.
+Field numbers are one based, i.e., the first field is field one.
+.It Fl s Ar chars
+Ignore the first
+.Ar chars
+characters in each input line when doing comparisons.
+If specified in conjunction with the
+.Fl f
+option, the first
+.Ar chars
+characters after the first
+.Ar fields
+fields will be ignored.
+Character numbers are one based, i.e., the first character is character one.
+.It Fl u
+Don't output lines that are repeated in the input.
+.\".It Fl Ns Ar n
+.\"(Deprecated; replaced by
+.\".Fl f ) .
+.\"Ignore the first n
+.\"fields on each input line when doing comparisons,
+.\"where n is a number.
+.\"A field is a string of non-blank
+.\"characters separated from adjacent fields
+.\"by blanks.
+.\".It Cm \&\(pl Ns Ar n
+.\"(Deprecated; replaced by
+.\".Fl s ) .
+.\"Ignore the first
+.\".Ar m
+.\"characters when doing comparisons, where
+.\".Ar m
+.\"is a
+.\"number.
+.El
+.Pp
+Additional arguments
+.Ar input_file
+and
+.Ar output_file
+may be specified on the command line,
+where the former is then used as the name of an input file,
+and the latter as the name of an output file.
+.Sh EXIT STATUS
+.Ex -std
+.Sh COMPATIBILITY
+The historic
+.Cm \&\(pl Ns Ar number
+and
+.Fl Ns Ar number
+options have been deprecated but are still supported in this implementation.
+.Sh SEE ALSO
+.Xr sort 1
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
diff --git a/usr.bin/uniq/uniq.c b/usr.bin/uniq/uniq.c
new file mode 100644
index 0000000..ded7037
--- /dev/null
+++ b/usr.bin/uniq/uniq.c
@@ -0,0 +1,266 @@
+/* $NetBSD: uniq.c,v 1.20 2016/10/16 06:17:51 abhinav Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Case Larsen.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)uniq.c 8.3 (Berkeley) 5/4/95";
+#endif
+__RCSID("$NetBSD: uniq.c,v 1.20 2016/10/16 06:17:51 abhinav Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int cflag, dflag, uflag;
+static int numchars, numfields, repeats;
+
+static FILE *file(const char *, const char *);
+static void show(FILE *, const char *);
+static const char *skip(const char *, size_t *);
+static void obsolete(char *[]);
+static void usage(void) __dead;
+
+int
+main (int argc, char *argv[])
+{
+ const char *prevp, *thisp;
+ FILE *ifp, *ofp;
+ int ch;
+ char *prevline, *thisline, *p;
+ size_t prevlinesize, thislinesize, psize;
+ size_t prevlen, thislen;
+
+ setprogname(argv[0]);
+ ifp = ofp = NULL;
+ obsolete(argv);
+ while ((ch = getopt(argc, argv, "-cdf:s:u")) != -1)
+ switch (ch) {
+ case '-':
+ --optind;
+ goto done;
+ case 'c':
+ cflag = 1;
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'f':
+ numfields = strtol(optarg, &p, 10);
+ if (numfields < 0 || *p)
+ errx(1, "illegal field skip value: %s", optarg);
+ break;
+ case 's':
+ numchars = strtol(optarg, &p, 10);
+ if (numchars < 0 || *p)
+ errx(1, "illegal character skip value: %s",
+ optarg);
+ break;
+ case 'u':
+ uflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+
+done: argc -= optind;
+ argv +=optind;
+
+ switch(argc) {
+ case 0:
+ ifp = stdin;
+ ofp = stdout;
+ break;
+ case 1:
+ ifp = file(argv[0], "r");
+ ofp = stdout;
+ break;
+ case 2:
+ ifp = file(argv[0], "r");
+ ofp = file(argv[1], "w");
+ break;
+ default:
+ usage();
+ }
+
+ if ((p = fgetln(ifp, &psize)) == NULL)
+ return 0;
+ prevlinesize = prevlen = psize;
+ if ((prevline = malloc(prevlinesize + 1)) == NULL)
+ err(1, "malloc");
+ (void)memcpy(prevline, p, prevlinesize);
+ prevline[prevlinesize] = '\0';
+
+ if (numfields || numchars)
+ prevp = skip(prevline, &prevlen);
+ else
+ prevp = prevline;
+
+ thislinesize = psize;
+ if ((thisline = malloc(thislinesize + 1)) == NULL)
+ err(1, "malloc");
+
+ while ((p = fgetln(ifp, &psize)) != NULL) {
+ if (psize > thislinesize) {
+ if ((thisline = realloc(thisline, psize + 1)) == NULL)
+ err(1, "realloc");
+ thislinesize = psize;
+ }
+ thislen = psize;
+ (void)memcpy(thisline, p, psize);
+ thisline[psize] = '\0';
+
+ /* If requested get the chosen fields + character offsets. */
+ if (numfields || numchars) {
+ thisp = skip(thisline, &thislen);
+ } else {
+ thisp = thisline;
+ }
+
+ /* If different, print; set previous to new value. */
+ if (thislen != prevlen || strcmp(thisp, prevp)) {
+ char *t;
+ size_t ts;
+
+ show(ofp, prevline);
+ t = prevline;
+ prevline = thisline;
+ thisline = t;
+ ts = prevlinesize;
+ prevlinesize = thislinesize;
+ thislinesize = ts;
+ prevp = thisp;
+ prevlen = thislen;
+ repeats = 0;
+ } else
+ ++repeats;
+ }
+ show(ofp, prevline);
+ free(prevline);
+ free(thisline);
+ return 0;
+}
+
+/*
+ * show --
+ * Output a line depending on the flags and number of repetitions
+ * of the line.
+ */
+static void
+show(FILE *ofp, const char *str)
+{
+
+ if ((dflag && repeats == 0) || (uflag && repeats > 0))
+ return;
+ if (cflag) {
+ (void)fprintf(ofp, "%4d %s", repeats + 1, str);
+ } else {
+ (void)fprintf(ofp, "%s", str);
+ }
+}
+
+static const char *
+skip(const char *str, size_t *linesize)
+{
+ int infield, nchars, nfields;
+ size_t ls = *linesize;
+
+ for (nfields = numfields, infield = 0; nfields && *str; ++str, --ls)
+ if (isspace((unsigned char)*str)) {
+ if (infield) {
+ infield = 0;
+ --nfields;
+ }
+ } else if (!infield)
+ infield = 1;
+ for (nchars = numchars; nchars-- && *str; ++str, --ls)
+ continue;
+ *linesize = ls;
+ return str;
+}
+
+static FILE *
+file(const char *name, const char *mode)
+{
+ FILE *fp;
+
+ if ((fp = fopen(name, mode)) == NULL)
+ err(1, "%s", name);
+ return(fp);
+}
+
+static void
+obsolete(char *argv[])
+{
+ char *ap, *p, *start;
+
+ while ((ap = *++argv) != NULL) {
+ /* Return if "--" or not an option of any form. */
+ if (ap[0] != '-') {
+ if (ap[0] != '+')
+ return;
+ } else if (ap[1] == '-')
+ return;
+ if (!isdigit((unsigned char)ap[1]))
+ continue;
+ /*
+ * Digit signifies an old-style option. Malloc space for dash,
+ * new option and argument.
+ */
+ (void)asprintf(&p, "-%c%s", ap[0] == '+' ? 's' : 'f', ap + 1);
+ if (!p)
+ err(1, "malloc");
+ start = p;
+ *argv = start;
+ }
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: %s [-cdu] [-f fields] [-s chars] "
+ "[input_file [output_file]]\n", getprogname());
+ exit(1);
+}
diff --git a/usr.bin/uudecode/uudecode.c b/usr.bin/uudecode/uudecode.c
new file mode 100644
index 0000000..977ace2
--- /dev/null
+++ b/usr.bin/uudecode/uudecode.c
@@ -0,0 +1,323 @@
+/* $NetBSD: uudecode.c,v 1.28 2013/01/28 19:50:30 apb Exp $ */
+
+/*-
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#if !defined(lint)
+__COPYRIGHT("@(#) Copyright (c) 1983, 1993\
+ The Regents of the University of California. All rights reserved.");
+#if 0
+static char sccsid[] = "@(#)uudecode.c 8.2 (Berkeley) 4/2/94";
+#endif
+__RCSID("$NetBSD: uudecode.c,v 1.28 2013/01/28 19:50:30 apb Exp $");
+#endif /* not lint */
+
+/*
+ * uudecode [file ...]
+ *
+ * create the specified file, decoding as you go.
+ * used with uuencode.
+ */
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifndef NO_BASE64
+#include <netinet/in.h>
+#include <resolv.h>
+#endif
+
+static int decode(char *);
+__dead static void usage(void);
+static int checkend(const char *, const char *, const char *);
+static int base64_decode(void);
+
+static int base64;
+static const char *inputname;
+
+int
+main(int argc, char *argv[])
+{
+ int ch, rval;
+ char *outputname = NULL;
+
+ setlocale(LC_ALL, "");
+ setprogname(argv[0]);
+
+ while ((ch = getopt(argc, argv, "mo:p")) != -1)
+ switch (ch) {
+ case 'm':
+ base64 = 1;
+ break;
+ case 'o':
+ outputname = optarg;
+ break;
+ case 'p':
+ outputname = __UNCONST("/dev/stdout");
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (*argv) {
+ rval = 0;
+ do {
+ if (!freopen(inputname = *argv, "r", stdin)) {
+ warn("%s", *argv);
+ rval = 1;
+ continue;
+ }
+ rval |= decode(outputname);
+ } while (*++argv);
+ } else {
+ inputname = "stdin";
+ rval = decode(outputname);
+ }
+ exit(rval);
+}
+
+/*
+ * Decode one file from stdin. If outputname is not NULL
+ * then it overrides the file name embedded in the input data.
+ */
+static int
+decode(char *outputname)
+{
+ struct passwd *pw;
+ int n;
+ char ch, *p;
+ int n1;
+ long mode;
+ char *fn;
+ char buf[MAXPATHLEN];
+
+ /* search for header line */
+ for (;;) {
+ if (!fgets(buf, sizeof(buf), stdin)) {
+ warnx("%s: no \"%s\" line", inputname, base64 ?
+ "begin-base64" : "begin");
+ return(1);
+ }
+ p = buf;
+ if (strncmp(p, "begin-base64", 12) == 0) {
+ base64 = 1;
+ p += 13;
+ break;
+ } else if (strncmp(p, "begin", 5) == 0) {
+ p += 6;
+ break;
+ } else
+ continue;
+ }
+
+ /* must be followed by an octal mode and a space */
+ mode = strtol(p, &fn, 8);
+ if (fn == (p) || !isspace((unsigned char)*fn) || mode==LONG_MIN || mode==LONG_MAX)
+ {
+ warnx("%s: invalid mode on \"%s\" line", inputname,
+ base64 ? "begin-base64" : "begin");
+ return(1);
+ }
+ /* skip whitespace for file name */
+ while (*fn && isspace((unsigned char)*fn)) fn++;
+ if (*fn == 0) {
+ warnx("%s: no filename on \"%s\" line", inputname,
+ base64 ? "begin-base64" : "begin");
+ return(1);
+ }
+ /* zap newline */
+ for (p = fn; *p && *p != '\n'; p++)
+ ;
+ if (*p) *p = 0;
+
+ /* outputname overrides fn */
+ if (outputname)
+ fn = outputname;
+
+ /* handle ~user/file format */
+ if (*fn == '~') {
+ if (!(p = strchr(fn, '/'))) {
+ warnx("%s: illegal ~user.", inputname);
+ return(1);
+ }
+ *p++ = '\0';
+ if (!(pw = getpwnam(fn + 1))) {
+ warnx("%s: no user %s.", inputname, buf);
+ return(1);
+ }
+ n = strlen(pw->pw_dir);
+ n1 = strlen(p);
+ if (n + n1 + 2 > MAXPATHLEN) {
+ warnx("%s: path too long.", inputname);
+ return(1);
+ }
+ /* make space at beginning of buf by moving end of pathname */
+ memmove(buf + n + 1, p, n1 + 1);
+ memmove(buf, pw->pw_dir, n);
+ buf[n] = '/';
+ fn = buf;
+ }
+
+ if (strcmp(fn, "/dev/stdout") == 0 || strcmp(fn, "-") == 0) {
+ /*
+ * POSIX.1-2008 says that both "-" and "/dev/stdout"
+ * refer to standard output when they appear in the file
+ * header, but only "/dev/stdout" refers to standard
+ * output when it appears as the argument to the "-o"
+ * command line option.
+ *
+ * We handle both special names, regardless of whether
+ * they came from the "-o" option or from the header of
+ * the input stream.
+ */
+ } else {
+ /*
+ * Create output file, and set its mode. POSIX.1-2008
+ * requires the mode to be used exactly, ignoring the
+ * umask and anything else, but we mask it with 0666.
+ */
+ if (freopen(fn, "w", stdout) == NULL ||
+ fchmod(fileno(stdout), mode & 0666) != 0) {
+ warn("%s: %s", fn, inputname);
+ return(1);
+ }
+ }
+
+ if (base64)
+ return base64_decode();
+ else {
+ /* for each input line */
+ for (;;) {
+ if (!fgets(p = buf, sizeof(buf), stdin)) {
+ warnx("%s: short file.", inputname);
+ return(1);
+ }
+#define DEC(c) (((c) - ' ') & 077) /* single character decode */
+ /*
+ * `n' is used to avoid writing out all the characters
+ * at the end of the file.
+ */
+ if ((n = DEC(*p)) <= 0)
+ break;
+ for (++p; n > 0; p += 4, n -= 3)
+ if (n >= 3) {
+ ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
+ putchar(ch);
+ ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
+ putchar(ch);
+ ch = DEC(p[2]) << 6 | DEC(p[3]);
+ putchar(ch);
+ }
+ else {
+ if (n >= 1) {
+ ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
+ putchar(ch);
+ }
+ if (n >= 2) {
+ ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
+ putchar(ch);
+ }
+ if (n >= 3) {
+ ch = DEC(p[2]) << 6 | DEC(p[3]);
+ putchar(ch);
+ }
+ }
+ }
+ if (!fgets(buf, sizeof(buf), stdin) || strcmp(buf, "end\n")) {
+ warnx("%s: no \"end\" line.", inputname);
+ return(1);
+ }
+ return(0);
+ }
+}
+
+static int
+checkend(const char *ptr, const char *end, const char *msg)
+{
+ size_t n;
+
+ n = strlen(end);
+ if (strncmp(ptr, end, n) != 0 ||
+ strspn(ptr + n, "\t\r\n") != strlen(ptr + n)) {
+ warnx("%s", msg);
+ return (1);
+ }
+ return (0);
+}
+
+static int
+base64_decode(void)
+{
+ int n;
+ char inbuf[MAXPATHLEN];
+ unsigned char outbuf[MAXPATHLEN * 4];
+
+ for (;;) {
+ if (!fgets(inbuf, sizeof(inbuf), stdin)) {
+ warnx("%s: short file.", inputname);
+ return (1);
+ }
+#ifdef NO_BASE64
+ warnx("%s: base64 decoding is not supported", inputname);
+ return (1);
+#else
+ n = b64_pton(inbuf, outbuf, sizeof(outbuf));
+#endif
+ if (n < 0)
+ break;
+ fwrite(outbuf, 1, n, stdout);
+ }
+ return (checkend(inbuf, "====",
+ "error decoding base64 input stream"));
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: %s [-m] [-p | -o outfile] [encoded-file ...]\n",
+ getprogname());
+ exit(1);
+}
diff --git a/usr.bin/uuencode/uuencode.1 b/usr.bin/uuencode/uuencode.1
new file mode 100644
index 0000000..fabd310
--- /dev/null
+++ b/usr.bin/uuencode/uuencode.1
@@ -0,0 +1,175 @@
+.\" $NetBSD: uuencode.1,v 1.27 2017/07/03 21:34:22 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)uuencode.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd September 6, 2014
+.Dt UUENCODE 1
+.Os
+.Sh NAME
+.Nm uuencode ,
+.Nm uudecode
+.Nd encode/decode a binary file
+.Sh SYNOPSIS
+.Nm
+.Op Fl m
+.Op Ar inputfile
+.Ar headername
+.Nm uudecode
+.Op Fl m
+.Op Fl p | Fl o Ar outputfile
+.Op Ar encoded-file ...
+.Sh DESCRIPTION
+.Nm
+and
+.Nm uudecode
+are used to transmit binary files over transmission mediums
+that do not support other than simple
+.Tn ASCII
+data.
+.Pp
+The following options are available:
+.Bl -tag -width ".Fl m"
+.It Fl m
+Use base64 encoding.
+For
+.Nm ,
+the historical uuencode algorithm is the default.
+For
+.Nm uudecode ,
+by default the encoding is automatically detected.
+.It Fl o Ar outputfile
+.Po Nm uudecode No only . Pc
+Send the decoded output data to
+.Ar outputfile .
+By default,
+.Nm uudecode
+uses the
+.Ar headername
+recorded in the header of the encoded data stream.
+.It Fl p
+.Po Nm uudecode No only . Pc
+Write the decoded file to standard output instead of to a file.
+.El
+.Pp
+.Nm
+reads
+.Ar inputfile
+(or by default the standard input) and writes an encoded version
+to (always) the standard output.
+The encoding uses only printing
+.Tn ASCII
+characters suitable for text-only transport media.
+The string
+.Ar headername
+is inserted into the output header as the
+.Ar outputfile
+to use at
+.Nm uudecode
+time.
+The header also includes the mode (permissions) of the file.
+.Pp
+.Nm uudecode
+transforms
+.Em uuencoded
+files (or by default, the standard input) into the original form.
+The resulting file is named
+.Ar headername
+as recorded in the encoded file,
+or as specified by the
+.Fl o
+option,
+and will have the mode of the original file except that setuid
+and execute bits are not retained.
+If the
+.Fl p
+option is specified, or if the output file name is given as
+.Pa /dev/stdout ,
+then the data will be written to the standard output
+instead of to a named file.
+.Nm uudecode
+ignores any leading and trailing lines.
+.Pp
+The encoded form of the file is expanded by 35%.
+Every 3 bytes become 4 plus control information.
+.Sh EXIT STATUS
+The
+.Nm uudecode
+and
+.Nm
+utilities exits 0 on success, and >0 if an error occurs.
+.Sh EXAMPLES
+The following example packages up a source tree, compresses it,
+uuencodes it and mails it to a user on another system.
+.Pp
+.Bd -literal -offset indent -compact
+tar czf \- src_tree \&| uuencode src_tree.tgz \&| mail user@example.com
+.Ed
+.Pp
+On the other system, if the user saves the mail to the file
+.Pa temp ,
+the following example creates the file
+.Pa src_tree.tgz
+and extracts it to make a copy of the original tree.
+.Pp
+.Bd -literal -offset indent -compact
+uudecode temp
+tar xzf src_tree.tgz
+.Ed
+.Sh SEE ALSO
+.Xr gzip 1 ,
+.Xr mail 1 ,
+.Xr tar 1 ,
+.\".Xr uucp 1 ,
+.Xr uuencode 5
+.Sh STANDARDS
+The
+.Nm uudecode
+and
+.Nm
+utilities conform to
+.St -p1003.1-2008 .
+.Sh HISTORY
+The
+.Nm uudecode
+and
+.Nm
+utilities appeared in
+.Bx 4.0 .
+.Sh SECURITY CONSIDERATIONS
+When using
+.Nm uudecode
+with files coming from dubious sources,
+always either explicitly pass the
+.Fl o
+option or check the header (the first line) of the encoded file for
+safety.
+Blindly using a
+.Ar headername
+from a hostile source can overwrite important files.
diff --git a/usr.bin/uuencode/uuencode.5 b/usr.bin/uuencode/uuencode.5
new file mode 100644
index 0000000..4c8950a
--- /dev/null
+++ b/usr.bin/uuencode/uuencode.5
@@ -0,0 +1,145 @@
+.\" $NetBSD: uuencode.5,v 1.12 2016/06/06 15:09:33 abhinav Exp $
+.\"
+.\" Copyright (c) 1989, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)uuencode.format.5 8.2 (Berkeley) 1/12/94
+.\"
+.Dd June 2, 2016
+.Dt UUENCODE 5
+.Os
+.Sh NAME
+.Nm uuencode
+.Nd format of an encoded uuencode file
+.Sh DESCRIPTION
+Files output by
+.Xr uuencode 1
+consist of a header line,
+followed by a number of body lines,
+and a trailer line.
+The
+.Xr uudecode 1
+command
+will ignore any lines preceding the header or
+following the trailer.
+Lines preceding a header must not, of course,
+look like a header.
+.Pp
+The header line starts with the word
+.Dq begin ,
+a space,
+a file mode (in octal),
+a space,
+and finally a string which names the file being encoded.
+.Pp
+The central engine of
+.Xr uuencode 1
+is a six-bit encoding function which outputs an
+.Tn ASCII
+character.
+The six bits to be encoded are treated as a small integer and added
+with the
+.Tn ASCII
+value for the space character (octal 40).
+The result is a printable
+.Tn ASCII
+character.
+In the case where all six bits to be encoded are zero,
+the
+.Tn ASCII
+backquote character \` (octal 140) is emitted instead of what
+would normally be a space.
+.Pp
+The body of an encoded file consists of one or more lines,
+each of which may be a maximum of 86 characters long (including the trailing
+newline).
+Each line represents an encoded chunk of data from the input file and begins
+with a byte count,
+followed by encoded bytes,
+followed by a newline.
+.Pp
+The byte count is a six-bit integer encoded with the above function,
+representing the number of bytes encoded in the rest of the line.
+The method used to encode the data expands its size by
+133% (described below).
+Therefore it is important to note that the byte count describes the size of
+the chunk of data before it is encoded, not afterwards.
+The six bit size of this number effectively limits the number of bytes
+that can be encoded in each line to a maximum of 63.
+While
+.Xr uuencode 1
+will not encode more than 45 bytes per line,
+.Xr uudecode 1
+will tolerate the maximum line size.
+.Pp
+The remaining characters in the line represent the data of the input
+file encoded as follows.
+Input data are broken into groups of three eight-bit bytes,
+which are then interpreted together as a 24-bit block.
+The first bit of the block is the highest order bit of the first character,
+and the last is the lowest order bit of the third character.
+This block is then broken into four six-bit integers which are encoded one by
+one starting from the first bit of the block.
+The result is a four character
+.Tn ASCII
+string for every three bytes of input data.
+.Pp
+Encoded lines of data continue in this manner until the input file is
+exhausted.
+The end of the body is signaled by an encoded line with a byte count
+of zero (the
+.Tn ASCII
+backquote character \`).
+.Pp
+Obviously, not every input file will be a multiple of three bytes in size.
+In these cases,
+.Xr uuencode 1
+will pad the remaining one or two bytes of data with garbage bytes until
+a three byte group is created.
+The byte count in a line containing
+garbage padding will reflect the actual number of bytes encoded, making
+it possible to convey how many bytes are garbage.
+.Pp
+The trailer line consists of
+.Dq end
+on a line by itself.
+.Sh SEE ALSO
+.Xr mail 1 ,
+.Xr uudecode 1 ,
+.Xr uuencode 1 ,
+.Xr ascii 7
+.Sh HISTORY
+The
+.Nm
+file format appeared in
+.Bx 4.0 .
+.Sh BUGS
+The interpretation of the
+.Nm
+format relies on properties of the
+.Tn ASCII
+character set and may not work correctly on non-ASCII systems.
diff --git a/usr.bin/uuencode/uuencode.c b/usr.bin/uuencode/uuencode.c
new file mode 100644
index 0000000..50b7051
--- /dev/null
+++ b/usr.bin/uuencode/uuencode.c
@@ -0,0 +1,202 @@
+/* $NetBSD: uuencode.c,v 1.16 2014/09/06 18:58:35 dholland Exp $ */
+
+/*-
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1983, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)uuencode.c 8.2 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: uuencode.c,v 1.16 2014/09/06 18:58:35 dholland Exp $");
+#endif
+#endif /* not lint */
+
+/*
+ * uuencode [input] output
+ *
+ * Encode a file so it can be mailed to a remote system.
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <err.h>
+#include <errno.h>
+#include <locale.h>
+#include <resolv.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void encode(void);
+static void base64_encode(void);
+__dead static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ int base64, ch, mode;
+
+ mode = 0;
+ base64 = 0;
+ setlocale(LC_ALL, "");
+ setprogname(argv[0]);
+
+ while ((ch = getopt(argc, argv, "m")) != -1) {
+ switch(ch) {
+ case 'm':
+ base64 = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argv += optind;
+ argc -= optind;
+
+ switch(argc) {
+ case 2: /* optional first argument is input file */
+ if (!freopen(*argv, "r", stdin) || fstat(fileno(stdin), &sb))
+ err(1, "%s", *argv);
+#define RWX (S_IRWXU|S_IRWXG|S_IRWXO)
+ mode = sb.st_mode & RWX;
+ ++argv;
+ break;
+ case 1:
+#define RW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
+ mode = RW & ~umask(RW);
+ break;
+ case 0:
+ default:
+ usage();
+ }
+
+ if (base64) {
+ (void)printf("begin-base64 %o %s\n", mode, *argv);
+ base64_encode();
+ (void)printf("====\n");
+ } else {
+ (void)printf("begin %o %s\n", mode, *argv);
+ encode();
+ (void)printf("end\n");
+ }
+
+ if (ferror(stdout))
+ err(1, "write error");
+ exit(0);
+}
+
+/* ENC is the basic 1 character encoding function to make a char printing */
+#define ENC(c) ((c) ? ((c) & 077) + ' ': '`')
+
+/*
+ * copy from in to out, encoding in base64 as you go along.
+ */
+static void
+base64_encode(void)
+{
+ /*
+ * Output must fit into 80 columns, chunks come in 4, leave 1.
+ */
+#define GROUPS ((70 / 4) - 1)
+ unsigned char buf[3];
+ char buf2[sizeof(buf) * 2 + 1];
+ size_t n;
+ int rv, sequence;
+
+ sequence = 0;
+
+ while ((n = fread(buf, 1, sizeof(buf), stdin))) {
+ ++sequence;
+ rv = b64_ntop(buf, n, buf2, (sizeof(buf2) / sizeof(buf2[0])));
+ if (rv == -1)
+ errx(1, "b64_ntop: error encoding base64");
+ printf("%s%s", buf2, (sequence % GROUPS) ? "" : "\n");
+ }
+ if (sequence % GROUPS)
+ printf("\n");
+}
+
+/*
+ * copy from in to out, encoding as you go along.
+ */
+static void
+encode(void)
+{
+ int ch, n;
+ char *p;
+ char buf[80];
+
+ while ((n = fread(buf, 1, 45, stdin)) > 0) {
+ ch = ENC(n);
+ if (putchar(ch) == EOF)
+ break;
+ for (p = buf; n > 0; n -= 3, p += 3) {
+ ch = *p >> 2;
+ ch = ENC(ch);
+ if (putchar(ch) == EOF)
+ break;
+ ch = ((*p << 4) & 060) | ((p[1] >> 4) & 017);
+ ch = ENC(ch);
+ if (putchar(ch) == EOF)
+ break;
+ ch = ((p[1] << 2) & 074) | ((p[2] >> 6) & 03);
+ ch = ENC(ch);
+ if (putchar(ch) == EOF)
+ break;
+ ch = p[2] & 077;
+ ch = ENC(ch);
+ if (putchar(ch) == EOF)
+ break;
+ }
+ if (putchar('\n') == EOF)
+ break;
+ }
+ if (ferror(stdin))
+ err(1, "read error.");
+ ch = ENC('\0');
+ (void)putchar(ch);
+ (void)putchar('\n');
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: %s [-m] [inputfile] headername > encodedfile\n",
+ getprogname());
+ exit(1);
+}
diff --git a/usr.bin/wc/wc.1 b/usr.bin/wc/wc.1
new file mode 100644
index 0000000..60454a3
--- /dev/null
+++ b/usr.bin/wc/wc.1
@@ -0,0 +1,146 @@
+.\" $NetBSD: wc.1,v 1.17 2017/07/03 21:34:22 wiz Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)wc.1 8.2 (Berkeley) 4/19/94
+.\"
+.Dd February 18, 2010
+.Dt WC 1
+.Os
+.Sh NAME
+.Nm wc
+.Nd word, line, and byte count
+.Sh SYNOPSIS
+.Nm
+.Op Fl c | Fl m
+.Op Fl Llw
+.Op Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility displays the number of lines, words, bytes and characters contained in each
+input
+.Ar file
+(or standard input, by default) to the standard output.
+A line is defined as a string of characters delimited by a <newline>
+character,
+and a word is defined as a string of characters delimited by white space
+characters.
+White space characters are the set of characters for which the
+.Xr iswspace 3
+function returns true.
+If more than one input file is specified, a line of cumulative counts
+for all the files is displayed on a separate line after the output for
+the last file.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl c
+The number of bytes in each input file
+is written to the standard output.
+.It Fl L
+The number of characters in the longest line of each input file
+is written to the standard output.
+.It Fl l
+The number of lines in each input file
+is written to the standard output.
+.It Fl m
+The number of characters in each input file
+is written to the standard output.
+.It Fl w
+The number of words in each input file
+is written to the standard output.
+.El
+.Pp
+When an option is specified,
+.Nm
+only
+reports the
+information requested by that option.
+The default action is equivalent to all the flags
+.Fl clw
+having been specified.
+.Pp
+The following operands are available:
+.Bl -tag -width Ds
+.It Ar file
+A pathname of an input file.
+.El
+.Pp
+If no file names
+are specified, the standard input is used and
+no file name is displayed.
+.Pp
+By default, the standard output contains a line for each
+input file of the form:
+.Bd -literal -offset indent
+lines words bytes file_name
+.Ed
+.Sh EXIT STATUS
+.Ex -std wc
+.Sh COMPATIBILITY
+Historically, the
+.Nm
+utility was documented to define a word as a ``maximal string of
+characters delimited by
+.Aq space ,
+.Aq tab
+or
+.Aq newline
+characters''.
+The implementation, however, didn't handle non-printing characters
+correctly so that `` ^D^E '' counted as 6 spaces, while ``foo^D^Ebar''
+counted as 8 characters.
+.Bx 4
+systems after
+.Bx 4.3
+modified the implementation to be consistent
+with the documentation.
+This implementation defines a ``word'' in terms of the
+.Xr iswspace 3
+function, as required by
+.St -p1003.2 .
+.Pp
+The
+.Fl L
+option is a non-standard extension, compatible with the
+.Fl L
+option of the GNU and
+.Fx
+.Nm
+utilities.
+.Sh SEE ALSO
+.Xr iswspace 3
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.2-92 .
diff --git a/usr.bin/wc/wc.c b/usr.bin/wc/wc.c
new file mode 100644
index 0000000..602a450
--- /dev/null
+++ b/usr.bin/wc/wc.c
@@ -0,0 +1,354 @@
+/* $NetBSD: wc.c,v 1.35 2011/09/16 15:39:30 joerg Exp $ */
+
+/*
+ * Copyright (c) 1980, 1987, 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1980, 1987, 1991, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)wc.c 8.2 (Berkeley) 5/2/95";
+#else
+__RCSID("$NetBSD: wc.c,v 1.35 2011/09/16 15:39:30 joerg Exp $");
+#endif
+#endif /* not lint */
+
+/* wc line, word, char count and optionally longest line. */
+
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <err.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#ifdef NO_QUAD
+typedef u_long wc_count_t;
+# define WCFMT " %7lu"
+# define WCCAST unsigned long
+#else
+typedef u_quad_t wc_count_t;
+# define WCFMT " %7llu"
+# define WCCAST unsigned long long
+#endif
+
+static wc_count_t tlinect, twordct, tcharct, tlongest;
+static bool doline, doword, dobyte, dochar, dolongest;
+static int rval = 0;
+
+static void cnt(const char *);
+static void print_counts(wc_count_t, wc_count_t, wc_count_t, wc_count_t,
+ const char *);
+__dead static void usage(void);
+static size_t do_mb(wchar_t *, const char *, size_t, mbstate_t *,
+ size_t *, const char *);
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+
+ setlocale(LC_ALL, "");
+
+ while ((ch = getopt(argc, argv, "lwcmL")) != -1)
+ switch (ch) {
+ case 'l':
+ doline = true;
+ break;
+ case 'w':
+ doword = true;
+ break;
+ case 'm':
+ dochar = true;
+ dobyte = 0;
+ break;
+ case 'c':
+ dochar = 0;
+ dobyte = true;
+ break;
+ case 'L':
+ dolongest = true;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argv += optind;
+ argc -= optind;
+
+ /* Wc's flags are on by default. */
+ if (!(doline || doword || dobyte || dochar || dolongest))
+ doline = doword = dobyte = true;
+
+ if (*argv == NULL) {
+ cnt(NULL);
+ } else {
+ bool dototal = (argc > 1);
+
+ do {
+ cnt(*argv);
+ } while(*++argv);
+
+ if (dototal) {
+ print_counts(tlinect, twordct, tcharct, tlongest,
+ "total");
+ }
+ }
+
+ exit(rval);
+}
+
+static size_t
+do_mb(wchar_t *wc, const char *p, size_t len, mbstate_t *st,
+ size_t *retcnt, const char *file)
+{
+ size_t r;
+ size_t c = 0;
+
+ do {
+ r = mbrtowc(wc, p, len, st);
+ if (r == (size_t)-1) {
+ warnx("%s: invalid byte sequence", file);
+ rval = 1;
+
+ /* XXX skip 1 byte */
+ len--;
+ p++;
+ memset(st, 0, sizeof(*st));
+ continue;
+ } else if (r == (size_t)-2)
+ break;
+ else if (r == 0)
+ r = 1;
+ c++;
+ if (wc)
+ wc++;
+ len -= r;
+ p += r;
+ } while (len > 0);
+
+ *retcnt = c;
+
+ return (r);
+}
+
+static void
+cnt(const char *file)
+{
+ u_char buf[MAXBSIZE];
+ wchar_t wbuf[MAXBSIZE];
+ struct stat sb;
+ wc_count_t charct, linect, wordct, longest;
+ mbstate_t st;
+ u_char *C;
+ wchar_t *WC;
+ const char *name; /* filename or <stdin> */
+ size_t r = 0;
+ int fd, len = 0;
+
+ linect = wordct = charct = longest = 0;
+ if (file != NULL) {
+ if ((fd = open(file, O_RDONLY, 0)) < 0) {
+ warn("%s", file);
+ rval = 1;
+ return;
+ }
+ name = file;
+ } else {
+ fd = STDIN_FILENO;
+ name = "<stdin>";
+ }
+
+ if (dochar || doword || dolongest)
+ (void)memset(&st, 0, sizeof(st));
+
+ if (!(doword || dolongest)) {
+ /*
+ * line counting is split out because it's a lot
+ * faster to get lines than to get words, since
+ * the word count requires some logic.
+ */
+ if (doline || dochar) {
+ while ((len = read(fd, buf, MAXBSIZE)) > 0) {
+ if (dochar) {
+ size_t wlen;
+
+ r = do_mb(0, (char *)buf, (size_t)len,
+ &st, &wlen, name);
+ charct += wlen;
+ } else if (dobyte)
+ charct += len;
+ if (doline) {
+ for (C = buf; len--; ++C) {
+ if (*C == '\n')
+ ++linect;
+ }
+ }
+ }
+ }
+
+ /*
+ * if all we need is the number of characters and
+ * it's a directory or a regular or linked file, just
+ * stat the puppy. We avoid testing for it not being
+ * a special device in case someone adds a new type
+ * of inode.
+ */
+ else if (dobyte) {
+ if (fstat(fd, &sb)) {
+ warn("%s", name);
+ rval = 1;
+ } else {
+ if (S_ISREG(sb.st_mode) ||
+ S_ISLNK(sb.st_mode) ||
+ S_ISDIR(sb.st_mode)) {
+ charct = sb.st_size;
+ } else {
+ while ((len =
+ read(fd, buf, MAXBSIZE)) > 0)
+ charct += len;
+ }
+ }
+ }
+ } else {
+ /* do it the hard way... */
+ wc_count_t linelen;
+ bool gotsp;
+
+ linelen = 0;
+ gotsp = true;
+ while ((len = read(fd, buf, MAXBSIZE)) > 0) {
+ size_t wlen;
+
+ r = do_mb(wbuf, (char *)buf, (size_t)len, &st, &wlen,
+ name);
+ if (dochar) {
+ charct += wlen;
+ } else if (dobyte) {
+ charct += len;
+ }
+ for (WC = wbuf; wlen--; ++WC) {
+ if (iswspace(*WC)) {
+ gotsp = true;
+ if (*WC == L'\n') {
+ ++linect;
+ if (linelen > longest)
+ longest = linelen;
+ linelen = 0;
+ } else {
+ linelen++;
+ }
+ } else {
+ /*
+ * This line implements the POSIX
+ * spec, i.e. a word is a "maximal
+ * string of characters delimited by
+ * whitespace." Notice nothing was
+ * said about a character being
+ * printing or non-printing.
+ */
+ if (gotsp) {
+ gotsp = false;
+ ++wordct;
+ }
+
+ linelen++;
+ }
+ }
+ }
+ }
+
+ if (len == -1) {
+ warn("%s", name);
+ rval = 1;
+ }
+ if (dochar && r == (size_t)-2) {
+ warnx("%s: incomplete multibyte character", name);
+ rval = 1;
+ }
+
+ print_counts(linect, wordct, charct, longest, file);
+
+ /*
+ * don't bother checkint doline, doword, or dobyte --- speeds
+ * up the common case
+ */
+ tlinect += linect;
+ twordct += wordct;
+ tcharct += charct;
+ if (dolongest && longest > tlongest)
+ tlongest = longest;
+
+ if (close(fd)) {
+ warn("%s", name);
+ rval = 1;
+ }
+}
+
+static void
+print_counts(wc_count_t lines, wc_count_t words, wc_count_t chars,
+ wc_count_t longest, const char *name)
+{
+
+ if (doline)
+ (void)printf(WCFMT, (WCCAST)lines);
+ if (doword)
+ (void)printf(WCFMT, (WCCAST)words);
+ if (dobyte || dochar)
+ (void)printf(WCFMT, (WCCAST)chars);
+ if (dolongest)
+ (void)printf(WCFMT, (WCCAST)longest);
+
+ if (name != NULL)
+ (void)printf(" %s\n", name);
+ else
+ (void)putchar('\n');
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: wc [-c | -m] [-Llw] [file ...]\n");
+ exit(1);
+}
diff --git a/usr.bin/what/what.1 b/usr.bin/what/what.1
new file mode 100644
index 0000000..4a96a7a
--- /dev/null
+++ b/usr.bin/what/what.1
@@ -0,0 +1,82 @@
+.\" $NetBSD: what.1,v 1.15 2017/07/03 21:34:22 wiz Exp $
+.\"
+.\" Copyright (c) 1980, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)what.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd December 12, 2015
+.Dt WHAT 1
+.Os
+.Sh NAME
+.Nm what
+.Nd search files for SCCS identifiers
+.Sh SYNOPSIS
+.Nm
+.Op Fl s
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility reads each
+.Ar file
+operand and searches for sequences of the form
+.Dq \&@(#)
+as inserted by SCCS and SCCS-compatible version control systems.
+It prints the remainder of the string following this marker, up to
+a null character, newline, double quote, backslash, or
+.Dq \&>
+character.
+.Pp
+If the
+.Fl s
+option is specified, only the first occurrence of an identification string in
+each file is printed.
+.Sh EXIT STATUS
+The
+.Nm
+utility exits 0 if any matches were found, and 1 otherwise.
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -xpg4.2 .
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.0 .
+.Sh BUGS
+As
+.Bx
+is not licensed to distribute
+.Tn SCCS
+this is a rewrite of the
+.Nm
+command which is part of
+.Tn SCCS ,
+and may not behave exactly the same as that command does.
diff --git a/usr.bin/what/what.c b/usr.bin/what/what.c
new file mode 100644
index 0000000..fb29e52
--- /dev/null
+++ b/usr.bin/what/what.c
@@ -0,0 +1,127 @@
+/* $NetBSD: what.c,v 1.12 2015/12/12 09:50:12 dholland Exp $ */
+
+/*
+ * Copyright (c) 1980, 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1980, 1988, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)what.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: what.c,v 1.12 2015/12/12 09:50:12 dholland Exp $");
+#endif /* not lint */
+
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static void search(void);
+__dead static void usage(void);
+
+static int matches;
+static int sflag;
+
+/*
+ * what
+ */
+int
+main(int argc, char **argv)
+{
+ int c;
+
+ (void)setlocale(LC_ALL, "");
+
+ matches = sflag = 0;
+ while ((c = getopt(argc, argv, "s")) != -1) {
+ switch (c) {
+ case 's':
+ sflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ usage();
+ } else do {
+ if (freopen(*argv, "r", stdin) == NULL) {
+ perror(*argv);
+ exit(matches ? EXIT_SUCCESS : 1);
+ }
+ printf("%s\n", *argv);
+ search();
+ } while (*++argv != NULL);
+
+ /* Note: the standard explicitly specifies an exit status of 1. */
+ exit(matches ? EXIT_SUCCESS : 1);
+ /* NOTREACHED */
+}
+
+static void
+search(void)
+{
+ int c;
+
+ while ((c = getchar()) != EOF) {
+loop: if (c != '@')
+ continue;
+ if ((c = getchar()) != '(')
+ goto loop;
+ if ((c = getchar()) != '#')
+ goto loop;
+ if ((c = getchar()) != ')')
+ goto loop;
+ putchar('\t');
+ while ((c = getchar()) != EOF && c != '\0' && c != '"' &&
+ c != '>' && c != '\n' && c != '\\')
+ putchar(c);
+ putchar('\n');
+ matches++;
+ if (sflag)
+ break;
+ }
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: what [-s] file ...\n");
+ exit(1);
+}
diff --git a/usr.bin/who/utmpentry.c b/usr.bin/who/utmpentry.c
new file mode 100644
index 0000000..c74397b
--- /dev/null
+++ b/usr.bin/who/utmpentry.c
@@ -0,0 +1,329 @@
+/* $NetBSD: utmpentry.c,v 1.18 2015/11/21 15:01:43 christos Exp $ */
+
+/*-
+ * Copyright (c) 2002 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: utmpentry.c,v 1.18 2015/11/21 15:01:43 christos Exp $");
+#endif
+
+#include <sys/stat.h>
+
+#include <time.h>
+#include <string.h>
+#include <err.h>
+#include <stdlib.h>
+
+#ifdef SUPPORT_UTMP
+#include <utmp.h>
+#endif
+#ifdef SUPPORT_UTMPX
+#include <utmpx.h>
+#endif
+
+#include "utmpentry.h"
+
+
+/* Fail the compile if x is not true, by constructing an illegal type. */
+#define COMPILE_ASSERT(x) /*LINTED null effect */ \
+ ((void)sizeof(struct { unsigned : ((x) ? 1 : -1); }))
+
+
+#ifdef SUPPORT_UTMP
+static void getentry(struct utmpentry *, struct utmp *);
+static struct timespec utmptime = {0, 0};
+#endif
+#ifdef SUPPORT_UTMPX
+static void getentryx(struct utmpentry *, struct utmpx *);
+static struct timespec utmpxtime = {0, 0};
+#endif
+#if defined(SUPPORT_UTMPX) || defined(SUPPORT_UTMP)
+static int setup(const char *);
+static void adjust_size(struct utmpentry *e);
+#endif
+
+size_t maxname = 8, maxline = 8, maxhost = 16;
+int etype = 1 << USER_PROCESS;
+static size_t numutmp = 0;
+static struct utmpentry *ehead;
+
+#if defined(SUPPORT_UTMPX) || defined(SUPPORT_UTMP)
+static void
+adjust_size(struct utmpentry *e)
+{
+ size_t max;
+
+ if ((max = strlen(e->name)) > maxname)
+ maxname = max;
+ if ((max = strlen(e->line)) > maxline)
+ maxline = max;
+ if ((max = strlen(e->host)) > maxhost)
+ maxhost = max;
+}
+
+static int
+setup(const char *fname)
+{
+ int what = 3;
+ struct stat st;
+ const char *sfname;
+
+ if (fname == NULL) {
+#ifdef SUPPORT_UTMPX
+ setutxent();
+#endif
+#ifdef SUPPORT_UTMP
+ setutent();
+#endif
+ } else {
+ size_t len = strlen(fname);
+ if (len == 0)
+ errx(1, "Filename cannot be 0 length.");
+ what = fname[len - 1] == 'x' ? 1 : 2;
+ if (what == 1) {
+#ifdef SUPPORT_UTMPX
+ if (utmpxname(fname) == 0)
+ warnx("Cannot set utmpx file to `%s'",
+ fname);
+#else
+ warnx("utmpx support not compiled in");
+#endif
+ } else {
+#ifdef SUPPORT_UTMP
+ if (utmpname(fname) == 0)
+ warnx("Cannot set utmp file to `%s'",
+ fname);
+#else
+ warnx("utmp support not compiled in");
+#endif
+ }
+ }
+#ifdef SUPPORT_UTMPX
+ if (what & 1) {
+ sfname = fname ? fname : _PATH_UTMPX;
+ if (stat(sfname, &st) == -1) {
+ warn("Cannot stat `%s'", sfname);
+ what &= ~1;
+ } else {
+ if (timespeccmp(&st.st_mtimespec, &utmpxtime, >))
+ utmpxtime = st.st_mtimespec;
+ else
+ what &= ~1;
+ }
+ }
+#endif
+#ifdef SUPPORT_UTMP
+ if (what & 2) {
+ sfname = fname ? fname : _PATH_UTMP;
+ if (stat(sfname, &st) == -1) {
+ warn("Cannot stat `%s'", sfname);
+ what &= ~2;
+ } else {
+ if (timespeccmp(&st.st_mtimespec, &utmptime, >))
+ utmptime = st.st_mtimespec;
+ else
+ what &= ~2;
+ }
+ }
+#endif
+ return what;
+}
+#endif
+
+void
+endutentries(void)
+{
+ struct utmpentry *ep;
+
+#ifdef SUPPORT_UTMP
+ timespecclear(&utmptime);
+#endif
+#ifdef SUPPORT_UTMPX
+ timespecclear(&utmpxtime);
+#endif
+ ep = ehead;
+ while (ep) {
+ struct utmpentry *sep = ep;
+ ep = ep->next;
+ free(sep);
+ }
+ ehead = NULL;
+ numutmp = 0;
+}
+
+size_t
+getutentries(const char *fname, struct utmpentry **epp)
+{
+#ifdef SUPPORT_UTMPX
+ struct utmpx *utx;
+#endif
+#ifdef SUPPORT_UTMP
+ struct utmp *ut;
+#endif
+#if defined(SUPPORT_UTMP) || defined(SUPPORT_UTMPX)
+ struct utmpentry *ep;
+ int what = setup(fname);
+ struct utmpentry **nextp = &ehead;
+ switch (what) {
+ case 0:
+ /* No updates */
+ *epp = ehead;
+ return numutmp;
+ default:
+ /* Need to re-scan */
+ ehead = NULL;
+ numutmp = 0;
+ }
+#endif
+
+#ifdef SUPPORT_UTMPX
+ while ((what & 1) && (utx = getutxent()) != NULL) {
+ if (fname == NULL && ((1 << utx->ut_type) & etype) == 0)
+ continue;
+ if ((ep = calloc(1, sizeof(struct utmpentry))) == NULL) {
+ warn(NULL);
+ return 0;
+ }
+ getentryx(ep, utx);
+ *nextp = ep;
+ nextp = &(ep->next);
+ }
+#endif
+
+#ifdef SUPPORT_UTMP
+ if ((etype & (1 << USER_PROCESS)) != 0) {
+ while ((what & 2) && (ut = getutent()) != NULL) {
+ if (fname == NULL && (*ut->ut_name == '\0' ||
+ *ut->ut_line == '\0'))
+ continue;
+ /* Don't process entries that we have utmpx for */
+ for (ep = ehead; ep != NULL; ep = ep->next) {
+ if (strncmp(ep->line, ut->ut_line,
+ sizeof(ut->ut_line)) == 0)
+ break;
+ }
+ if (ep != NULL)
+ continue;
+ if ((ep = calloc(1, sizeof(*ep))) == NULL) {
+ warn(NULL);
+ return 0;
+ }
+ getentry(ep, ut);
+ *nextp = ep;
+ nextp = &(ep->next);
+ }
+ }
+#endif
+ numutmp = 0;
+#if defined(SUPPORT_UTMP) || defined(SUPPORT_UTMPX)
+ if (ehead != NULL) {
+ struct utmpentry *from = ehead, *save;
+
+ ehead = NULL;
+ while (from != NULL) {
+ for (nextp = &ehead;
+ (*nextp) && strcmp(from->line, (*nextp)->line) > 0;
+ nextp = &(*nextp)->next)
+ continue;
+ save = from;
+ from = from->next;
+ save->next = *nextp;
+ *nextp = save;
+ numutmp++;
+ }
+ }
+ *epp = ehead;
+ return numutmp;
+#else
+ *epp = NULL;
+ return 0;
+#endif
+}
+
+#ifdef SUPPORT_UTMP
+static void
+getentry(struct utmpentry *e, struct utmp *up)
+{
+ COMPILE_ASSERT(sizeof(e->name) > sizeof(up->ut_name));
+ COMPILE_ASSERT(sizeof(e->line) > sizeof(up->ut_line));
+ COMPILE_ASSERT(sizeof(e->host) > sizeof(up->ut_host));
+
+ /*
+ * e has just been calloc'd. We don't need to clear it or
+ * append null-terminators, because its length is strictly
+ * greater than the source string. Use strncpy to _read_
+ * up->ut_* because they may not be terminated. For this
+ * reason we use the size of the _source_ as the length
+ * argument.
+ */
+ (void)strncpy(e->name, up->ut_name, sizeof(up->ut_name));
+ (void)strncpy(e->line, up->ut_line, sizeof(up->ut_line));
+ (void)strncpy(e->host, up->ut_host, sizeof(up->ut_host));
+
+ e->tv.tv_sec = up->ut_time;
+ e->tv.tv_usec = 0;
+ e->pid = 0;
+ e->term = 0;
+ e->exit = 0;
+ e->sess = 0;
+ e->type = USER_PROCESS;
+ adjust_size(e);
+}
+#endif
+
+#ifdef SUPPORT_UTMPX
+static void
+getentryx(struct utmpentry *e, struct utmpx *up)
+{
+ COMPILE_ASSERT(sizeof(e->name) > sizeof(up->ut_name));
+ COMPILE_ASSERT(sizeof(e->line) > sizeof(up->ut_line));
+ COMPILE_ASSERT(sizeof(e->host) > sizeof(up->ut_host));
+
+ /*
+ * e has just been calloc'd. We don't need to clear it or
+ * append null-terminators, because its length is strictly
+ * greater than the source string. Use strncpy to _read_
+ * up->ut_* because they may not be terminated. For this
+ * reason we use the size of the _source_ as the length
+ * argument.
+ */
+ (void)strncpy(e->name, up->ut_name, sizeof(up->ut_name));
+ (void)strncpy(e->line, up->ut_line, sizeof(up->ut_line));
+ (void)strncpy(e->host, up->ut_host, sizeof(up->ut_host));
+
+ e->tv = up->ut_tv;
+ e->pid = up->ut_pid;
+ e->term = up->ut_exit.e_termination;
+ e->exit = up->ut_exit.e_exit;
+ e->sess = up->ut_session;
+ e->type = up->ut_type;
+ adjust_size(e);
+}
+#endif
diff --git a/usr.bin/who/utmpentry.h b/usr.bin/who/utmpentry.h
new file mode 100644
index 0000000..263a33a
--- /dev/null
+++ b/usr.bin/who/utmpentry.h
@@ -0,0 +1,76 @@
+/* $NetBSD: utmpentry.h,v 1.8 2015/11/21 15:01:43 christos Exp $ */
+
+/*-
+ * Copyright (c) 2002 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if defined(SUPPORT_UTMPX)
+# include <utmpx.h>
+# define WHO_NAME_LEN _UTX_USERSIZE
+# define WHO_LINE_LEN _UTX_LINESIZE
+# define WHO_HOST_LEN _UTX_HOSTSIZE
+#elif defined(SUPPORT_UTMP)
+# include <utmp.h>
+# define WHO_NAME_LEN UT_NAMESIZE
+# define WHO_LINE_LEN UT_LINESIZE
+# define WHO_HOST_LEN UT_HOSTSIZE
+#else
+# error Either SUPPORT_UTMPX or SUPPORT_UTMP must be defined!
+#endif
+
+
+struct utmpentry {
+ char name[WHO_NAME_LEN + 1];
+ char line[WHO_LINE_LEN + 1];
+ char host[WHO_HOST_LEN + 1];
+ struct timeval tv;
+ pid_t pid;
+ uint16_t term;
+ uint16_t exit;
+ uint16_t sess;
+ uint16_t type;
+ struct utmpentry *next;
+};
+
+extern size_t maxname, maxline, maxhost;
+extern int etype;
+
+/*
+ * getutentries provides a linked list of struct utmpentry and returns
+ * the number of entries. The first argument, if not null, names an
+ * alternate utmp(x) file to look in.
+ *
+ * The memory returned by getutentries belongs to getutentries. The
+ * list returned (or elements of it) may be returned again later if
+ * utmp hasn't changed in the meantime.
+ *
+ * endutentries clears and frees the cached data.
+ */
+
+size_t getutentries(const char *, struct utmpentry **);
+void endutentries(void);
diff --git a/usr.bin/who/who.1 b/usr.bin/who/who.1
new file mode 100644
index 0000000..3873b47
--- /dev/null
+++ b/usr.bin/who/who.1
@@ -0,0 +1,197 @@
+.\" $NetBSD: who.1,v 1.24 2018/05/11 16:36:57 sevan Exp $
+.\"
+.\" Copyright (c) 1986, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)who.1 8.2 (Berkeley) 12/30/93
+.\"
+.Dd May 11, 2018
+.Dt WHO 1
+.Os
+.Sh NAME
+.Nm who
+.Nd display who is logged in
+.Sh SYNOPSIS
+.Nm
+.Op Fl abdHlmqrsTtuv
+.Op Ar file
+.Nm
+.Ar am i
+.Sh DESCRIPTION
+The
+.Nm
+utility displays a list of all users currently logged on, showing for
+each user the login name, tty name, the date and time of login, and
+hostname if not local.
+.Pp
+Available options:
+.Bl -tag -width file
+.It Fl a
+Same as
+.Fl -bdlprTtuv .
+.It Fl b
+Time of last system boot.
+.It Fl d
+Print dead processes.
+.It Fl H
+Write column headings above the regular output.
+.It Fl l
+Print system login processes.
+.It Fl m
+Only print information about the current terminal.
+This is the POSIX way of saying
+.Nm
+.Ar am i .
+.It Fl p
+Print active processes spawned by
+.Xr init 8 .
+.It Fl q
+.Dq Quick mode :
+List only the names and the number of users currently logged on.
+When this option is used, all other options are ignored.
+.It Fl r
+Print the current runlevel.
+Supported runlevels are:
+.Bl -tag -width "s (SINGLE_USER)"
+.It Dv d Pq Dv DEATH
+The system has halted.
+.It Dv s Pq Dv SINGLE_USER
+The system is running in single user mode.
+.It Dv r Pq Dv RUNCOM
+The system is executing
+.Pa /etc/rc .
+.It Dv t Pq Dv READ_TTYS
+The system is processing
+.Pa /etc/ttys .
+.It Dv m Pq Dv MULTI_USER
+The system is running in multi-user mode.
+.It Dv T Pq Dv CLEAN_TTYS
+The system is in the process of stopping processes
+associated with terminal devices.
+.It Dv c Pq Dv CATATONIA
+The system is in the process of shutting down and will
+not create new processes.
+.El
+.It Fl s
+List only the name, line and time fields.
+This is the default.
+.It Fl T
+Print a character after the user name indicating the state of the
+terminal line:
+.Sq +
+if the terminal is writable;
+.Sq -
+if it is not;
+and
+.Sq \&?
+if a bad line is encountered.
+.It Fl t
+Print last system clock change.
+.It Fl u
+Print the idle time for each user, and the associated process ID.
+.It Fl v
+When printing of more information is requested with
+.Fl u ,
+this switch can be used to also printed
+process termination signals,
+process exit status,
+session id for windowing
+and the type of the entry, see documentation of ut_type in
+.Xr getutxent 3 .
+.It Ar \&am I
+Returns the invoker's real user name.
+.It Ar file
+By default,
+.Nm
+gathers information from the file
+.Pa /var/run/utmpx .
+An alternative
+.Ar file
+may be specified which is usually
+.Pa /var/log/wtmpx
+(or
+.Pa /var/log/wtmp ,
+or
+.Pa /var/log/wtmpx.[0-6]
+or
+.Pa /var/log/wtmp.[0-6]
+depending on site policy as
+.Pa wtmpx
+can grow quite large and daily versions may or may not
+be kept around after compression by
+.Xr ac 8 ) .
+The
+.Pa wtmpx
+and
+.Pa wtmp
+file contains a record of every login, logout,
+crash, shutdown and date change
+since
+.Pa wtmpx
+and
+.Pa wtmp
+were last truncated or
+created.
+.El
+.Pp
+If
+.Pa /var/log/wtmpx
+or
+.Pa /var/log/wtmp
+are being used as the file, the user name may be empty
+or one of the special characters '|', '}' and '~'.
+Logouts produce an output line without any user name.
+For more information on the
+special characters, see
+.Xr utmp 5 .
+.Sh FILES
+.Bl -tag -width /var/log/wtmp.[0-6] -compact
+.It Pa /var/run/utmp
+.It Pa /var/run/utmpx
+.It Pa /var/log/wtmp
+.It Pa /var/log/wtmp.[0-6]
+.It Pa /var/log/wtmpx
+.It Pa /var/log/wtmpx.[0-6]
+.El
+.Sh SEE ALSO
+.Xr last 1 ,
+.Xr mesg 1 ,
+.Xr users 1 ,
+.Xr getuid 2 ,
+.Xr utmp 5 ,
+.Xr utmpx 5
+.Sh STANDARDS
+The
+.Nm
+utility is expected to conform to
+.St -p1003.2-92 .
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v1 :
+.Lk http://cm.bell-labs.com/cm/cs/who/dmr/pdfs/man14.pdf
diff --git a/usr.bin/who/who.c b/usr.bin/who/who.c
new file mode 100644
index 0000000..f0c6564
--- /dev/null
+++ b/usr.bin/who/who.c
@@ -0,0 +1,391 @@
+/* $NetBSD: who.c,v 1.25 2015/11/21 15:01:43 christos Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Michael Fischbein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)who.c 8.1 (Berkeley) 6/6/93";
+#endif
+__RCSID("$NetBSD: who.c,v 1.25 2015/11/21 15:01:43 christos Exp $");
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <locale.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#ifdef SUPPORT_UTMP
+#include <utmp.h>
+#endif
+#ifdef SUPPORT_UTMPX
+#include <utmpx.h>
+#endif
+
+#include "utmpentry.h"
+
+static void output_labels(void);
+static void who_am_i(const char *, int);
+static void usage(void) __dead;
+static void process(const char *, int);
+static void eprint(const struct utmpentry *);
+static void print(const char *, const char *, time_t, const char *, pid_t pid,
+ uint16_t term, uint16_t xit, uint16_t sess, uint16_t type);
+static void quick(const char *);
+
+static int show_term; /* show term state */
+static int show_idle; /* show idle time */
+static int show_details; /* show exit status etc. */
+
+struct ut_type_names {
+ int type;
+ const char *name;
+} ut_type_names[] = {
+#ifdef SUPPORT_UTMPX
+ { EMPTY, "empty" },
+ { RUN_LVL, "run level" },
+ { BOOT_TIME, "boot time" },
+ { OLD_TIME, "old time" },
+ { NEW_TIME, "new time" },
+ { INIT_PROCESS, "init process" },
+ { LOGIN_PROCESS, "login process" },
+ { USER_PROCESS, "user process" },
+ { DEAD_PROCESS, "dead process" },
+#if defined(_NETBSD_SOURCE)
+ { ACCOUNTING, "accounting" },
+ { SIGNATURE, "signature" },
+ { DOWN_TIME, "down time" },
+#endif /* _NETBSD_SOURCE */
+#endif /* SUPPORT_UTMPX */
+ { -1, "unknown" }
+};
+
+int
+main(int argc, char *argv[])
+{
+ int c, only_current_term, show_labels, quick_mode, default_mode;
+ int et = 0;
+
+ setlocale(LC_ALL, "");
+
+ only_current_term = show_term = show_idle = show_labels = 0;
+ quick_mode = default_mode = 0;
+
+ while ((c = getopt(argc, argv, "abdHlmpqrsTtuv")) != -1) {
+ switch (c) {
+ case 'a':
+ et = -1;
+ show_idle = show_details = 1;
+ break;
+ case 'b':
+ et |= (1 << BOOT_TIME);
+ break;
+ case 'd':
+ et |= (1 << DEAD_PROCESS);
+ break;
+ case 'H':
+ show_labels = 1;
+ break;
+ case 'l':
+ et |= (1 << LOGIN_PROCESS);
+ break;
+ case 'm':
+ only_current_term = 1;
+ break;
+ case 'p':
+ et |= (1 << INIT_PROCESS);
+ break;
+ case 'q':
+ quick_mode = 1;
+ break;
+ case 'r':
+ et |= (1 << RUN_LVL);
+ break;
+ case 's':
+ default_mode = 1;
+ break;
+ case 'T':
+ show_term = 1;
+ break;
+ case 't':
+ et |= (1 << NEW_TIME);
+ break;
+ case 'u':
+ show_idle = 1;
+ break;
+ case 'v':
+ show_details = 1;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (et != 0)
+ etype = et;
+
+ if (chdir("/dev")) {
+ err(EXIT_FAILURE, "cannot change directory to /dev");
+ /* NOTREACHED */
+ }
+
+ if (default_mode)
+ only_current_term = show_term = show_idle = 0;
+
+ switch (argc) {
+ case 0: /* who */
+ if (quick_mode) {
+ quick(NULL);
+ } else if (only_current_term) {
+ who_am_i(NULL, show_labels);
+ } else {
+ process(NULL, show_labels);
+ }
+ break;
+ case 1: /* who utmp_file */
+ if (quick_mode) {
+ quick(*argv);
+ } else if (only_current_term) {
+ who_am_i(*argv, show_labels);
+ } else {
+ process(*argv, show_labels);
+ }
+ break;
+ case 2: /* who am i */
+ who_am_i(NULL, show_labels);
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+
+ return 0;
+}
+
+static char *
+strrstr(const char *str, const char *pat)
+{
+ const char *estr;
+ size_t len;
+ if (*pat == '\0')
+ return __UNCONST(str);
+
+ len = strlen(pat);
+
+ for (estr = str + strlen(str); str < estr; estr--)
+ if (strncmp(estr, pat, len) == 0)
+ return __UNCONST(estr);
+ return NULL;
+}
+
+static void
+who_am_i(const char *fname, int show_labels)
+{
+ struct passwd *pw;
+ const char *p;
+ char *t;
+ time_t now;
+ struct utmpentry *ehead, *ep;
+
+ /* search through the utmp and find an entry for this tty */
+ if ((p = ttyname(STDIN_FILENO)) != NULL) {
+
+ /* strip directory prefixes for ttys */
+ if ((t = strrstr(p, "/pts/")) != NULL ||
+ (t = strrchr(p, '/')) != NULL)
+ p = t + 1;
+
+ (void)getutentries(fname, &ehead);
+ for (ep = ehead; ep; ep = ep->next)
+ if (strcmp(ep->line, p) == 0) {
+ if (show_labels)
+ output_labels();
+ eprint(ep);
+ return;
+ }
+ } else
+ p = "tty??";
+
+ (void)time(&now);
+ pw = getpwuid(getuid());
+ if (show_labels)
+ output_labels();
+ print(pw ? pw->pw_name : "?", p, now, "", getpid(), 0, 0, 0, 0);
+}
+
+static void
+process(const char *fname, int show_labels)
+{
+ struct utmpentry *ehead, *ep;
+ (void)getutentries(fname, &ehead);
+ if (show_labels)
+ output_labels();
+ for (ep = ehead; ep != NULL; ep = ep->next)
+ eprint(ep);
+}
+
+static void
+eprint(const struct utmpentry *ep)
+{
+ print(ep->name, ep->line, (time_t)ep->tv.tv_sec, ep->host, ep->pid,
+ ep->term, ep->exit, ep->sess, ep->type);
+}
+
+static void
+print(const char *name, const char *line, time_t t, const char *host,
+ pid_t pid, uint16_t term, uint16_t xit, uint16_t sess, uint16_t type)
+{
+ struct stat sb;
+ char state;
+ static time_t now = 0;
+ time_t idle;
+ const char *types = NULL;
+ size_t i;
+ char *tstr;
+
+ state = '?';
+ idle = 0;
+
+ for (i = 0; ut_type_names[i].type >= 0; i++) {
+ types = ut_type_names[i].name;
+ if (ut_type_names[i].type == type)
+ break;
+ }
+
+ if (show_term || show_idle) {
+ if (now == 0)
+ time(&now);
+
+ if (stat(line, &sb) == 0) {
+ state = (sb.st_mode & 020) ? '+' : '-';
+ idle = now - sb.st_atime;
+ }
+
+ }
+
+ (void)printf("%-*.*s ", (int)maxname, (int)maxname, name);
+
+ if (show_term)
+ (void)printf("%c ", state);
+
+ (void)printf("%-*.*s ", (int)maxline, (int)maxline, line);
+ tstr = ctime(&t);
+ (void)printf("%.12s ", tstr ? tstr + 4 : "?");
+
+ if (show_idle) {
+ if (idle < 60)
+ (void)printf(" . ");
+ else if (idle < (24 * 60 * 60))
+ (void)printf("%02ld:%02ld ",
+ (long)(idle / (60 * 60)),
+ (long)(idle % (60 * 60)) / 60);
+ else
+ (void)printf(" old ");
+
+ (void)printf("\t%6d", pid);
+
+ if (show_details) {
+ if (type == RUN_LVL)
+ (void)printf("\tnew=%c old=%c", term, xit);
+ else
+ (void)printf("\tterm=%d exit=%d", term, xit);
+ (void)printf(" sess=%d", sess);
+ (void)printf(" type=%s ", types);
+ }
+ }
+
+ if (*host)
+ (void)printf("\t(%.*s)", (int)maxhost, host);
+ (void)putchar('\n');
+}
+
+static void
+output_labels(void)
+{
+ (void)printf("%-*.*s ", (int)maxname, (int)maxname, "USER");
+
+ if (show_term)
+ (void)printf("S ");
+
+ (void)printf("%-*.*s ", (int)maxline, (int)maxline, "LINE");
+ (void)printf("WHEN ");
+
+ if (show_idle) {
+ (void)printf("IDLE ");
+ (void)printf("\t PID");
+
+ (void)printf("\tCOMMENT");
+ }
+
+ (void)putchar('\n');
+}
+
+static void
+quick(const char *fname)
+{
+ struct utmpentry *ehead, *ep;
+ int num = 0;
+
+ (void)getutentries(fname, &ehead);
+ for (ep = ehead; ep != NULL; ep = ep->next) {
+ (void)printf("%-*s ", (int)maxname, ep->name);
+ if ((++num % 8) == 0)
+ (void)putchar('\n');
+ }
+ if (num % 8)
+ (void)putchar('\n');
+
+ (void)printf("# users = %d\n", num);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s [-abdHlmqrsTtuv] [file]\n\t%s am i\n",
+ getprogname(), getprogname());
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/write/term_chk.c b/usr.bin/write/term_chk.c
new file mode 100644
index 0000000..d7d2cbe
--- /dev/null
+++ b/usr.bin/write/term_chk.c
@@ -0,0 +1,137 @@
+/* $NetBSD: term_chk.c,v 1.8 2009/04/14 07:59:17 lukem Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: term_chk.c,v 1.8 2009/04/14 07:59:17 lukem Exp $");
+#endif
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <paths.h>
+#include <fcntl.h>
+#include <string.h>
+#include <err.h>
+
+#include "term_chk.h"
+
+/*
+ * term_chk - check that a terminal exists, and get the message bit
+ * and the access time
+ */
+int
+term_chk(uid_t uid, const char *tty, int *msgsokP, time_t *atimeP, int ismytty,
+ gid_t saved_egid)
+{
+ char path[MAXPATHLEN];
+ struct stat s;
+ int i, fd, serrno;
+
+ if (strstr(tty, "../") != NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+ i = snprintf(path, sizeof path, _PATH_DEV "%s", tty);
+ if (i < 0 || i >= (int)sizeof(path)) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ (void)setegid(saved_egid);
+ fd = open(path, O_WRONLY, 0);
+ serrno = errno;
+ (void)setegid(getgid());
+ errno = serrno;
+
+ if (fd == -1)
+ return(-1);
+ if (fstat(fd, &s) == -1)
+ goto error;
+ if (!isatty(fd))
+ goto error;
+ if (s.st_uid != uid && uid != 0) {
+ errno = EPERM;
+ goto error;
+ }
+ if (msgsokP)
+ *msgsokP = (s.st_mode & S_IWGRP) != 0; /* group write bit */
+ if (atimeP)
+ *atimeP = s.st_atime;
+ if (ismytty)
+ (void)close(fd);
+ return ismytty ? 0 : fd;
+error:
+ if (fd != -1) {
+ serrno = errno;
+ (void)close(fd);
+ errno = serrno;
+ }
+ return -1;
+}
+
+char *
+check_sender(time_t *atime, uid_t myuid, gid_t saved_egid)
+{
+ int myttyfd;
+ int msgsok;
+ char *mytty;
+
+ /* check that sender has write enabled */
+ if (isatty(fileno(stdin)))
+ myttyfd = fileno(stdin);
+ else if (isatty(fileno(stdout)))
+ myttyfd = fileno(stdout);
+ else if (isatty(fileno(stderr)))
+ myttyfd = fileno(stderr);
+ else if (atime == NULL)
+ return NULL;
+ else
+ errx(1, "Cannot find your tty");
+ if ((mytty = ttyname(myttyfd)) == NULL)
+ err(1, "Cannot find the name of your tty");
+ if (strncmp(mytty, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+ mytty += sizeof(_PATH_DEV) - 1;
+ if (term_chk(myuid, mytty, &msgsok, atime, 1, saved_egid) == -1)
+ err(1, "%s%s", _PATH_DEV, mytty);
+ if (!msgsok) {
+ warnx(
+ "You have write permission turned off; no reply possible");
+ }
+ return mytty;
+}
diff --git a/usr.bin/write/term_chk.h b/usr.bin/write/term_chk.h
new file mode 100644
index 0000000..aca4568
--- /dev/null
+++ b/usr.bin/write/term_chk.h
@@ -0,0 +1,36 @@
+/* $NetBSD: term_chk.h,v 1.2 2003/08/07 11:17:48 agc Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+int term_chk(uid_t, const char *, int *, time_t *, int, gid_t);
+char *check_sender(time_t *, uid_t, gid_t);
diff --git a/usr.bin/write/write.1 b/usr.bin/write/write.1
new file mode 100644
index 0000000..28d614c
--- /dev/null
+++ b/usr.bin/write/write.1
@@ -0,0 +1,106 @@
+.\" $NetBSD: write.1,v 1.6 2003/08/07 11:17:48 agc Exp $
+.\"
+.\" Copyright (c) 1989, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)write.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd June 6, 1993
+.Dt WRITE 1
+.Os
+.Sh NAME
+.Nm write
+.Nd send a message to another user
+.Sh SYNOPSIS
+.Nm
+.Ar user
+.Op Ar ttyname
+.Sh DESCRIPTION
+.Nm
+allows you to communicate with other users, by copying lines from
+your terminal to theirs.
+.Pp
+When you run the
+.Nm
+command, the user you are writing to gets a message of the form:
+.Pp
+.Dl Message from yourname@yourhost on yourtty at hh:mm ...
+.Pp
+Any further lines you enter will be copied to the specified user's
+terminal.
+If the other user wants to reply, they must run
+.Nm
+as well.
+.Pp
+When you are done, type an end-of-file or interrupt character.
+The other user will see the message
+.Ql EOF
+indicating that the
+conversation is over.
+.Pp
+You can prevent people (other than the super-user) from writing to you
+with the
+.Xr mesg 1
+command.
+Some commands, for example
+.Xr nroff 1
+and
+.Xr pr 1 ,
+disallow writing automatically, so that your output isn't overwritten.
+.Pp
+If the user you want to write to is logged in on more than one terminal,
+you can specify which terminal to write to by specifying the terminal
+name as the second operand to the
+.Nm
+command.
+Alternatively, you can let
+.Nm
+select one of the terminals \- it will pick the one with the shortest
+idle time.
+This is so that if the user is logged in at work and also dialed up from
+home, the message will go to the right place.
+.Pp
+The traditional protocol for writing to someone is that the string
+.Ql \-o ,
+either at the end of a line or on a line by itself, means that it's the
+other person's turn to talk.
+The string
+.Ql oo
+means that the person believes the conversation to be
+over.
+.Sh SEE ALSO
+.Xr mesg 1 ,
+.Xr talk 1 ,
+.Xr who 1
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v6 .
diff --git a/usr.bin/write/write.c b/usr.bin/write/write.c
new file mode 100644
index 0000000..945e92f
--- /dev/null
+++ b/usr.bin/write/write.c
@@ -0,0 +1,292 @@
+/* $NetBSD: write.c,v 1.27 2011/09/06 18:46:35 joerg Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)write.c 8.2 (Berkeley) 4/27/95";
+#else
+__RCSID("$NetBSD: write.c,v 1.27 2011/09/06 18:46:35 joerg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+
+#include "utmpentry.h"
+#include "term_chk.h"
+
+__dead static void done(int);
+static void do_write(int, const char *, const uid_t);
+static void wr_fputs(char *);
+static int search_utmp(char *, char *, uid_t, gid_t);
+static int utmp_chk(const char *, const char *);
+
+int
+main(int argc, char **argv)
+{
+ time_t atime;
+ uid_t myuid, uid;
+ int msgsok, ttyfd;
+ char *mytty;
+ gid_t saved_egid = getegid();
+
+ if (setegid(getgid()) == -1)
+ err(1, "setegid");
+ myuid = getuid();
+ ttyfd = -1;
+
+ mytty = check_sender(&atime, myuid, saved_egid);
+
+ /* check args */
+ switch (argc) {
+ case 2:
+ ttyfd = search_utmp(argv[1], mytty, myuid, saved_egid);
+ break;
+ case 3:
+ if (!strncmp(argv[2], _PATH_DEV, strlen(_PATH_DEV)))
+ argv[2] += strlen(_PATH_DEV);
+ if (uid_from_user(argv[1], &uid) == -1)
+ errx(1, "%s: unknown user", argv[1]);
+ if (utmp_chk(argv[1], argv[2]))
+ errx(1, "%s is not logged in on %s",
+ argv[1], argv[2]);
+ ttyfd = term_chk(uid, argv[2], &msgsok, &atime, 0, saved_egid);
+ if (ttyfd == -1)
+ err(1, "%s%s", _PATH_DEV, argv[2]);
+ if (myuid && !msgsok)
+ errx(1, "%s has messages disabled on %s",
+ argv[1], argv[2]);
+ break;
+ default:
+ (void)fprintf(stderr, "usage: write user [tty]\n");
+ exit(1);
+ }
+ if (setgid(getgid()) == -1)
+ err(1, "setgid");
+ do_write(ttyfd, mytty, myuid);
+ done(0);
+ /* NOTREACHED */
+#ifdef __GNUC__
+ return (0);
+#endif
+}
+
+/*
+ * utmp_chk - checks that the given user is actually logged in on
+ * the given tty
+ */
+static int
+utmp_chk(const char *user, const char *tty)
+{
+ struct utmpentry *ep;
+
+ (void)getutentries(NULL, &ep);
+
+ for (; ep; ep = ep->next)
+ if (strcmp(user, ep->name) == 0 && strcmp(tty, ep->line) == 0)
+ return(0);
+ return(1);
+}
+
+/*
+ * search_utmp - search utmp for the "best" terminal to write to
+ *
+ * Ignores terminals with messages disabled, and of the rest, returns
+ * the one with the most recent access time. Returns as value the number
+ * of the user's terminals with messages enabled, or -1 if the user is
+ * not logged in at all.
+ *
+ * Special case for writing to yourself - ignore the terminal you're
+ * writing from, unless that's the only terminal with messages enabled.
+ */
+static int
+search_utmp(char *user, char *mytty, uid_t myuid, gid_t saved_egid)
+{
+ char tty[MAXPATHLEN];
+ time_t bestatime, atime;
+ int nloggedttys, nttys, msgsok, user_is_me;
+ struct utmpentry *ep;
+ int fd, nfd;
+ uid_t uid;
+
+ if (uid_from_user(user, &uid) == -1)
+ errx(1, "%s: unknown user", user);
+
+ (void)getutentries(NULL, &ep);
+
+ nloggedttys = nttys = 0;
+ bestatime = 0;
+ user_is_me = 0;
+ fd = -1;
+ for (; ep; ep = ep->next)
+ if (strcmp(user, ep->name) == 0) {
+ ++nloggedttys;
+ nfd = term_chk(uid, ep->line, &msgsok, &atime, 0,
+ saved_egid);
+ if (nfd == -1)
+ continue; /* bad term? skip */
+ if (myuid && !msgsok) {
+ close(nfd);
+ continue; /* skip ttys with msgs off */
+ }
+ if (strcmp(ep->line, mytty) == 0) {
+ user_is_me = 1;
+ if (fd == -1)
+ fd = nfd;
+ else
+ close(nfd);
+ continue; /* don't write to yourself */
+ }
+ ++nttys;
+ if (atime > bestatime) {
+ bestatime = atime;
+ (void)strlcpy(tty, ep->line, sizeof(tty));
+ close(fd);
+ fd = nfd;
+ } else
+ close(nfd);
+ }
+
+ if (nloggedttys == 0)
+ errx(1, "%s is not logged in", user);
+ if (nttys == 0) {
+ if (user_is_me) /* ok, so write to yourself! */
+ return fd;
+ errx(1, "%s has messages disabled", user);
+ } else if (nttys > 1)
+ warnx("%s is logged in more than once; writing to %s",
+ user, tty);
+ return fd;
+}
+
+/*
+ * do_write - actually make the connection
+ */
+static void
+do_write(int ttyfd, const char *mytty, const uid_t myuid)
+{
+ const char *login;
+ char *nows;
+ struct passwd *pwd;
+ time_t now;
+ char host[MAXHOSTNAMELEN + 1], line[512];
+
+ /* Determine our login name before we re-open stdout */
+ if ((login = getlogin()) == NULL) {
+ if ((pwd = getpwuid(myuid)) != NULL)
+ login = pwd->pw_name;
+ else login = "???";
+ }
+
+ if (dup2(ttyfd, STDOUT_FILENO) == -1)
+ err(1, "dup2");
+
+ (void)signal(SIGINT, done);
+ (void)signal(SIGHUP, done);
+ (void)close(ttyfd);
+
+ /* print greeting */
+ if (gethostname(host, sizeof(host)) < 0)
+ (void)strlcpy(host, "???", sizeof(host));
+ else
+ host[sizeof(host) - 1] = '\0';
+ now = time(NULL);
+ nows = ctime(&now);
+ nows[16] = '\0';
+ (void)printf("\r\n\a\a\aMessage from %s@%s on %s at %s ...\r\n",
+ login, host, mytty, nows + 11);
+
+ while (fgets(line, sizeof(line), stdin) != NULL)
+ wr_fputs(line);
+}
+
+/*
+ * done - cleanup and exit
+ */
+static void
+done(int signo)
+{
+
+ (void)write(STDOUT_FILENO, "EOF\r\n", sizeof("EOF\r\n") - 1);
+ if (signo == 0)
+ exit(0);
+ else
+ _exit(0);
+}
+
+/*
+ * wr_fputs - like fputs(), but makes control characters visible and
+ * turns \n into \r\n
+ */
+static void
+wr_fputs(char *s)
+{
+ unsigned char c;
+
+#define PUTC(c) if (putchar(c) == EOF) goto err;
+
+ for (; *s != '\0'; ++s) {
+ c = toascii(*s);
+ if (c == '\n') {
+ PUTC('\r');
+ } else if (!isprint(c) && !isspace(c) && c != '\a') {
+ PUTC('^');
+ c ^= 0x40; /* DEL to ?, others to alpha */
+ }
+ PUTC(c);
+ }
+ return;
+
+err: err(1, NULL);
+#undef PUTC
+}
diff --git a/usr.bin/xargs/pathnames.h b/usr.bin/xargs/pathnames.h
new file mode 100644
index 0000000..3f05b3d
--- /dev/null
+++ b/usr.bin/xargs/pathnames.h
@@ -0,0 +1,36 @@
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 6/6/93
+ */
+
+#define _PATH_ECHO "/bin/echo"
diff --git a/usr.bin/xargs/strnsubst.c b/usr.bin/xargs/strnsubst.c
new file mode 100644
index 0000000..0953f49
--- /dev/null
+++ b/usr.bin/xargs/strnsubst.c
@@ -0,0 +1,115 @@
+/* $xMach: strnsubst.c,v 1.3 2002/02/23 02:10:24 jmallett Exp $ */
+
+/*
+ * Copyright (c) 2002 J. Mallett. All rights reserved.
+ * You may do whatever you want with this file as long as
+ * the above copyright and this notice remain intact, along
+ * with the following statement:
+ * For the man who taught me vi, and who got too old, too young.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+__FBSDID("$FreeBSD: src/usr.bin/xargs/strnsubst.c,v 1.8 2005/12/30 23:22:50 jmallett Exp $");
+#endif
+__RCSID("$NetBSD: strnsubst.c,v 1.1 2007/04/18 15:56:07 christos Exp $");
+#endif /* not lint */
+
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+
+void strnsubst(char **, const char *, const char *, size_t);
+
+/*
+ * Replaces str with a string consisting of str with match replaced with
+ * replstr as many times as can be done before the constructed string is
+ * maxsize bytes large. It does not free the string pointed to by str, it
+ * is up to the calling program to be sure that the original contents of
+ * str as well as the new contents are handled in an appropriate manner.
+ * If replstr is NULL, then that internally is changed to a nil-string, so
+ * that we can still pretend to do somewhat meaningful substitution.
+ * No value is returned.
+ */
+void
+strnsubst(char **str, const char *match, const char *replstr, size_t maxsize)
+{
+ char *s1, *s2, *this;
+
+ s1 = *str;
+ if (s1 == NULL)
+ return;
+ /*
+ * If maxsize is 0 then set it to to the length of s1, because we have
+ * to duplicate s1. XXX we maybe should double-check whether the match
+ * appears in s1. If it doesn't, then we also have to set the length
+ * to the length of s1, to avoid modifying the argument. It may make
+ * sense to check if maxsize is <= strlen(s1), because in that case we
+ * want to return the unmodified string, too.
+ */
+ if (maxsize == 0) {
+ match = NULL;
+ maxsize = strlen(s1) + 1;
+ }
+ s2 = calloc(maxsize, 1);
+ if (s2 == NULL)
+ err(1, "calloc");
+
+ if (replstr == NULL)
+ replstr = "";
+
+ if (match == NULL || replstr == NULL || maxsize == strlen(s1)) {
+ (void)strlcpy(s2, s1, maxsize);
+ goto done;
+ }
+
+ for (;;) {
+ this = strstr(s1, match);
+ if (this == NULL)
+ break;
+ if ((strlen(s2) + strlen(s1) + strlen(replstr) -
+ strlen(match) + 1) > maxsize) {
+ (void)strlcat(s2, s1, maxsize);
+ goto done;
+ }
+ (void)strncat(s2, s1, (uintptr_t)this - (uintptr_t)s1);
+ (void)strcat(s2, replstr);
+ s1 = this + strlen(match);
+ }
+ (void)strcat(s2, s1);
+done:
+ *str = s2;
+ return;
+}
+
+#ifdef TEST
+#include <stdio.h>
+
+int
+main(void)
+{
+ char *x, *y, *z, *za;
+
+ x = "{}%$";
+ strnsubst(&x, "%$", "{} enpury!", 255);
+ y = x;
+ strnsubst(&y, "}{}", "ybir", 255);
+ z = y;
+ strnsubst(&z, "{", "v ", 255);
+ za = z;
+ strnsubst(&z, NULL, za, 255);
+ if (strcmp(z, "v ybir enpury!") == 0)
+ (void)printf("strnsubst() seems to work!\n");
+ else
+ (void)printf("strnsubst() is broken.\n");
+ (void)printf("%s\n", z);
+ free(x);
+ free(y);
+ free(z);
+ free(za);
+ return 0;
+}
+#endif
diff --git a/usr.bin/xargs/xargs.1 b/usr.bin/xargs/xargs.1
new file mode 100644
index 0000000..d693838
--- /dev/null
+++ b/usr.bin/xargs/xargs.1
@@ -0,0 +1,387 @@
+.\" $NetBSD: xargs.1,v 1.23 2012/10/13 14:18:17 njoly Exp $
+.\"
+.\" Copyright (c) 1990, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" John B. Roll Jr. and the Institute of Electrical and Electronics
+.\" Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)xargs.1 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD: src/usr.bin/xargs/xargs.1,v 1.40 2010/12/11 08:32:16 joel Exp $
+.\" $xMach: xargs.1,v 1.2 2002/02/23 05:23:37 tim Exp $
+.\"
+.Dd December 21, 2010
+.Dt XARGS 1
+.Os
+.Sh NAME
+.Nm xargs
+.Nd "construct argument list(s) and execute utility"
+.Sh SYNOPSIS
+.Nm
+.Op Fl 0oprt
+.Op Fl E Ar eofstr
+.Oo
+.Fl I Ar replstr
+.Op Fl R Ar replacements
+.Op Fl S Ar replsize
+.Oc
+.Op Fl J Ar replstr
+.Op Fl L Ar number
+.Oo
+.Fl n Ar number
+.Op Fl x
+.Oc
+.Op Fl P Ar maxprocs
+.Op Fl s Ar size
+.Op Ar utility Op Ar argument ...
+.Sh DESCRIPTION
+The
+.Nm
+utility reads space, tab, newline and end-of-file delimited strings
+from the standard input and executes
+.Ar utility
+with the strings as
+arguments.
+.Pp
+Any arguments specified on the command line are given to
+.Ar utility
+upon each invocation, followed by some number of the arguments read
+from the standard input of
+.Nm .
+This is repeated until standard input is exhausted.
+.Pp
+Spaces, tabs and newlines may be embedded in arguments using single
+(``\ '\ '')
+or double (``"'') quotes or backslashes (``\e'').
+Single quotes escape all non-single quote characters, excluding newlines,
+up to the matching single quote.
+Double quotes escape all non-double quote characters, excluding newlines,
+up to the matching double quote.
+Any single character, including newlines, may be escaped by a backslash.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl 0
+Change
+.Nm
+to expect NUL
+(``\e0'')
+characters as separators, instead of spaces and newlines.
+This is expected to be used in concert with the
+.Fl print0
+function in
+.Xr find 1 .
+.It Fl E Ar eofstr
+Use
+.Ar eofstr
+as a logical EOF marker.
+.It Fl I Ar replstr
+Execute
+.Ar utility
+for each input line, replacing one or more occurrences of
+.Ar replstr
+in up to
+.Ar replacements
+(or 5 if no
+.Fl R
+flag is specified) arguments to
+.Ar utility
+with the entire line of input.
+The resulting arguments, after replacement is done, will not be allowed to grow
+beyond
+.Ar replsize
+(or 255 if no
+.Fl S
+flag is specified)
+bytes; this is implemented by concatenating as much of the argument
+containing
+.Ar replstr
+as possible, to the constructed arguments to
+.Ar utility ,
+up to
+.Ar replsize
+bytes.
+The size limit does not apply to arguments to
+.Ar utility
+which do not contain
+.Ar replstr ,
+and furthermore, no replacement will be done on
+.Ar utility
+itself.
+Implies
+.Fl x .
+.It Fl J Ar replstr
+If this option is specified,
+.Nm
+will use the data read from standard input to replace the first occurrence of
+.Ar replstr
+instead of appending that data after all other arguments.
+This option will not affect how many arguments will be read from input
+.Pq Fl n ,
+or the size of the command(s)
+.Nm
+will generate
+.Pq Fl s .
+The option just moves where those arguments will be placed in the command(s)
+that are executed.
+The
+.Ar replstr
+must show up as a distinct
+.Ar argument
+to
+.Nm .
+It will not be recognized if, for instance, it is in the middle of a
+quoted string.
+Furthermore, only the first occurrence of the
+.Ar replstr
+will be replaced.
+For example, the following command will copy the list of files and
+directories which start with an uppercase letter in the current
+directory to
+.Pa destdir :
+.Pp
+.Dl /bin/ls -1d [A-Z]* | xargs -J % cp -rp % destdir
+.It Fl L Ar number
+Call
+.Ar utility
+for every
+.Ar number
+lines read.
+If EOF is reached and fewer lines have been read than
+.Ar number
+then
+.Ar utility
+will be called with the available lines.
+.It Fl n Ar number
+Set the maximum number of arguments taken from standard input for each
+invocation of
+.Ar utility .
+An invocation of
+.Ar utility
+will use less than
+.Ar number
+standard input arguments if the number of bytes accumulated (see the
+.Fl s
+option) exceeds the specified
+.Ar size
+or there are fewer than
+.Ar number
+arguments remaining for the last invocation of
+.Ar utility .
+The current default value for
+.Ar number
+is 5000.
+.It Fl o
+Reopen stdin as
+.Pa /dev/tty
+in the child process before executing the command.
+This is useful if you want
+.Nm
+to run an interactive application.
+.It Fl P Ar maxprocs
+Parallel mode: run at most
+.Ar maxprocs
+invocations of
+.Ar utility
+at once.
+.It Fl p
+Echo each command to be executed and ask the user whether it should be
+executed.
+An affirmative response,
+.Ql y
+in the POSIX locale,
+causes the command to be executed, any other response causes it to be
+skipped.
+No commands are executed if the process is not attached to a terminal.
+.It Fl r
+Compatibility with GNU
+.Nm .
+The GNU version of
+.Nm
+runs the
+.Ar utility
+argument at least once, even if
+.Nm
+input is empty, and it supports a
+.Fl r
+option to inhibit this behavior.
+The
+.Nx
+version of
+.Nm
+does not run the
+.Ar utility
+argument on empty input, but it supports the
+.Fl r
+option for command-line compatibility with GNU
+.Nm ;
+but the
+.Fl r
+option does nothing in the
+.Nx
+version of
+.Nm .
+.It Fl R Ar replacements
+Specify the maximum number of arguments that
+.Fl I
+will do replacement in.
+If
+.Ar replacements
+is negative, the number of arguments in which to replace is unbounded.
+.It Fl S Ar replsize
+Specify the amount of space (in bytes) that
+.Fl I
+can use for replacements.
+The default for
+.Ar replsize
+is 255.
+.It Fl s Ar size
+Set the maximum number of bytes for the command line length provided to
+.Ar utility .
+The sum of the length of the utility name, the arguments passed to
+.Ar utility
+(including
+.Dv NULL
+terminators) and the current environment will be less than or equal to
+this number.
+The current default value for
+.Ar size
+is
+.Dv ARG_MAX
+- 4096.
+.It Fl t
+Echo the command to be executed to standard error immediately before it
+is executed.
+.It Fl x
+Force
+.Nm
+to terminate immediately if a command line containing
+.Ar number
+arguments will not fit in the specified (or default) command line length.
+.El
+.Pp
+If
+.Ar utility
+is omitted,
+.Xr echo 1
+is used.
+.Pp
+Undefined behavior may occur if
+.Ar utility
+reads from the standard input.
+.Pp
+The
+.Nm
+utility exits immediately (without processing any further input) if a
+command line cannot be assembled,
+.Ar utility
+cannot be invoked, an invocation of
+.Ar utility
+is terminated by a signal,
+or an invocation of
+.Ar utility
+exits with a value of 255.
+.Sh FILES
+.Bl -tag -width /dev/tty -compact
+.It Pa /dev/tty
+used to read responses in prompt mode
+.El
+.Sh EXIT STATUS
+.Nm
+exits with one of the following values:
+.Bl -tag -width Ds -compact
+.It 0
+All invocations of
+.Ar utility
+returned a zero exit status.
+.It 123
+One or more invocations of
+.Ar utility
+returned a nonzero exit status.
+.It 124
+The
+.Ar utility
+exited with a 255 exit status.
+.It 125
+The
+.Ar utility
+was killed or stopped by a signal.
+.It 126
+The
+.Ar utility
+was found but could not be invoked.
+.It 127
+The
+.Ar utility
+could not be found.
+.It 1
+Some other error occurred.
+.El
+.Sh SEE ALSO
+.Xr echo 1 ,
+.Xr find 1 ,
+.Xr execvp 3
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compliant.
+The
+.Fl J , o , P , R ,
+and
+.Fl S
+options are non-standard
+.Fx
+extensions which may not be available on other operating systems.
+.Sh HISTORY
+The
+.Nm
+utility appeared in PWB UNIX 1.0.
+It made its first BSD appearance in the 4.3 Reno release.
+.Pp
+The meaning of 123, 124, and 125 exit values and the
+.Fl 0
+option were taken from GNU xargs.
+.Sh BUGS
+If
+.Ar utility
+attempts to invoke another command such that the number of arguments or the
+size of the environment is increased, it risks
+.Xr execvp 3
+failing with
+.Er E2BIG .
+.Pp
+The
+.Nm
+utility does not take multibyte characters into account when performing
+string comparisons for the
+.Fl I
+and
+.Fl J
+options, which may lead to incorrect results in some locales.
diff --git a/usr.bin/xargs/xargs.c b/usr.bin/xargs/xargs.c
new file mode 100644
index 0000000..5c89377
--- /dev/null
+++ b/usr.bin/xargs/xargs.c
@@ -0,0 +1,652 @@
+/* $NetBSD: xargs.c,v 1.20 2010/12/17 11:32:57 plunky Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * John B. Roll Jr.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $xMach: xargs.c,v 1.6 2002/02/23 05:27:47 tim Exp $
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1990, 1993\
+ The Regents of the University of California. All rights reserved.");
+#if 0
+static char sccsid[] = "@(#)xargs.c 8.1 (Berkeley) 6/6/93";
+__FBSDID("$FreeBSD: src/usr.bin/xargs/xargs.c,v 1.62 2006/01/01 22:59:54 jmallett Exp $");
+#endif
+__RCSID("$NetBSD: xargs.c,v 1.20 2010/12/17 11:32:57 plunky Exp $");
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <langinfo.h>
+#include <locale.h>
+#include <paths.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "pathnames.h"
+
+static void parse_input(int, char *[]);
+static void prerun(int, char *[]);
+static int prompt(void);
+static void run(char **);
+static void usage(void) __dead;
+void strnsubst(char **, const char *, const char *, size_t);
+static void waitchildren(const char *, int);
+
+static char echo[] = _PATH_ECHO;
+static char **av, **bxp, **ep, **endxp, **xp;
+static char *argp, *bbp, *ebp, *inpline, *p, *replstr;
+static const char *eofstr;
+static int count, insingle, indouble, oflag, pflag, tflag, Rflag, rval, zflag;
+static int cnt, Iflag, jfound, Lflag, Sflag, wasquoted, xflag;
+static int curprocs, maxprocs;
+
+static volatile int childerr;
+
+extern char **environ;
+
+int
+main(int argc, char *argv[])
+{
+ long arg_max;
+ int ch, Jflag, nargs, nflag, nline;
+ size_t linelen;
+ char *endptr;
+
+ setprogname(argv[0]);
+
+ inpline = replstr = NULL;
+ ep = environ;
+ eofstr = "";
+ Jflag = nflag = 0;
+
+ (void)setlocale(LC_ALL, "");
+
+ /*
+ * SUSv3 says of the exec family of functions:
+ * The number of bytes available for the new process'
+ * combined argument and environment lists is {ARG_MAX}. It
+ * is implementation-defined whether null terminators,
+ * pointers, and/or any alignment bytes are included in this
+ * total.
+ *
+ * SUSv3 says of xargs:
+ * ... the combined argument and environment lists ...
+ * shall not exceed {ARG_MAX}-2048.
+ *
+ * To be conservative, we use ARG_MAX - 4K, and we do include
+ * nul terminators and pointers in the calculation.
+ *
+ * Given that the smallest argument is 2 bytes in length, this
+ * means that the number of arguments is limited to:
+ *
+ * (ARG_MAX - 4K - LENGTH(env + utility + arguments)) / 2.
+ *
+ * We arbitrarily limit the number of arguments to 5000. This is
+ * allowed by POSIX.2 as long as the resulting minimum exec line is
+ * at least LINE_MAX. Realloc'ing as necessary is possible, but
+ * probably not worthwhile.
+ */
+ nargs = 5000;
+ if ((arg_max = sysconf(_SC_ARG_MAX)) == -1)
+ errx(1, "sysconf(_SC_ARG_MAX) failed");
+ nline = arg_max - 4 * 1024;
+ while (*ep != NULL) {
+ /* 1 byte for each '\0' */
+ nline -= strlen(*ep++) + 1 + sizeof(*ep);
+ }
+ maxprocs = 1;
+ while ((ch = getopt(argc, argv, "0E:I:J:L:n:oP:pR:S:s:rtx")) != -1)
+ switch (ch) {
+ case 'E':
+ eofstr = optarg;
+ break;
+ case 'I':
+ Jflag = 0;
+ Iflag = 1;
+ Lflag = 1;
+ replstr = optarg;
+ break;
+ case 'J':
+ Iflag = 0;
+ Jflag = 1;
+ replstr = optarg;
+ break;
+ case 'L':
+ Lflag = atoi(optarg);
+ break;
+ case 'n':
+ nflag = 1;
+ if ((nargs = atoi(optarg)) <= 0)
+ errx(1, "illegal argument count");
+ break;
+ case 'o':
+ oflag = 1;
+ break;
+ case 'P':
+ if ((maxprocs = atoi(optarg)) <= 0)
+ errx(1, "max. processes must be >0");
+ break;
+ case 'p':
+ pflag = 1;
+ break;
+ case 'R':
+ Rflag = strtol(optarg, &endptr, 10);
+ if (*endptr != '\0')
+ errx(1, "replacements must be a number");
+ break;
+ case 'r':
+ /* GNU compatibility */
+ break;
+ case 'S':
+ Sflag = strtoul(optarg, &endptr, 10);
+ if (*endptr != '\0')
+ errx(1, "replsize must be a number");
+ break;
+ case 's':
+ nline = atoi(optarg);
+ break;
+ case 't':
+ tflag = 1;
+ break;
+ case 'x':
+ xflag = 1;
+ break;
+ case '0':
+ zflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!Iflag && Rflag)
+ usage();
+ if (!Iflag && Sflag)
+ usage();
+ if (Iflag && !Rflag)
+ Rflag = 5;
+ if (Iflag && !Sflag)
+ Sflag = 255;
+ if (xflag && !nflag)
+ usage();
+ if (Iflag || Lflag)
+ xflag = 1;
+ if (replstr != NULL && *replstr == '\0')
+ errx(1, "replstr may not be empty");
+
+ /*
+ * Allocate pointers for the utility name, the utility arguments,
+ * the maximum arguments to be read from stdin and the trailing
+ * NULL.
+ */
+ linelen = 1 + argc + nargs + 1;
+ if ((av = bxp = malloc(linelen * sizeof(char **))) == NULL)
+ errx(1, "malloc failed");
+
+ /*
+ * Use the user's name for the utility as argv[0], just like the
+ * shell. Echo is the default. Set up pointers for the user's
+ * arguments.
+ */
+ if (*argv == NULL)
+ cnt = strlen(*bxp++ = echo);
+ else {
+ do {
+ if (Jflag && strcmp(*argv, replstr) == 0) {
+ char **avj;
+ jfound = 1;
+ argv++;
+ for (avj = argv; *avj; avj++)
+ cnt += strlen(*avj) + 1;
+ break;
+ }
+ cnt += strlen(*bxp++ = *argv) + 1;
+ } while (*++argv != NULL);
+ }
+
+ /*
+ * Set up begin/end/traversing pointers into the array. The -n
+ * count doesn't include the trailing NULL pointer, so the malloc
+ * added in an extra slot.
+ */
+ endxp = (xp = bxp) + nargs;
+
+ /*
+ * Allocate buffer space for the arguments read from stdin and the
+ * trailing NULL. Buffer space is defined as the default or specified
+ * space, minus the length of the utility name and arguments. Set up
+ * begin/end/traversing pointers into the array. The -s count does
+ * include the trailing NULL, so the malloc didn't add in an extra
+ * slot.
+ */
+ nline -= cnt;
+ if (nline <= 0)
+ errx(1, "insufficient space for command");
+
+ if ((bbp = malloc((size_t)(nline + 1))) == NULL)
+ errx(1, "malloc failed");
+ ebp = (argp = p = bbp) + nline - 1;
+ for (;;)
+ parse_input(argc, argv);
+}
+
+static void
+parse_input(int argc, char *argv[])
+{
+ int ch, foundeof;
+ char **avj;
+
+ foundeof = 0;
+
+ switch (ch = getchar()) {
+ case EOF:
+ /* No arguments since last exec. */
+ if (p == bbp) {
+ waitchildren(*argv, 1);
+ exit(rval);
+ }
+ goto arg1;
+ case ' ':
+ case '\t':
+ /* Quotes escape tabs and spaces. */
+ if (insingle || indouble || zflag)
+ goto addch;
+ goto arg2;
+ case '\0':
+ if (zflag) {
+ /*
+ * Increment 'count', so that nulls will be treated
+ * as end-of-line, as well as end-of-argument. This
+ * is needed so -0 works properly with -I and -L.
+ */
+ count++;
+ goto arg2;
+ }
+ goto addch;
+ case '\n':
+ if (zflag)
+ goto addch;
+ count++; /* Indicate end-of-line (used by -L) */
+
+ /* Quotes do not escape newlines. */
+arg1: if (insingle || indouble)
+ errx(1, "unterminated quote");
+arg2:
+ foundeof = *eofstr != '\0' &&
+ strncmp(argp, eofstr, (size_t)(p - argp)) == 0;
+
+ /* Do not make empty args unless they are quoted */
+ if ((argp != p || wasquoted) && !foundeof) {
+ *p++ = '\0';
+ *xp++ = argp;
+ if (Iflag) {
+ size_t curlen;
+
+ if (inpline == NULL)
+ curlen = 0;
+ else {
+ /*
+ * If this string is not zero
+ * length, append a space for
+ * separation before the next
+ * argument.
+ */
+ if ((curlen = strlen(inpline)) != 0)
+ (void)strcat(inpline, " ");
+ }
+ curlen++;
+ /*
+ * Allocate enough to hold what we will
+ * be holding in a second, and to append
+ * a space next time through, if we have
+ * to.
+ */
+ inpline = realloc(inpline, curlen + 2 +
+ strlen(argp));
+ if (inpline == NULL)
+ errx(1, "realloc failed");
+ if (curlen == 1)
+ (void)strcpy(inpline, argp);
+ else
+ (void)strcat(inpline, argp);
+ }
+ }
+
+ /*
+ * If max'd out on args or buffer, or reached EOF,
+ * run the command. If xflag and max'd out on buffer
+ * but not on args, object. Having reached the limit
+ * of input lines, as specified by -L is the same as
+ * maxing out on arguments.
+ */
+ if (xp == endxp || p > ebp || ch == EOF ||
+ (Lflag <= count && xflag) || foundeof) {
+ if (xflag && xp != endxp && p > ebp)
+ errx(1, "insufficient space for arguments");
+ if (jfound) {
+ for (avj = argv; *avj; avj++)
+ *xp++ = *avj;
+ }
+ prerun(argc, av);
+ if (ch == EOF || foundeof) {
+ waitchildren(*argv, 1);
+ exit(rval);
+ }
+ p = bbp;
+ xp = bxp;
+ count = 0;
+ }
+ argp = p;
+ wasquoted = 0;
+ break;
+ case '\'':
+ if (indouble || zflag)
+ goto addch;
+ insingle = !insingle;
+ wasquoted = 1;
+ break;
+ case '"':
+ if (insingle || zflag)
+ goto addch;
+ indouble = !indouble;
+ wasquoted = 1;
+ break;
+ case '\\':
+ if (zflag)
+ goto addch;
+ /* Backslash escapes anything, is escaped by quotes. */
+ if (!insingle && !indouble && (ch = getchar()) == EOF)
+ errx(1, "backslash at EOF");
+ /* FALLTHROUGH */
+ default:
+addch: if (p < ebp) {
+ *p++ = ch;
+ break;
+ }
+
+ /* If only one argument, not enough buffer space. */
+ if (bxp == xp)
+ errx(1, "insufficient space for argument");
+ /* Didn't hit argument limit, so if xflag object. */
+ if (xflag)
+ errx(1, "insufficient space for arguments");
+
+ if (jfound) {
+ for (avj = argv; *avj; avj++)
+ *xp++ = *avj;
+ }
+ prerun(argc, av);
+ xp = bxp;
+ cnt = ebp - argp;
+ (void)memcpy(bbp, argp, (size_t)cnt);
+ p = (argp = bbp) + cnt;
+ *p++ = ch;
+ break;
+ }
+}
+
+/*
+ * Do things necessary before run()'ing, such as -I substitution,
+ * and then call run().
+ */
+static void
+prerun(int argc, char *argv[])
+{
+ char **tmp, **tmp2, **avj;
+ int repls;
+
+ repls = Rflag;
+
+ if (argc == 0 || repls == 0) {
+ *xp = NULL;
+ run(argv);
+ return;
+ }
+
+ avj = argv;
+
+ /*
+ * Allocate memory to hold the argument list, and
+ * a NULL at the tail.
+ */
+ tmp = malloc((argc + 1) * sizeof(char**));
+ if (tmp == NULL)
+ errx(1, "malloc failed");
+ tmp2 = tmp;
+
+ /*
+ * Save the first argument and iterate over it, we
+ * cannot do strnsubst() to it.
+ */
+ if ((*tmp++ = strdup(*avj++)) == NULL)
+ errx(1, "strdup failed");
+
+ /*
+ * For each argument to utility, if we have not used up
+ * the number of replacements we are allowed to do, and
+ * if the argument contains at least one occurrence of
+ * replstr, call strnsubst(), else just save the string.
+ * Iterations over elements of avj and tmp are done
+ * where appropriate.
+ */
+ while (--argc) {
+ *tmp = *avj++;
+ if (repls && strstr(*tmp, replstr) != NULL) {
+ strnsubst(tmp++, replstr, inpline, (size_t)Sflag);
+ if (repls > 0)
+ repls--;
+ } else {
+ if ((*tmp = strdup(*tmp)) == NULL)
+ errx(1, "strdup failed");
+ tmp++;
+ }
+ }
+
+ /*
+ * Run it.
+ */
+ *tmp = NULL;
+ run(tmp2);
+
+ /*
+ * Walk from the tail to the head, free along the way.
+ */
+ for (; tmp2 != tmp; tmp--)
+ free(*tmp);
+ /*
+ * Now free the list itself.
+ */
+ free(tmp2);
+
+ /*
+ * Free the input line buffer, if we have one.
+ */
+ if (inpline != NULL) {
+ free(inpline);
+ inpline = NULL;
+ }
+}
+
+static void
+run(char **argv)
+{
+ int fd;
+ char **avec;
+
+ /*
+ * If the user wants to be notified of each command before it is
+ * executed, notify them. If they want the notification to be
+ * followed by a prompt, then prompt them.
+ */
+ if (tflag || pflag) {
+ (void)fprintf(stderr, "%s", *argv);
+ for (avec = argv + 1; *avec != NULL; ++avec)
+ (void)fprintf(stderr, " %s", *avec);
+ /*
+ * If the user has asked to be prompted, do so.
+ */
+ if (pflag)
+ /*
+ * If they asked not to exec, return without execution
+ * but if they asked to, go to the execution. If we
+ * could not open their tty, break the switch and drop
+ * back to -t behaviour.
+ */
+ switch (prompt()) {
+ case 0:
+ return;
+ case 1:
+ goto exec;
+ case 2:
+ break;
+ }
+ (void)fprintf(stderr, "\n");
+ (void)fflush(stderr);
+ }
+exec:
+ childerr = 0;
+ switch (vfork()) {
+ case -1:
+ err(1, "vfork");
+ /*NOTREACHED*/
+ case 0:
+ if (oflag) {
+ if ((fd = open(_PATH_TTY, O_RDONLY)) == -1)
+ err(1, "can't open /dev/tty");
+ } else {
+ fd = open(_PATH_DEVNULL, O_RDONLY);
+ }
+ if (fd > STDIN_FILENO) {
+ if (dup2(fd, STDIN_FILENO) != 0)
+ err(1, "can't dup2 to stdin");
+ (void)close(fd);
+ }
+ (void)execvp(argv[0], argv);
+ childerr = errno;
+ _exit(1);
+ }
+ curprocs++;
+ waitchildren(*argv, 0);
+}
+
+static void
+waitchildren(const char *name, int waitall)
+{
+ pid_t pid;
+ int status;
+
+ while ((pid = waitpid(-1, &status, !waitall && curprocs < maxprocs ?
+ WNOHANG : 0)) > 0) {
+ curprocs--;
+ /* If we couldn't invoke the utility, exit. */
+ if (childerr != 0) {
+ errno = childerr;
+ err(errno == ENOENT ? 127 : 126, "%s", name);
+ }
+ /*
+ * According to POSIX, we have to exit if the utility exits
+ * with a 255 status, or is interrupted by a signal. xargs
+ * is allowed to return any exit status between 1 and 125
+ * in these cases, but we'll use 124 and 125, the same
+ * values used by GNU xargs.
+ */
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS (status) == 255) {
+ warnx ("%s exited with status 255", name);
+ exit(124);
+ } else if (WEXITSTATUS (status) != 0) {
+ rval = 123;
+ }
+ } else if (WIFSIGNALED (status)) {
+ if (WTERMSIG(status) < NSIG) {
+ warnx("%s terminated by SIG%s", name,
+ sys_signame[WTERMSIG(status)]);
+ } else {
+ warnx("%s terminated by signal %d", name,
+ WTERMSIG(status));
+ }
+ exit(125);
+ }
+ }
+ if (pid == -1 && errno != ECHILD)
+ err(1, "waitpid");
+}
+
+/*
+ * Prompt the user about running a command.
+ */
+static int
+prompt(void)
+{
+ regex_t cre;
+ size_t rsize;
+ int match;
+ char *response;
+ FILE *ttyfp;
+
+ if ((ttyfp = fopen(_PATH_TTY, "r")) == NULL)
+ return (2); /* Indicate that the TTY failed to open. */
+ (void)fprintf(stderr, "?...");
+ (void)fflush(stderr);
+ if ((response = fgetln(ttyfp, &rsize)) == NULL ||
+ regcomp(&cre, nl_langinfo(YESEXPR), REG_BASIC) != 0) {
+ (void)fclose(ttyfp);
+ return (0);
+ }
+ response[rsize - 1] = '\0';
+ match = regexec(&cre, response, 0, NULL, 0);
+ (void)fclose(ttyfp);
+ regfree(&cre);
+ return (match == 0);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+"Usage: %s [-0opt] [-E eofstr] [-I replstr [-R replacements] [-S replsize]]\n"
+" [-J replstr] [-L number] [-n number [-x]] [-P maxprocs]\n"
+" [-s size] [utility [argument ...]]\n", getprogname());
+ exit(1);
+}