morpheus-base

morpheus base system
git clone git://git.2f30.org/morpheus-base
Log | Files | Refs

commit c28e6b723b17b1463f8c7425c6c0b09c9311272d
Author: sin <sin@2f30.org>
Date:   Wed, 21 Jan 2015 01:05:19 +0000

Initial commit

Diffstat:
AMakefile | 16++++++++++++++++
Aawk/Makefile | 21+++++++++++++++++++++
Aawk/arc4random.c | 12++++++++++++
Aawk/awk.1 | 800+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aawk/awk.h | 240+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aawk/awkgram.y | 487+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aawk/b.c | 958+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aawk/lex.c | 597+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aawk/lib.c | 739+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aawk/main.c | 212+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aawk/maketab.c | 172+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aawk/parse.c | 277+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aawk/proto.h | 196+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aawk/reallocarray.c | 38++++++++++++++++++++++++++++++++++++++
Aawk/run.c | 2027+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aawk/strlcat.c | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Aawk/strlcpy.c | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aawk/tran.c | 458+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aawk/util.h | 10++++++++++
Asbase/LICENSE | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/Makefile | 205+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/README | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/TODO | 42++++++++++++++++++++++++++++++++++++++++++
Asbase/arg.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/basename.1 | 26++++++++++++++++++++++++++
Asbase/basename.c | 41+++++++++++++++++++++++++++++++++++++++++
Asbase/cal.1 | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/cal.c | 216+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/cat.1 | 29+++++++++++++++++++++++++++++
Asbase/cat.c | 45+++++++++++++++++++++++++++++++++++++++++++++
Asbase/chgrp.1 | 22++++++++++++++++++++++
Asbase/chgrp.c | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/chmod.1 | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/chmod.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/chown.1 | 20++++++++++++++++++++
Asbase/chown.c | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/chroot.1 | 26++++++++++++++++++++++++++
Asbase/chroot.c | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/cksum.1 | 27+++++++++++++++++++++++++++
Asbase/cksum.c | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/cmp.1 | 42++++++++++++++++++++++++++++++++++++++++++
Asbase/cmp.c | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/cols.1 | 41+++++++++++++++++++++++++++++++++++++++++
Asbase/cols.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/comm.1 | 43+++++++++++++++++++++++++++++++++++++++++++
Asbase/comm.c | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/compat.h | 6++++++
Asbase/config.mk | 15+++++++++++++++
Asbase/cp.1 | 42++++++++++++++++++++++++++++++++++++++++++
Asbase/cp.c | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/cron.1 | 24++++++++++++++++++++++++
Asbase/cron.c | 494+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/crypt.h | 12++++++++++++
Asbase/cut.1 | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/cut.c | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/date.1 | 26++++++++++++++++++++++++++
Asbase/date.c | 46++++++++++++++++++++++++++++++++++++++++++++++
Asbase/dirname.1 | 14++++++++++++++
Asbase/dirname.c | 29+++++++++++++++++++++++++++++
Asbase/du.1 | 37+++++++++++++++++++++++++++++++++++++
Asbase/du.c | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/echo.1 | 14++++++++++++++
Asbase/echo.c | 32++++++++++++++++++++++++++++++++
Asbase/env.1 | 41+++++++++++++++++++++++++++++++++++++++++
Asbase/env.c | 45+++++++++++++++++++++++++++++++++++++++++++++
Asbase/expand.1 | 25+++++++++++++++++++++++++
Asbase/expand.c | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/expr.1 | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/expr.c | 287+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/false.1 | 16++++++++++++++++
Asbase/false.c | 6++++++
Asbase/fold.1 | 25+++++++++++++++++++++++++
Asbase/fold.c | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/fs.h | 14++++++++++++++
Asbase/grep.1 | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/grep.c | 255+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/head.1 | 18++++++++++++++++++
Asbase/head.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/hostname.1 | 12++++++++++++
Asbase/hostname.c | 34++++++++++++++++++++++++++++++++++
Asbase/kill.1 | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/kill.c | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/libutf/chartorunearr.c | 27+++++++++++++++++++++++++++
Asbase/libutf/readrune.c | 46++++++++++++++++++++++++++++++++++++++++++++++
Asbase/libutf/rune.c | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/libutf/runetype.c | 48++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/libutf/utf.c | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/libutf/writerune.c | 23+++++++++++++++++++++++
Asbase/libutil/agetcwd.c | 18++++++++++++++++++
Asbase/libutil/apathmax.c | 22++++++++++++++++++++++
Asbase/libutil/concat.c | 20++++++++++++++++++++
Asbase/libutil/cp.c | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/libutil/crypt.c | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/libutil/ealloc.c | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asbase/libutil/enmasse.c | 41+++++++++++++++++++++++++++++++++++++++++
Asbase/libutil/eprintf.c | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/libutil/eregcomp.c | 25+++++++++++++++++++++++++
Asbase/libutil/estrtod.c | 18++++++++++++++++++
Asbase/libutil/estrtol.c | 27+++++++++++++++++++++++++++
Asbase/libutil/fnck.c | 20++++++++++++++++++++
Asbase/libutil/getlines.c | 27+++++++++++++++++++++++++++
Asbase/libutil/human.c | 22++++++++++++++++++++++
Asbase/libutil/md5.c | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/libutil/mode.c | 163+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/libutil/putword.c | 16++++++++++++++++
Asbase/libutil/recurse.c | 42++++++++++++++++++++++++++++++++++++++++++
Asbase/libutil/rm.c | 17+++++++++++++++++
Asbase/libutil/sha1.c | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/libutil/sha256.c | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/libutil/sha512.c | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/libutil/strcasestr.c | 34++++++++++++++++++++++++++++++++++
Asbase/libutil/strlcat.c | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/libutil/strlcpy.c | 48++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/link.1 | 14++++++++++++++
Asbase/link.c | 17+++++++++++++++++
Asbase/ln.1 | 36++++++++++++++++++++++++++++++++++++
Asbase/ln.c | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/logger.1 | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/logger.c | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/logname.1 | 8++++++++
Asbase/logname.c | 20++++++++++++++++++++
Asbase/ls.1 | 46++++++++++++++++++++++++++++++++++++++++++++++
Asbase/ls.c | 310+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/md5.h | 18++++++++++++++++++
Asbase/md5sum.1 | 12++++++++++++
Asbase/md5sum.c | 43+++++++++++++++++++++++++++++++++++++++++++
Asbase/mkdir.1 | 19+++++++++++++++++++
Asbase/mkdir.c | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/mkfifo.1 | 19+++++++++++++++++++
Asbase/mkfifo.c | 40++++++++++++++++++++++++++++++++++++++++
Asbase/mktemp.1 | 25+++++++++++++++++++++++++
Asbase/mktemp.c | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/mv.1 | 22++++++++++++++++++++++
Asbase/mv.c | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/nice.1 | 18++++++++++++++++++
Asbase/nice.c | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/nl.1 | 46++++++++++++++++++++++++++++++++++++++++++++++
Asbase/nl.c | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/nohup.1 | 16++++++++++++++++
Asbase/nohup.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/paste.1 | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/paste.c | 225+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/printenv.1 | 19+++++++++++++++++++
Asbase/printenv.c | 38++++++++++++++++++++++++++++++++++++++
Asbase/printf.1 | 386+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/printf.c | 497+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/pwd.1 | 32++++++++++++++++++++++++++++++++
Asbase/pwd.c | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/queue.h | 648+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/readlink.1 | 25+++++++++++++++++++++++++
Asbase/readlink.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/renice.1 | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/renice.c | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/rm.1 | 22++++++++++++++++++++++
Asbase/rm.c | 42++++++++++++++++++++++++++++++++++++++++++
Asbase/rmdir.1 | 25+++++++++++++++++++++++++
Asbase/rmdir.c | 29+++++++++++++++++++++++++++++
Asbase/runetypebody.h | 1886+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/seq.1 | 36++++++++++++++++++++++++++++++++++++
Asbase/seq.c | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/setsid.1 | 7+++++++
Asbase/setsid.c | 45+++++++++++++++++++++++++++++++++++++++++++++
Asbase/sha1.h | 18++++++++++++++++++
Asbase/sha1sum.1 | 12++++++++++++
Asbase/sha1sum.c | 43+++++++++++++++++++++++++++++++++++++++++++
Asbase/sha256.h | 18++++++++++++++++++
Asbase/sha256sum.1 | 12++++++++++++
Asbase/sha256sum.c | 43+++++++++++++++++++++++++++++++++++++++++++
Asbase/sha512.h | 18++++++++++++++++++
Asbase/sha512sum.1 | 12++++++++++++
Asbase/sha512sum.c | 43+++++++++++++++++++++++++++++++++++++++++++
Asbase/sleep.1 | 11+++++++++++
Asbase/sleep.c | 30++++++++++++++++++++++++++++++
Asbase/sort.1 | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/sort.c | 307+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/split.1 | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/split.c | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/sponge.1 | 15+++++++++++++++
Asbase/sponge.c | 41+++++++++++++++++++++++++++++++++++++++++
Asbase/strings.1 | 17+++++++++++++++++
Asbase/strings.c | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/sync.1 | 14++++++++++++++
Asbase/sync.c | 22++++++++++++++++++++++
Asbase/tail.1 | 20++++++++++++++++++++
Asbase/tail.c | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/tar.1 | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/tar.c | 332+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/tee.1 | 14++++++++++++++
Asbase/tee.c | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/test.1 | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/test.c | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/text.h | 11+++++++++++
Asbase/touch.1 | 32++++++++++++++++++++++++++++++++
Asbase/touch.c | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/tr.1 | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/tr.c | 289++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/true.1 | 16++++++++++++++++
Asbase/true.c | 6++++++
Asbase/tty.1 | 13+++++++++++++
Asbase/tty.c | 27+++++++++++++++++++++++++++
Asbase/uname.1 | 32++++++++++++++++++++++++++++++++
Asbase/uname.c | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/unexpand.1 | 25+++++++++++++++++++++++++
Asbase/unexpand.c | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/uniq.1 | 39+++++++++++++++++++++++++++++++++++++++
Asbase/uniq.c | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/unlink.1 | 16++++++++++++++++
Asbase/unlink.c | 23+++++++++++++++++++++++
Asbase/utf.h | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/util.h | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/uudecode.1 | 19+++++++++++++++++++
Asbase/uudecode.c | 179+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/uuencode.1 | 16++++++++++++++++
Asbase/uuencode.c | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/wc.1 | 25+++++++++++++++++++++++++
Asbase/wc.c | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/xargs.1 | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/xargs.c | 260+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asbase/yes.1 | 10++++++++++
Asbase/yes.c | 24++++++++++++++++++++++++
Asdhcp/LICENSE | 22++++++++++++++++++++++
Asdhcp/Makefile | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asdhcp/TODO | 41+++++++++++++++++++++++++++++++++++++++++
Asdhcp/arg.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asdhcp/config.mk | 13+++++++++++++
Asdhcp/debug.c | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asdhcp/sdhcp.1 | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asdhcp/sdhcp.c | 495+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asdhcp/util.h | 9+++++++++
Asdhcp/util/eprintf.c | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asdhcp/util/strlcpy.c | 32++++++++++++++++++++++++++++++++
Ased/Makefile | 9+++++++++
Ased/compile.c | 861+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ased/defs.h | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ased/extern.h | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ased/fgetln.c | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ased/main.c | 358+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ased/misc.c | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ased/process.c | 625+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ased/reallocarray.c | 38++++++++++++++++++++++++++++++++++++++
Ased/sed.1 | 566+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ased/strlcat.c | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Ased/strlcpy.c | 48++++++++++++++++++++++++++++++++++++++++++++++++
Ased/util.h | 12++++++++++++
Asinit/LICENSE | 21+++++++++++++++++++++
Asinit/Makefile | 39+++++++++++++++++++++++++++++++++++++++
Asinit/README | 26++++++++++++++++++++++++++
Asinit/config.def.h | 5+++++
Asinit/config.h | 5+++++
Asinit/config.mk | 12++++++++++++
Asinit/sinit.8 | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asinit/sinit.c | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asmdev/LICENSE | 22++++++++++++++++++++++
Asmdev/Makefile | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asmdev/README | 25+++++++++++++++++++++++++
Asmdev/TODO | 6++++++
Asmdev/arg.h | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asmdev/bin/simevent | 18++++++++++++++++++
Asmdev/config.def.h | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asmdev/config.h | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asmdev/config.mk | 11+++++++++++
Asmdev/mkpath.h | 2++
Asmdev/smdev.c | 441+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asmdev/util.h | 18++++++++++++++++++
Asmdev/util/agetcwd.c | 18++++++++++++++++++
Asmdev/util/apathmax.c | 25+++++++++++++++++++++++++
Asmdev/util/dev.c | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asmdev/util/eprintf.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asmdev/util/estrtol.c | 27+++++++++++++++++++++++++++
Asmdev/util/mkpath.c | 29+++++++++++++++++++++++++++++
Asmdev/util/recurse.c | 40++++++++++++++++++++++++++++++++++++++++
Asmdev/util/strlcpy.c | 47+++++++++++++++++++++++++++++++++++++++++++++++
Astd.mk | 12++++++++++++
Aubase/LICENSE | 33+++++++++++++++++++++++++++++++++
Aubase/Makefile | 210+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/README | 29+++++++++++++++++++++++++++++
Aubase/TODO | 35+++++++++++++++++++++++++++++++++++
Aubase/arg.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/chvt.1 | 8++++++++
Aubase/chvt.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/clear.1 | 3+++
Aubase/clear.c | 10++++++++++
Aubase/config.def.h | 11+++++++++++
Aubase/config.h | 11+++++++++++
Aubase/config.mk | 16++++++++++++++++
Aubase/ctrlaltdel.8 | 20++++++++++++++++++++
Aubase/ctrlaltdel.c | 44++++++++++++++++++++++++++++++++++++++++++++
Aubase/dd.1 | 45+++++++++++++++++++++++++++++++++++++++++++++
Aubase/dd.c | 297+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/df.1 | 23+++++++++++++++++++++++
Aubase/df.c | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/dmesg.1 | 22++++++++++++++++++++++
Aubase/dmesg.c | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/eject.1 | 12++++++++++++
Aubase/eject.c | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/fallocate.1 | 20++++++++++++++++++++
Aubase/fallocate.c | 46++++++++++++++++++++++++++++++++++++++++++++++
Aubase/free.1 | 21+++++++++++++++++++++
Aubase/free.c | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/freeramdisk.8 | 8++++++++
Aubase/freeramdisk.c | 39+++++++++++++++++++++++++++++++++++++++
Aubase/fsfreeze.8 | 24++++++++++++++++++++++++
Aubase/fsfreeze.c | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/getty.8 | 11+++++++++++
Aubase/getty.c | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/halt.8 | 16++++++++++++++++
Aubase/halt.c | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/hwclock.8 | 25+++++++++++++++++++++++++
Aubase/hwclock.c | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/id.1 | 21+++++++++++++++++++++
Aubase/id.c | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/insmod.8 | 10++++++++++
Aubase/insmod.c | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/killall5.8 | 18++++++++++++++++++
Aubase/killall5.c | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/last.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/lastlog.8 | 11+++++++++++
Aubase/lastlog.c | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/libutil/agetcwd.c | 18++++++++++++++++++
Aubase/libutil/agetline.c | 13+++++++++++++
Aubase/libutil/apathmax.c | 22++++++++++++++++++++++
Aubase/libutil/concat.c | 20++++++++++++++++++++
Aubase/libutil/ealloc.c | 47+++++++++++++++++++++++++++++++++++++++++++++++
Aubase/libutil/eprintf.c | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/libutil/estrtol.c | 27+++++++++++++++++++++++++++
Aubase/libutil/estrtoul.c | 26++++++++++++++++++++++++++
Aubase/libutil/explicit_bzero.c | 12++++++++++++
Aubase/libutil/passwd.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/libutil/proc.c | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/libutil/putword.c | 16++++++++++++++++
Aubase/libutil/recurse.c | 42++++++++++++++++++++++++++++++++++++++++++
Aubase/libutil/strlcat.c | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/libutil/strlcpy.c | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/libutil/tty.c | 39+++++++++++++++++++++++++++++++++++++++
Aubase/login.1 | 13+++++++++++++
Aubase/login.c | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/lsmod.8 | 6++++++
Aubase/lsmod.c | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/lsusb.8 | 6++++++
Aubase/lsusb.c | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/mesg.1 | 19+++++++++++++++++++
Aubase/mesg.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/mknod.1 | 23+++++++++++++++++++++++
Aubase/mknod.c | 45+++++++++++++++++++++++++++++++++++++++++++++
Aubase/mkswap.8 | 12++++++++++++
Aubase/mkswap.c | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/mount.8 | 33+++++++++++++++++++++++++++++++++
Aubase/mount.c | 239+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/mountpoint.1 | 22++++++++++++++++++++++
Aubase/mountpoint.c | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/pagesize.1 | 8++++++++
Aubase/pagesize.c | 32++++++++++++++++++++++++++++++++
Aubase/passwd.1 | 11+++++++++++
Aubase/passwd.c | 257+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/passwd.h | 4++++
Aubase/pidof.1 | 18++++++++++++++++++
Aubase/pidof.c | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/pivot_root.8 | 8++++++++
Aubase/pivot_root.c | 31+++++++++++++++++++++++++++++++
Aubase/proc.h | 42++++++++++++++++++++++++++++++++++++++++++
Aubase/ps.1 | 26++++++++++++++++++++++++++
Aubase/ps.c | 188+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/queue.h | 648+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/readahead.8 | 11+++++++++++
Aubase/readahead.c | 38++++++++++++++++++++++++++++++++++++++
Aubase/reboot.h | 32++++++++++++++++++++++++++++++++
Aubase/respawn.1 | 18++++++++++++++++++
Aubase/respawn.c | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/rmmod.8 | 23+++++++++++++++++++++++
Aubase/rmmod.c | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/rtc.h | 20++++++++++++++++++++
Aubase/stat.1 | 14++++++++++++++
Aubase/stat.c | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/su.1 | 20++++++++++++++++++++
Aubase/su.c | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/swaplabel.8 | 11+++++++++++
Aubase/swaplabel.c | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/swapoff.8 | 11+++++++++++
Aubase/swapoff.c | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/swapon.8 | 19+++++++++++++++++++
Aubase/swapon.c | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/switch_root.8 | 15+++++++++++++++
Aubase/switch_root.c | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/sysctl.8 | 13+++++++++++++
Aubase/sysctl.c | 213+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/text.h | 13+++++++++++++
Aubase/truncate.1 | 20++++++++++++++++++++
Aubase/truncate.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/umount.8 | 30++++++++++++++++++++++++++++++
Aubase/umount.c | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/unshare.1 | 43+++++++++++++++++++++++++++++++++++++++++++
Aubase/unshare.c | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/uptime.1 | 12++++++++++++
Aubase/uptime.c | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/util.h | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/vtallow.1 | 22++++++++++++++++++++++
Aubase/vtallow.c | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/watch.1 | 19+++++++++++++++++++
Aubase/watch.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aubase/who.1 | 21+++++++++++++++++++++
Aubase/who.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
401 files changed, 36226 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,16 @@ +TARG = awk sbase sdhcp sed sinit smdev ubase + +all: + @echo "CFLAGS = $(CFLAGS)" + @echo "LDFLAGS = $(LDFLAGS)" + @echo "CC = $(CC)" + for i in $(TARG); do cd $$i && $(MAKE) CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" || exit; cd ..; done + +install: all + for i in $(TARG); do cd $$i && $(MAKE) install || exit; cd ..; done + +uninstall: + for i in $(TARG); do cd $$i && $(MAKE) uninstall || exit; cd ..; done + +clean: + for i in $(TARG); do cd $$i && $(MAKE) clean || exit; cd ..; done diff --git a/awk/Makefile b/awk/Makefile @@ -0,0 +1,21 @@ +OBJ = arc4random.o b.o lex.o lib.o main.o parse.o proctab.o reallocarray.o run.o strlcat.o strlcpy.o tran.o ytab.o +TARG = awk +LDLIBS = -lm + +all: ytab.c proctab.c $(TARG) + +ytab.c ytab.h: awkgram.y + $(YACC) -d awkgram.y + mv y.tab.c ytab.c + mv y.tab.h ytab.h + +proctab.c: maketab + ./maketab > proctab.c + +maketab: ytab.h maketab.c + $(CC) maketab.c -o $@ + +include ../std.mk + +clean: + rm -f $(TARG) $(OBJ) maketab proctab.c ytab.[ch] diff --git a/awk/arc4random.c b/awk/arc4random.c @@ -0,0 +1,12 @@ +#include <sys/types.h> +#include <stdint.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +uint32_t +arc4random(void) +{ + srand(time(NULL) ^ getpid() ^ 0x42); + return rand(); +} diff --git a/awk/awk.1 b/awk/awk.1 @@ -0,0 +1,800 @@ +.\" $OpenBSD: awk.1,v 1.41 2014/03/17 21:48:51 jmc Exp $ +.\" +.\" Copyright (C) Lucent Technologies 1997 +.\" 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 the copyright notice and this +.\" permission notice and warranty disclaimer appear in supporting +.\" documentation, and that the name Lucent Technologies or any of +.\" its entities not be used in advertising or publicity pertaining +.\" to distribution of the software without specific, written prior +.\" permission. +.\" +.\" LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +.\" INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +.\" IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES 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. +.\" +.Dd $Mdocdate: March 17 2014 $ +.Dt AWK 1 +.Os +.Sh NAME +.Nm awk +.Nd pattern-directed scanning and processing language +.Sh SYNOPSIS +.Nm awk +.Op Fl safe +.Op Fl V +.Op Fl d Ns Op Ar n +.Op Fl F Ar fs +.Op Fl v Ar var Ns = Ns Ar value +.Op Ar prog | Fl f Ar progfile +.Ar +.Sh DESCRIPTION +.Nm +scans each input +.Ar file +for lines that match any of a set of patterns specified literally in +.Ar prog +or in one or more files specified as +.Fl f Ar progfile . +With each pattern there can be an associated action that will be performed +when a line of a +.Ar file +matches the pattern. +Each line is matched against the +pattern portion of every pattern-action statement; +the associated action is performed for each matched pattern. +The file name +.Sq - +means the standard input. +Any +.Ar file +of the form +.Ar var Ns = Ns Ar value +is treated as an assignment, not a filename, +and is executed at the time it would have been opened if it were a filename. +.Pp +The options are as follows: +.Bl -tag -width "-safe " +.It Fl d Ns Op Ar n +Debug mode. +Set debug level to +.Ar n , +or 1 if +.Ar n +is not specified. +A value greater than 1 causes +.Nm +to dump core on fatal errors. +.It Fl F Ar fs +Define the input field separator to be the regular expression +.Ar fs . +.It Fl f Ar progfile +Read program code from the specified file +.Ar progfile +instead of from the command line. +.It Fl safe +Disable file output +.Pf ( Ic print No > , +.Ic print No >> ) , +process creation +.Po +.Ar cmd | Ic getline , +.Ic print | , +.Ic system +.Pc +and access to the environment +.Pf ( Va ENVIRON ; +see the section on variables below). +This is a first +.Pq and not very reliable +approximation to a +.Dq safe +version of +.Nm . +.It Fl V +Print the version number of +.Nm +to standard output and exit. +.It Fl v Ar var Ns = Ns Ar value +Assign +.Ar value +to variable +.Ar var +before +.Ar prog +is executed; +any number of +.Fl v +options may be present. +.El +.Pp +The input is normally made up of input lines +.Pq records +separated by newlines, or by the value of +.Va RS . +If +.Va RS +is null, then any number of blank lines are used as the record separator, +and newlines are used as field separators +(in addition to the value of +.Va FS ) . +This is convenient when working with multi-line records. +.Pp +An input line is normally made up of fields separated by whitespace, +or by the regular expression +.Va FS . +The fields are denoted +.Va $1 , $2 , ... , +while +.Va $0 +refers to the entire line. +If +.Va FS +is null, the input line is split into one field per character. +.Pp +Normally, any number of blanks separate fields. +In order to set the field separator to a single blank, use the +.Fl F +option with a value of +.Sq [\ \&] . +If a field separator of +.Sq t +is specified, +.Nm +treats it as if +.Sq \et +had been specified and uses +.Aq TAB +as the field separator. +In order to use a literal +.Sq t +as the field separator, use the +.Fl F +option with a value of +.Sq [t] . +.Pp +A pattern-action statement has the form +.Pp +.D1 Ar pattern Ic \&{ Ar action Ic \&} +.Pp +A missing +.Ic \&{ Ar action Ic \&} +means print the line; +a missing pattern always matches. +Pattern-action statements are separated by newlines or semicolons. +.Pp +Newlines are permitted after a terminating statement or following a comma +.Pq Sq ,\& , +an open brace +.Pq Sq { , +a logical AND +.Pq Sq && , +a logical OR +.Pq Sq || , +after the +.Sq do +or +.Sq else +keywords, +or after the closing parenthesis of an +.Sq if , +.Sq for , +or +.Sq while +statement. +Additionally, a backslash +.Pq Sq \e +can be used to escape a newline between tokens. +.Pp +An action is a sequence of statements. +A statement can be one of the following: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It Xo Ic if ( Ar expression ) Ar statement +.Op Ic else Ar statement +.Xc +.It Ic while ( Ar expression ) Ar statement +.It Xo Ic for +.No ( Ar expression ; expression ; expression ) statement +.Xc +.It Xo Ic for +.No ( Ar var Ic in Ar array ) statement +.Xc +.It Xo Ic do +.Ar statement Ic while ( Ar expression ) +.Xc +.It Ic break +.It Ic continue +.It Xo Ic { +.Op Ar statement ... +.Ic } +.Xc +.It Xo Ar expression +.No # commonly +.Ar var No = Ar expression +.Xc +.It Xo Ic print +.Op Ar expression-list +.Op > Ns Ar expression +.Xc +.It Xo Ic printf Ar format +.Op Ar ... , expression-list +.Op > Ns Ar expression +.Xc +.It Ic return Op Ar expression +.It Xo Ic next +.No # skip remaining patterns on this input line +.Xc +.It Xo Ic nextfile +.No # skip rest of this file, open next, start at top +.Xc +.It Xo Ic delete +.Sm off +.Ar array Ic \&[ Ar expression Ic \&] +.Sm on +.No # delete an array element +.Xc +.It Xo Ic delete Ar array +.No # delete all elements of array +.Xc +.It Xo Ic exit +.Op Ar expression +.No # exit immediately; status is Ar expression +.Xc +.El +.Pp +Statements are terminated by +semicolons, newlines or right braces. +An empty +.Ar expression-list +stands for +.Ar $0 . +String constants are quoted +.Li \&"" , +with the usual C escapes recognized within +(see +.Xr printf 1 +for a complete list of these). +Expressions take on string or numeric values as appropriate, +and are built using the operators +.Ic + \- * / % ^ +.Pq exponentiation , +and concatenation +.Pq indicated by whitespace . +The operators +.Ic \&! ++ \-\- += \-= *= /= %= ^= +.Ic > >= < <= == != ?: +are also available in expressions. +Variables may be scalars, array elements +(denoted +.Li x[i] ) +or fields. +Variables are initialized to the null string. +Array subscripts may be any string, +not necessarily numeric; +this allows for a form of associative memory. +Multiple subscripts such as +.Li [i,j,k] +are permitted; the constituents are concatenated, +separated by the value of +.Va SUBSEP +.Pq see the section on variables below . +.Pp +The +.Ic print +statement prints its arguments on the standard output +(or on a file if +.Pf > Ns Ar file +or +.Pf >> Ns Ar file +is present or on a pipe if +.Pf |\ \& Ar cmd +is present), separated by the current output field separator, +and terminated by the output record separator. +.Ar file +and +.Ar cmd +may be literal names or parenthesized expressions; +identical string values in different statements denote +the same open file. +The +.Ic printf +statement formats its expression list according to the format +(see +.Xr printf 1 ) . +.Pp +Patterns are arbitrary Boolean combinations +(with +.Ic "\&! || &&" ) +of regular expressions and +relational expressions. +.Nm +supports extended regular expressions +.Pq EREs . +See +.Xr re_format 7 +for more information on regular expressions. +Isolated regular expressions +in a pattern apply to the entire line. +Regular expressions may also occur in +relational expressions, using the operators +.Ic ~ +and +.Ic !~ . +.Pf / Ns Ar re Ns / +is a constant regular expression; +any string (constant or variable) may be used +as a regular expression, except in the position of an isolated regular expression +in a pattern. +.Pp +A pattern may consist of two patterns separated by a comma; +in this case, the action is performed for all lines +from an occurrence of the first pattern +through an occurrence of the second. +.Pp +A relational expression is one of the following: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It Ar expression matchop regular-expression +.It Ar expression relop expression +.It Ar expression Ic in Ar array-name +.It Xo Ic \&( Ns +.Ar expr , expr , \&... Ns Ic \&) in +.Ar array-name +.Xc +.El +.Pp +where a +.Ar relop +is any of the six relational operators in C, and a +.Ar matchop +is either +.Ic ~ +(matches) +or +.Ic !~ +(does not match). +A conditional is an arithmetic expression, +a relational expression, +or a Boolean combination +of these. +.Pp +The special patterns +.Ic BEGIN +and +.Ic END +may be used to capture control before the first input line is read +and after the last. +.Ic BEGIN +and +.Ic END +do not combine with other patterns. +.Pp +Variable names with special meanings: +.Pp +.Bl -tag -width "FILENAME " -compact +.It Va ARGC +Argument count, assignable. +.It Va ARGV +Argument array, assignable; +non-null members are taken as filenames. +.It Va CONVFMT +Conversion format when converting numbers +(default +.Qq Li %.6g ) . +.It Va ENVIRON +Array of environment variables; subscripts are names. +.It Va FILENAME +The name of the current input file. +.It Va FNR +Ordinal number of the current record in the current file. +.It Va FS +Regular expression used to separate fields; also settable +by option +.Fl F Ar fs . +.It Va NF +Number of fields in the current record. +.Va $NF +can be used to obtain the value of the last field in the current record. +.It Va NR +Ordinal number of the current record. +.It Va OFMT +Output format for numbers (default +.Qq Li %.6g ) . +.It Va OFS +Output field separator (default blank). +.It Va ORS +Output record separator (default newline). +.It Va RLENGTH +The length of the string matched by the +.Fn match +function. +.It Va RS +Input record separator (default newline). +.It Va RSTART +The starting position of the string matched by the +.Fn match +function. +.It Va SUBSEP +Separates multiple subscripts (default 034). +.El +.Sh FUNCTIONS +The awk language has a variety of built-in functions: +arithmetic, string, input/output, general, and bit-operation. +.Pp +Functions may be defined (at the position of a pattern-action statement) +thusly: +.Pp +.Dl function foo(a, b, c) { ...; return x } +.Pp +Parameters are passed by value if scalar, and by reference if array name; +functions may be called recursively. +Parameters are local to the function; all other variables are global. +Thus local variables may be created by providing excess parameters in +the function definition. +.Ss Arithmetic Functions +.Bl -tag -width "atan2(y, x)" +.It Fn atan2 y x +Return the arctangent of +.Fa y Ns / Ns Fa x +in radians. +.It Fn cos x +Return the cosine of +.Fa x , +where +.Fa x +is in radians. +.It Fn exp x +Return the exponential of +.Fa x . +.It Fn int x +Return +.Fa x +truncated to an integer value. +.It Fn log x +Return the natural logarithm of +.Fa x . +.It Fn rand +Return a random number, +.Fa n , +such that +.Sm off +.Pf 0 \*(Le Fa n No \*(Lt 1 . +.Sm on +.It Fn sin x +Return the sine of +.Fa x , +where +.Fa x +is in radians. +.It Fn sqrt x +Return the square root of +.Fa x . +.It Fn srand expr +Sets seed for +.Fn rand +to +.Fa expr +and returns the previous seed. +If +.Fa expr +is omitted, the time of day is used instead. +.El +.Ss String Functions +.Bl -tag -width "split(s, a, fs)" +.It Fn gsub r t s +The same as +.Fn sub +except that all occurrences of the regular expression are replaced. +.Fn gsub +returns the number of replacements. +.It Fn index s t +The position in +.Fa s +where the string +.Fa t +occurs, or 0 if it does not. +.It Fn length s +The length of +.Fa s +taken as a string, +or of +.Va $0 +if no argument is given. +.It Fn match s r +The position in +.Fa s +where the regular expression +.Fa r +occurs, or 0 if it does not. +The variable +.Va RSTART +is set to the starting position of the matched string +.Pq which is the same as the returned value +or zero if no match is found. +The variable +.Va RLENGTH +is set to the length of the matched string, +or \-1 if no match is found. +.It Fn split s a fs +Splits the string +.Fa s +into array elements +.Va a[1] , a[2] , ... , a[n] +and returns +.Va n . +The separation is done with the regular expression +.Ar fs +or with the field separator +.Va FS +if +.Ar fs +is not given. +An empty string as field separator splits the string +into one array element per character. +.It Fn sprintf fmt expr ... +The string resulting from formatting +.Fa expr , ... +according to the +.Xr printf 1 +format +.Fa fmt . +.It Fn sub r t s +Substitutes +.Fa t +for the first occurrence of the regular expression +.Fa r +in the string +.Fa s . +If +.Fa s +is not given, +.Va $0 +is used. +An ampersand +.Pq Sq & +in +.Fa t +is replaced in string +.Fa s +with regular expression +.Fa r . +A literal ampersand can be specified by preceding it with two backslashes +.Pq Sq \e\e . +A literal backslash can be specified by preceding it with another backslash +.Pq Sq \e\e . +.Fn sub +returns the number of replacements. +.It Fn substr s m n +Return at most the +.Fa n Ns -character +substring of +.Fa s +that begins at position +.Fa m +counted from 1. +If +.Fa n +is omitted, or if +.Fa n +specifies more characters than are left in the string, +the length of the substring is limited by the length of +.Fa s . +.It Fn tolower str +Returns a copy of +.Fa str +with all upper-case characters translated to their +corresponding lower-case equivalents. +.It Fn toupper str +Returns a copy of +.Fa str +with all lower-case characters translated to their +corresponding upper-case equivalents. +.El +.Ss Input/Output and General Functions +.Bl -tag -width "getline [var] < file" +.It Fn close expr +Closes the file or pipe +.Fa expr . +.Fa expr +should match the string that was used to open the file or pipe. +.It Ar cmd | Ic getline Op Va var +Read a record of input from a stream piped from the output of +.Ar cmd . +If +.Va var +is omitted, the variables +.Va $0 +and +.Va NF +are set. +Otherwise +.Va var +is set. +If the stream is not open, it is opened. +As long as the stream remains open, subsequent calls +will read subsequent records from the stream. +The stream remains open until explicitly closed with a call to +.Fn close . +.Ic getline +returns 1 for a successful input, 0 for end of file, and \-1 for an error. +.It Fn fflush [expr] +Flushes any buffered output for the file or pipe +.Fa expr , +or all open files or pipes if +.Fa expr +is omitted. +.Fa expr +should match the string that was used to open the file or pipe. +.It Ic getline +Sets +.Va $0 +to the next input record from the current input file. +This form of +.Ic getline +sets the variables +.Va NF , +.Va NR , +and +.Va FNR . +.Ic getline +returns 1 for a successful input, 0 for end of file, and \-1 for an error. +.It Ic getline Va var +Sets +.Va $0 +to variable +.Va var . +This form of +.Ic getline +sets the variables +.Va NR +and +.Va FNR . +.Ic getline +returns 1 for a successful input, 0 for end of file, and \-1 for an error. +.It Xo +.Ic getline Op Va var +.Pf \ \&< Ar file +.Xc +Sets +.Va $0 +to the next record from +.Ar file . +If +.Va var +is omitted, the variables +.Va $0 +and +.Va NF +are set. +Otherwise +.Va var +is set. +If +.Ar file +is not open, it is opened. +As long as the stream remains open, subsequent calls will read subsequent +records from +.Ar file . +.Ar file +remains open until explicitly closed with a call to +.Fn close . +.It Fn system cmd +Executes +.Fa cmd +and returns its exit status. +.El +.Ss Bit-Operation Functions +.Bl -tag -width "lshift(a, b)" +.It Fn compl x +Returns the bitwise complement of integer argument x. +.It Fn and x y +Performs a bitwise AND on integer arguments x and y. +.It Fn or x y +Performs a bitwise OR on integer arguments x and y. +.It Fn xor x y +Performs a bitwise Exclusive-OR on integer arguments x and y. +.It Fn lshift x n +Returns integer argument x shifted by n bits to the left. +.It Fn rshift x n +Returns integer argument x shifted by n bits to the right. +.El +.Sh EXIT STATUS +.Ex -std awk +.Pp +But note that the +.Ic exit +expression can modify the exit status. +.Sh EXAMPLES +Print lines longer than 72 characters: +.Pp +.Dl length($0) > 72 +.Pp +Print first two fields in opposite order: +.Pp +.Dl { print $2, $1 } +.Pp +Same, with input fields separated by comma and/or blanks and tabs: +.Bd -literal -offset indent +BEGIN { FS = ",[ \et]*|[ \et]+" } + { print $2, $1 } +.Ed +.Pp +Add up first column, print sum and average: +.Bd -literal -offset indent +{ s += $1 } +END { print "sum is", s, " average is", s/NR } +.Ed +.Pp +Print all lines between start/stop pairs: +.Pp +.Dl /start/, /stop/ +.Pp +Simulate echo(1): +.Bd -literal -offset indent +BEGIN { # Simulate echo(1) + for (i = 1; i < ARGC; i++) printf "%s ", ARGV[i] + printf "\en" + exit } +.Ed +.Pp +Print an error message to standard error: +.Bd -literal -offset indent +{ print "error!" > "/dev/stderr" } +.Ed +.Sh SEE ALSO +.Xr lex 1 , +.Xr printf 1 , +.Xr sed 1 , +.Xr re_format 7 , +.Xr script 7 +.Rs +.%A A. V. Aho +.%A B. W. Kernighan +.%A P. J. Weinberger +.%T The AWK Programming Language +.%I Addison-Wesley +.%D 1988 +.%O ISBN 0-201-07981-X +.Re +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification, +except +.Nm +does not support {n,m} pattern matching. +.Pp +The flags +.Op Fl \&dV +and +.Op Fl safe , +as well as the commands +.Cm fflush , compl , and , or , +.Cm xor , lshift , rshift , +are extensions to that specification. +.Sh HISTORY +An +.Nm +utility appeared in +.At v7 . +.Sh BUGS +There are no explicit conversions between numbers and strings. +To force an expression to be treated as a number add 0 to it; +to force it to be treated as a string concatenate +.Li \&"" +to it. +.Pp +The scope rules for variables in functions are a botch; +the syntax is worse. diff --git a/awk/awk.h b/awk/awk.h @@ -0,0 +1,240 @@ +/* $OpenBSD: awk.h,v 1.13 2008/10/06 20:38:33 millert Exp $ */ +/**************************************************************** +Copyright (C) Lucent Technologies 1997 +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 the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name Lucent Technologies or any of +its entities not be used in advertising or publicity pertaining +to distribution of the software without specific, written prior +permission. + +LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES 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. +****************************************************************/ + +#include <assert.h> + +typedef double Awkfloat; + +/* unsigned char is more trouble than it's worth */ + +typedef unsigned char uschar; + +#define xfree(a) { if ((a) != NULL) { free((void *) (a)); (a) = NULL; } } + +#define NN(p) ((p) ? (p) : "(null)") /* guaranteed non-null for dprintf +*/ +#define DEBUG +#ifdef DEBUG + /* uses have to be doubly parenthesized */ +# define dprintf(x) if (dbg) printf x +#else +# define dprintf(x) +#endif + +extern int compile_time; /* 1 if compiling, 0 if running */ +extern int safe; /* 0 => unsafe, 1 => safe */ + +#define RECSIZE (8 * 1024) /* sets limit on records, fields, etc., etc. */ +extern int recsize; /* size of current record, orig RECSIZE */ + +extern char **FS; +extern char **RS; +extern char **ORS; +extern char **OFS; +extern char **OFMT; +extern Awkfloat *NR; +extern Awkfloat *FNR; +extern Awkfloat *NF; +extern char **FILENAME; +extern char **SUBSEP; +extern Awkfloat *RSTART; +extern Awkfloat *RLENGTH; + +extern char *record; /* points to $0 */ +extern int lineno; /* line number in awk program */ +extern int errorflag; /* 1 if error has occurred */ +extern int donefld; /* 1 if record broken into fields */ +extern int donerec; /* 1 if record is valid (no fld has changed */ +extern char inputFS[]; /* FS at time of input, for field splitting */ + +extern int dbg; + +extern char *patbeg; /* beginning of pattern matched */ +extern int patlen; /* length of pattern matched. set in b.c */ + +/* Cell: all information about a variable or constant */ + +typedef struct Cell { + uschar ctype; /* OCELL, OBOOL, OJUMP, etc. */ + uschar csub; /* CCON, CTEMP, CFLD, etc. */ + char *nval; /* name, for variables only */ + char *sval; /* string value */ + Awkfloat fval; /* value as number */ + int tval; /* type info: STR|NUM|ARR|FCN|FLD|CON|DONTFREE */ + struct Cell *cnext; /* ptr to next if chained */ +} Cell; + +typedef struct Array { /* symbol table array */ + int nelem; /* elements in table right now */ + int size; /* size of tab */ + Cell **tab; /* hash table pointers */ +} Array; + +#define NSYMTAB 50 /* initial size of a symbol table */ +extern Array *symtab; + +extern Cell *nrloc; /* NR */ +extern Cell *fnrloc; /* FNR */ +extern Cell *nfloc; /* NF */ +extern Cell *rstartloc; /* RSTART */ +extern Cell *rlengthloc; /* RLENGTH */ + +/* Cell.tval values: */ +#define NUM 01 /* number value is valid */ +#define STR 02 /* string value is valid */ +#define DONTFREE 04 /* string space is not freeable */ +#define CON 010 /* this is a constant */ +#define ARR 020 /* this is an array */ +#define FCN 040 /* this is a function name */ +#define FLD 0100 /* this is a field $1, $2, ... */ +#define REC 0200 /* this is $0 */ + + +/* function types */ +#define FLENGTH 1 +#define FSQRT 2 +#define FEXP 3 +#define FLOG 4 +#define FINT 5 +#define FSYSTEM 6 +#define FRAND 7 +#define FSRAND 8 +#define FSIN 9 +#define FCOS 10 +#define FATAN 11 +#define FTOUPPER 12 +#define FTOLOWER 13 +#define FFLUSH 14 +#define FAND 15 +#define FFOR 16 +#define FXOR 17 +#define FCOMPL 18 +#define FLSHIFT 19 +#define FRSHIFT 20 + +/* Node: parse tree is made of nodes, with Cell's at bottom */ + +typedef struct Node { + int ntype; + struct Node *nnext; + int lineno; + int nobj; + struct Node *narg[1]; /* variable: actual size set by calling malloc */ +} Node; + +#define NIL ((Node *) 0) + +extern Node *winner; +extern Node *nullstat; +extern Node *nullnode; + +/* ctypes */ +#define OCELL 1 +#define OBOOL 2 +#define OJUMP 3 + +/* Cell subtypes: csub */ +#define CFREE 7 +#define CCOPY 6 +#define CCON 5 +#define CTEMP 4 +#define CNAME 3 +#define CVAR 2 +#define CFLD 1 +#define CUNK 0 + +/* bool subtypes */ +#define BTRUE 11 +#define BFALSE 12 + +/* jump subtypes */ +#define JEXIT 21 +#define JNEXT 22 +#define JBREAK 23 +#define JCONT 24 +#define JRET 25 +#define JNEXTFILE 26 + +/* node types */ +#define NVALUE 1 +#define NSTAT 2 +#define NEXPR 3 + + +extern int pairstack[], paircnt; + +#define notlegal(n) (n <= FIRSTTOKEN || n >= LASTTOKEN || proctab[n-FIRSTTOKEN] == nullproc) +#define isvalue(n) ((n)->ntype == NVALUE) +#define isexpr(n) ((n)->ntype == NEXPR) +#define isjump(n) ((n)->ctype == OJUMP) +#define isexit(n) ((n)->csub == JEXIT) +#define isbreak(n) ((n)->csub == JBREAK) +#define iscont(n) ((n)->csub == JCONT) +#define isnext(n) ((n)->csub == JNEXT || (n)->csub == JNEXTFILE) +#define isret(n) ((n)->csub == JRET) +#define isrec(n) ((n)->tval & REC) +#define isfld(n) ((n)->tval & FLD) +#define isstr(n) ((n)->tval & STR) +#define isnum(n) ((n)->tval & NUM) +#define isarr(n) ((n)->tval & ARR) +#define isfcn(n) ((n)->tval & FCN) +#define istrue(n) ((n)->csub == BTRUE) +#define istemp(n) ((n)->csub == CTEMP) +#define isargument(n) ((n)->nobj == ARG) +/* #define freeable(p) (!((p)->tval & DONTFREE)) */ +#define freeable(p) ( ((p)->tval & (STR|DONTFREE)) == STR ) + +/* structures used by regular expression matching machinery, mostly b.c: */ + +#define NCHARS (256+3) /* 256 handles 8-bit chars; 128 does 7-bit */ + /* watch out in match(), etc. */ +#define NSTATES 32 + +typedef struct rrow { + long ltype; /* long avoids pointer warnings on 64-bit */ + union { + int i; + Node *np; + uschar *up; + } lval; /* because Al stores a pointer in it! */ + int *lfollow; +} rrow; + +typedef struct fa { + uschar gototab[NSTATES][NCHARS]; + uschar out[NSTATES]; + uschar *restr; + int *posns[NSTATES]; + int anchor; + int use; + int initstat; + int curstat; + int accept; + int reset; + struct rrow re[1]; /* variable: actual size set by calling malloc */ +} fa; + + +#include "proto.h" diff --git a/awk/awkgram.y b/awk/awkgram.y @@ -0,0 +1,487 @@ +/* $OpenBSD: awkgram.y,v 1.9 2011/09/28 19:27:18 millert Exp $ */ +/**************************************************************** +Copyright (C) Lucent Technologies 1997 +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 the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name Lucent Technologies or any of +its entities not be used in advertising or publicity pertaining +to distribution of the software without specific, written prior +permission. + +LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES 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. +****************************************************************/ + +%{ +#include <stdio.h> +#include <string.h> +#include "awk.h" + +void checkdup(Node *list, Cell *item); +int yywrap(void) { return(1); } + +Node *beginloc = 0; +Node *endloc = 0; +int infunc = 0; /* = 1 if in arglist or body of func */ +int inloop = 0; /* = 1 if in while, for, do */ +char *curfname = 0; /* current function name */ +Node *arglist = 0; /* list of args for current function */ +%} + +%union { + Node *p; + Cell *cp; + int i; + char *s; +} + +%token <i> FIRSTTOKEN /* must be first */ +%token <p> PROGRAM PASTAT PASTAT2 XBEGIN XEND +%token <i> NL ',' '{' '(' '|' ';' '/' ')' '}' '[' ']' +%token <i> ARRAY +%token <i> MATCH NOTMATCH MATCHOP +%token <i> FINAL DOT ALL CCL NCCL CHAR OR STAR QUEST PLUS EMPTYRE +%token <i> AND BOR APPEND EQ GE GT LE LT NE IN +%token <i> ARG BLTIN BREAK CLOSE CONTINUE DELETE DO EXIT FOR FUNC +%token <i> SUB GSUB IF INDEX LSUBSTR MATCHFCN NEXT NEXTFILE +%token <i> ADD MINUS MULT DIVIDE MOD +%token <i> ASSIGN ASGNOP ADDEQ SUBEQ MULTEQ DIVEQ MODEQ POWEQ +%token <i> PRINT PRINTF SPRINTF +%token <p> ELSE INTEST CONDEXPR +%token <i> POSTINCR PREINCR POSTDECR PREDECR +%token <cp> VAR IVAR VARNF CALL NUMBER STRING +%token <s> REGEXPR + +%type <p> pas pattern ppattern plist pplist patlist prarg term re +%type <p> pa_pat pa_stat pa_stats +%type <s> reg_expr +%type <p> simple_stmt opt_simple_stmt stmt stmtlist +%type <p> var varname funcname varlist +%type <p> for if else while +%type <i> do st +%type <i> pst opt_pst lbrace rbrace rparen comma nl opt_nl and bor +%type <i> subop print + +%right ASGNOP +%right '?' +%right ':' +%left BOR +%left AND +%left GETLINE +%nonassoc APPEND EQ GE GT LE LT NE MATCHOP IN '|' +%left ARG BLTIN BREAK CALL CLOSE CONTINUE DELETE DO EXIT FOR FUNC +%left GSUB IF INDEX LSUBSTR MATCHFCN NEXT NUMBER +%left PRINT PRINTF RETURN SPLIT SPRINTF STRING SUB SUBSTR +%left REGEXPR VAR VARNF IVAR WHILE '(' +%left CAT +%left '+' '-' +%left '*' '/' '%' +%left NOT UMINUS +%right POWER +%right DECR INCR +%left INDIRECT +%token LASTTOKEN /* must be last */ + +%% + +program: + pas { if (errorflag==0) + winner = (Node *)stat3(PROGRAM, beginloc, $1, endloc); } + | error { yyclearin; bracecheck(); SYNTAX("bailing out"); } + ; + +and: + AND | and NL + ; + +bor: + BOR | bor NL + ; + +comma: + ',' | comma NL + ; + +do: + DO | do NL + ; + +else: + ELSE | else NL + ; + +for: + FOR '(' opt_simple_stmt ';' opt_nl pattern ';' opt_nl opt_simple_stmt rparen {inloop++;} stmt + { --inloop; $$ = stat4(FOR, $3, notnull($6), $9, $12); } + | FOR '(' opt_simple_stmt ';' ';' opt_nl opt_simple_stmt rparen {inloop++;} stmt + { --inloop; $$ = stat4(FOR, $3, NIL, $7, $10); } + | FOR '(' varname IN varname rparen {inloop++;} stmt + { --inloop; $$ = stat3(IN, $3, makearr($5), $8); } + ; + +funcname: + VAR { setfname($1); } + | CALL { setfname($1); } + ; + +if: + IF '(' pattern rparen { $$ = notnull($3); } + ; + +lbrace: + '{' | lbrace NL + ; + +nl: + NL | nl NL + ; + +opt_nl: + /* empty */ { $$ = 0; } + | nl + ; + +opt_pst: + /* empty */ { $$ = 0; } + | pst + ; + + +opt_simple_stmt: + /* empty */ { $$ = 0; } + | simple_stmt + ; + +pas: + opt_pst { $$ = 0; } + | opt_pst pa_stats opt_pst { $$ = $2; } + ; + +pa_pat: + pattern { $$ = notnull($1); } + ; + +pa_stat: + pa_pat { $$ = stat2(PASTAT, $1, stat2(PRINT, rectonode(), NIL)); } + | pa_pat lbrace stmtlist '}' { $$ = stat2(PASTAT, $1, $3); } + | pa_pat ',' opt_nl pa_pat { $$ = pa2stat($1, $4, stat2(PRINT, rectonode(), NIL)); } + | pa_pat ',' opt_nl pa_pat lbrace stmtlist '}' { $$ = pa2stat($1, $4, $6); } + | lbrace stmtlist '}' { $$ = stat2(PASTAT, NIL, $2); } + | XBEGIN lbrace stmtlist '}' + { beginloc = linkum(beginloc, $3); $$ = 0; } + | XEND lbrace stmtlist '}' + { endloc = linkum(endloc, $3); $$ = 0; } + | FUNC funcname '(' varlist rparen {infunc++;} lbrace stmtlist '}' + { infunc--; curfname=0; defn((Cell *)$2, $4, $8); $$ = 0; } + ; + +pa_stats: + pa_stat + | pa_stats opt_pst pa_stat { $$ = linkum($1, $3); } + ; + +patlist: + pattern + | patlist comma pattern { $$ = linkum($1, $3); } + ; + +ppattern: + var ASGNOP ppattern { $$ = op2($2, $1, $3); } + | ppattern '?' ppattern ':' ppattern %prec '?' + { $$ = op3(CONDEXPR, notnull($1), $3, $5); } + | ppattern bor ppattern %prec BOR + { $$ = op2(BOR, notnull($1), notnull($3)); } + | ppattern and ppattern %prec AND + { $$ = op2(AND, notnull($1), notnull($3)); } + | ppattern MATCHOP reg_expr { $$ = op3($2, NIL, $1, (Node*)makedfa($3, 0)); } + | ppattern MATCHOP ppattern + { if (constnode($3)) + $$ = op3($2, NIL, $1, (Node*)makedfa(strnode($3), 0)); + else + $$ = op3($2, (Node *)1, $1, $3); } + | ppattern IN varname { $$ = op2(INTEST, $1, makearr($3)); } + | '(' plist ')' IN varname { $$ = op2(INTEST, $2, makearr($5)); } + | ppattern term %prec CAT { $$ = op2(CAT, $1, $2); } + | re + | term + ; + +pattern: + var ASGNOP pattern { $$ = op2($2, $1, $3); } + | pattern '?' pattern ':' pattern %prec '?' + { $$ = op3(CONDEXPR, notnull($1), $3, $5); } + | pattern bor pattern %prec BOR + { $$ = op2(BOR, notnull($1), notnull($3)); } + | pattern and pattern %prec AND + { $$ = op2(AND, notnull($1), notnull($3)); } + | pattern EQ pattern { $$ = op2($2, $1, $3); } + | pattern GE pattern { $$ = op2($2, $1, $3); } + | pattern GT pattern { $$ = op2($2, $1, $3); } + | pattern LE pattern { $$ = op2($2, $1, $3); } + | pattern LT pattern { $$ = op2($2, $1, $3); } + | pattern NE pattern { $$ = op2($2, $1, $3); } + | pattern MATCHOP reg_expr { $$ = op3($2, NIL, $1, (Node*)makedfa($3, 0)); } + | pattern MATCHOP pattern + { if (constnode($3)) + $$ = op3($2, NIL, $1, (Node*)makedfa(strnode($3), 0)); + else + $$ = op3($2, (Node *)1, $1, $3); } + | pattern IN varname { $$ = op2(INTEST, $1, makearr($3)); } + | '(' plist ')' IN varname { $$ = op2(INTEST, $2, makearr($5)); } + | pattern '|' GETLINE var { + if (safe) SYNTAX("cmd | getline is unsafe"); + else $$ = op3(GETLINE, $4, itonp($2), $1); } + | pattern '|' GETLINE { + if (safe) SYNTAX("cmd | getline is unsafe"); + else $$ = op3(GETLINE, (Node*)0, itonp($2), $1); } + | pattern term %prec CAT { $$ = op2(CAT, $1, $2); } + | re + | term + ; + +plist: + pattern comma pattern { $$ = linkum($1, $3); } + | plist comma pattern { $$ = linkum($1, $3); } + ; + +pplist: + ppattern + | pplist comma ppattern { $$ = linkum($1, $3); } + ; + +prarg: + /* empty */ { $$ = rectonode(); } + | pplist + | '(' plist ')' { $$ = $2; } + ; + +print: + PRINT | PRINTF + ; + +pst: + NL | ';' | pst NL | pst ';' + ; + +rbrace: + '}' | rbrace NL + ; + +re: + reg_expr + { $$ = op3(MATCH, NIL, rectonode(), (Node*)makedfa($1, 0)); } + | NOT re { $$ = op1(NOT, notnull($2)); } + ; + +reg_expr: + '/' {startreg();} REGEXPR '/' { $$ = $3; } + ; + +rparen: + ')' | rparen NL + ; + +simple_stmt: + print prarg '|' term { + if (safe) SYNTAX("print | is unsafe"); + else $$ = stat3($1, $2, itonp($3), $4); } + | print prarg APPEND term { + if (safe) SYNTAX("print >> is unsafe"); + else $$ = stat3($1, $2, itonp($3), $4); } + | print prarg GT term { + if (safe) SYNTAX("print > is unsafe"); + else $$ = stat3($1, $2, itonp($3), $4); } + | print prarg { $$ = stat3($1, $2, NIL, NIL); } + | DELETE varname '[' patlist ']' { $$ = stat2(DELETE, makearr($2), $4); } + | DELETE varname { $$ = stat2(DELETE, makearr($2), 0); } + | pattern { $$ = exptostat($1); } + | error { yyclearin; SYNTAX("illegal statement"); } + ; + +st: + nl + | ';' opt_nl + ; + +stmt: + BREAK st { if (!inloop) SYNTAX("break illegal outside of loops"); + $$ = stat1(BREAK, NIL); } + | CONTINUE st { if (!inloop) SYNTAX("continue illegal outside of loops"); + $$ = stat1(CONTINUE, NIL); } + | do {inloop++;} stmt {--inloop;} WHILE '(' pattern ')' st + { $$ = stat2(DO, $3, notnull($7)); } + | EXIT pattern st { $$ = stat1(EXIT, $2); } + | EXIT st { $$ = stat1(EXIT, NIL); } + | for + | if stmt else stmt { $$ = stat3(IF, $1, $2, $4); } + | if stmt { $$ = stat3(IF, $1, $2, NIL); } + | lbrace stmtlist rbrace { $$ = $2; } + | NEXT st { if (infunc) + SYNTAX("next is illegal inside a function"); + $$ = stat1(NEXT, NIL); } + | NEXTFILE st { if (infunc) + SYNTAX("nextfile is illegal inside a function"); + $$ = stat1(NEXTFILE, NIL); } + | RETURN pattern st { $$ = stat1(RETURN, $2); } + | RETURN st { $$ = stat1(RETURN, NIL); } + | simple_stmt st + | while {inloop++;} stmt { --inloop; $$ = stat2(WHILE, $1, $3); } + | ';' opt_nl { $$ = 0; } + ; + +stmtlist: + stmt + | stmtlist stmt { $$ = linkum($1, $2); } + ; + +subop: + SUB | GSUB + ; + +term: + term '/' ASGNOP term { $$ = op2(DIVEQ, $1, $4); } + | term '+' term { $$ = op2(ADD, $1, $3); } + | term '-' term { $$ = op2(MINUS, $1, $3); } + | term '*' term { $$ = op2(MULT, $1, $3); } + | term '/' term { $$ = op2(DIVIDE, $1, $3); } + | term '%' term { $$ = op2(MOD, $1, $3); } + | term POWER term { $$ = op2(POWER, $1, $3); } + | '-' term %prec UMINUS { $$ = op1(UMINUS, $2); } + | '+' term %prec UMINUS { $$ = $2; } + | NOT term %prec UMINUS { $$ = op1(NOT, notnull($2)); } + | BLTIN '(' ')' { $$ = op2(BLTIN, itonp($1), rectonode()); } + | BLTIN '(' patlist ')' { $$ = op2(BLTIN, itonp($1), $3); } + | BLTIN { $$ = op2(BLTIN, itonp($1), rectonode()); } + | CALL '(' ')' { $$ = op2(CALL, celltonode($1,CVAR), NIL); } + | CALL '(' patlist ')' { $$ = op2(CALL, celltonode($1,CVAR), $3); } + | CLOSE term { $$ = op1(CLOSE, $2); } + | DECR var { $$ = op1(PREDECR, $2); } + | INCR var { $$ = op1(PREINCR, $2); } + | var DECR { $$ = op1(POSTDECR, $1); } + | var INCR { $$ = op1(POSTINCR, $1); } + | GETLINE var LT term { $$ = op3(GETLINE, $2, itonp($3), $4); } + | GETLINE LT term { $$ = op3(GETLINE, NIL, itonp($2), $3); } + | GETLINE var { $$ = op3(GETLINE, $2, NIL, NIL); } + | GETLINE { $$ = op3(GETLINE, NIL, NIL, NIL); } + | INDEX '(' pattern comma pattern ')' + { $$ = op2(INDEX, $3, $5); } + | INDEX '(' pattern comma reg_expr ')' + { SYNTAX("index() doesn't permit regular expressions"); + $$ = op2(INDEX, $3, (Node*)$5); } + | '(' pattern ')' { $$ = $2; } + | MATCHFCN '(' pattern comma reg_expr ')' + { $$ = op3(MATCHFCN, NIL, $3, (Node*)makedfa($5, 1)); } + | MATCHFCN '(' pattern comma pattern ')' + { if (constnode($5)) + $$ = op3(MATCHFCN, NIL, $3, (Node*)makedfa(strnode($5), 1)); + else + $$ = op3(MATCHFCN, (Node *)1, $3, $5); } + | NUMBER { $$ = celltonode($1, CCON); } + | SPLIT '(' pattern comma varname comma pattern ')' /* string */ + { $$ = op4(SPLIT, $3, makearr($5), $7, (Node*)STRING); } + | SPLIT '(' pattern comma varname comma reg_expr ')' /* const /regexp/ */ + { $$ = op4(SPLIT, $3, makearr($5), (Node*)makedfa($7, 1), (Node *)REGEXPR); } + | SPLIT '(' pattern comma varname ')' + { $$ = op4(SPLIT, $3, makearr($5), NIL, (Node*)STRING); } /* default */ + | SPRINTF '(' patlist ')' { $$ = op1($1, $3); } + | STRING { $$ = celltonode($1, CCON); } + | subop '(' reg_expr comma pattern ')' + { $$ = op4($1, NIL, (Node*)makedfa($3, 1), $5, rectonode()); } + | subop '(' pattern comma pattern ')' + { if (constnode($3)) + $$ = op4($1, NIL, (Node*)makedfa(strnode($3), 1), $5, rectonode()); + else + $$ = op4($1, (Node *)1, $3, $5, rectonode()); } + | subop '(' reg_expr comma pattern comma var ')' + { $$ = op4($1, NIL, (Node*)makedfa($3, 1), $5, $7); } + | subop '(' pattern comma pattern comma var ')' + { if (constnode($3)) + $$ = op4($1, NIL, (Node*)makedfa(strnode($3), 1), $5, $7); + else + $$ = op4($1, (Node *)1, $3, $5, $7); } + | SUBSTR '(' pattern comma pattern comma pattern ')' + { $$ = op3(SUBSTR, $3, $5, $7); } + | SUBSTR '(' pattern comma pattern ')' + { $$ = op3(SUBSTR, $3, $5, NIL); } + | var + ; + +var: + varname + | varname '[' patlist ']' { $$ = op2(ARRAY, makearr($1), $3); } + | IVAR { $$ = op1(INDIRECT, celltonode($1, CVAR)); } + | INDIRECT term { $$ = op1(INDIRECT, $2); } + ; + +varlist: + /* nothing */ { arglist = $$ = 0; } + | VAR { arglist = $$ = celltonode($1,CVAR); } + | varlist comma VAR { + checkdup($1, $3); + arglist = $$ = linkum($1,celltonode($3,CVAR)); } + ; + +varname: + VAR { $$ = celltonode($1, CVAR); } + | ARG { $$ = op1(ARG, itonp($1)); } + | VARNF { $$ = op1(VARNF, (Node *) $1); } + ; + + +while: + WHILE '(' pattern rparen { $$ = notnull($3); } + ; + +%% + +void setfname(Cell *p) +{ + if (isarr(p)) + SYNTAX("%s is an array, not a function", p->nval); + else if (isfcn(p)) + SYNTAX("you can't define function %s more than once", p->nval); + curfname = p->nval; +} + +int constnode(Node *p) +{ + return isvalue(p) && ((Cell *) (p->narg[0]))->csub == CCON; +} + +char *strnode(Node *p) +{ + return ((Cell *)(p->narg[0]))->sval; +} + +Node *notnull(Node *n) +{ + switch (n->nobj) { + case LE: case LT: case EQ: case NE: case GT: case GE: + case BOR: case AND: case NOT: + return n; + default: + return op2(NE, n, nullnode); + } +} + +void checkdup(Node *vl, Cell *cp) /* check if name already in list */ +{ + char *s = cp->nval; + for ( ; vl; vl = vl->nnext) { + if (strcmp(s, ((Cell *)(vl->narg[0]))->nval) == 0) { + SYNTAX("duplicate argument %s", s); + break; + } + } +} diff --git a/awk/b.c b/awk/b.c @@ -0,0 +1,958 @@ +/* $OpenBSD: b.c,v 1.17 2011/09/28 19:27:18 millert Exp $ */ +/**************************************************************** +Copyright (C) Lucent Technologies 1997 +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 the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name Lucent Technologies or any of +its entities not be used in advertising or publicity pertaining +to distribution of the software without specific, written prior +permission. + +LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES 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. +****************************************************************/ + +/* lasciate ogne speranza, voi ch'intrate. */ + +#define DEBUG + +#include <ctype.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include "awk.h" +#include "ytab.h" + +#define HAT (NCHARS+2) /* matches ^ in regular expr */ + /* NCHARS is 2**n */ +#define MAXLIN 22 + +#define type(v) (v)->nobj /* badly overloaded here */ +#define info(v) (v)->ntype /* badly overloaded here */ +#define left(v) (v)->narg[0] +#define right(v) (v)->narg[1] +#define parent(v) (v)->nnext + +#define LEAF case CCL: case NCCL: case CHAR: case DOT: case FINAL: case ALL: +#define ELEAF case EMPTYRE: /* empty string in regexp */ +#define UNARY case STAR: case PLUS: case QUEST: + +/* encoding in tree Nodes: + leaf (CCL, NCCL, CHAR, DOT, FINAL, ALL, EMPTYRE): + left is index, right contains value or pointer to value + unary (STAR, PLUS, QUEST): left is child, right is null + binary (CAT, OR): left and right are children + parent contains pointer to parent +*/ + + +int *setvec; +int *tmpset; +int maxsetvec = 0; + +int rtok; /* next token in current re */ +int rlxval; +static uschar *rlxstr; +static uschar *prestr; /* current position in current re */ +static uschar *lastre; /* origin of last re */ + +static int setcnt; +static int poscnt; + +char *patbeg; +int patlen; + +#define NFA 20 /* cache this many dynamic fa's */ +fa *fatab[NFA]; +int nfatab = 0; /* entries in fatab */ + +fa *makedfa(const char *s, int anchor) /* returns dfa for reg expr s */ +{ + int i, use, nuse; + fa *pfa; + static int now = 1; + + if (setvec == 0) { /* first time through any RE */ + maxsetvec = MAXLIN; + setvec = (int *) calloc(maxsetvec, sizeof(int)); + tmpset = (int *) calloc(maxsetvec, sizeof(int)); + if (setvec == 0 || tmpset == 0) + overflo("out of space initializing makedfa"); + } + + if (compile_time) /* a constant for sure */ + return mkdfa(s, anchor); + for (i = 0; i < nfatab; i++) /* is it there already? */ + if (fatab[i]->anchor == anchor + && strcmp((const char *) fatab[i]->restr, s) == 0) { + fatab[i]->use = now++; + return fatab[i]; + } + pfa = mkdfa(s, anchor); + if (nfatab < NFA) { /* room for another */ + fatab[nfatab] = pfa; + fatab[nfatab]->use = now++; + nfatab++; + return pfa; + } + use = fatab[0]->use; /* replace least-recently used */ + nuse = 0; + for (i = 1; i < nfatab; i++) + if (fatab[i]->use < use) { + use = fatab[i]->use; + nuse = i; + } + freefa(fatab[nuse]); + fatab[nuse] = pfa; + pfa->use = now++; + return pfa; +} + +fa *mkdfa(const char *s, int anchor) /* does the real work of making a dfa */ + /* anchor = 1 for anchored matches, else 0 */ +{ + Node *p, *p1; + fa *f; + + p = reparse(s); + p1 = op2(CAT, op2(STAR, op2(ALL, NIL, NIL), NIL), p); + /* put ALL STAR in front of reg. exp. */ + p1 = op2(CAT, p1, op2(FINAL, NIL, NIL)); + /* put FINAL after reg. exp. */ + + poscnt = 0; + penter(p1); /* enter parent pointers and leaf indices */ + if ((f = (fa *) calloc(1, sizeof(fa) + poscnt*sizeof(rrow))) == NULL) + overflo("out of space for fa"); + f->accept = poscnt-1; /* penter has computed number of positions in re */ + cfoll(f, p1); /* set up follow sets */ + freetr(p1); + if ((f->posns[0] = (int *) calloc(*(f->re[0].lfollow), sizeof(int))) == NULL) + overflo("out of space in makedfa"); + if ((f->posns[1] = (int *) calloc(1, sizeof(int))) == NULL) + overflo("out of space in makedfa"); + *f->posns[1] = 0; + f->initstat = makeinit(f, anchor); + f->anchor = anchor; + f->restr = (uschar *) tostring(s); + return f; +} + +int makeinit(fa *f, int anchor) +{ + int i, k; + + f->curstat = 2; + f->out[2] = 0; + f->reset = 0; + k = *(f->re[0].lfollow); + xfree(f->posns[2]); + if ((f->posns[2] = (int *) calloc(k+1, sizeof(int))) == NULL) + overflo("out of space in makeinit"); + for (i=0; i <= k; i++) { + (f->posns[2])[i] = (f->re[0].lfollow)[i]; + } + if ((f->posns[2])[1] == f->accept) + f->out[2] = 1; + for (i=0; i < NCHARS; i++) + f->gototab[2][i] = 0; + f->curstat = cgoto(f, 2, HAT); + if (anchor) { + *f->posns[2] = k-1; /* leave out position 0 */ + for (i=0; i < k; i++) { + (f->posns[0])[i] = (f->posns[2])[i]; + } + + f->out[0] = f->out[2]; + if (f->curstat != 2) + --(*f->posns[f->curstat]); + } + return f->curstat; +} + +void penter(Node *p) /* set up parent pointers and leaf indices */ +{ + switch (type(p)) { + ELEAF + LEAF + info(p) = poscnt; + poscnt++; + break; + UNARY + penter(left(p)); + parent(left(p)) = p; + break; + case CAT: + case OR: + penter(left(p)); + penter(right(p)); + parent(left(p)) = p; + parent(right(p)) = p; + break; + default: /* can't happen */ + FATAL("can't happen: unknown type %d in penter", type(p)); + break; + } +} + +void freetr(Node *p) /* free parse tree */ +{ + switch (type(p)) { + ELEAF + LEAF + xfree(p); + break; + UNARY + freetr(left(p)); + xfree(p); + break; + case CAT: + case OR: + freetr(left(p)); + freetr(right(p)); + xfree(p); + break; + default: /* can't happen */ + FATAL("can't happen: unknown type %d in freetr", type(p)); + break; + } +} + +/* in the parsing of regular expressions, metacharacters like . have */ +/* to be seen literally; \056 is not a metacharacter. */ + +int hexstr(uschar **pp) /* find and eval hex string at pp, return new p */ +{ /* only pick up one 8-bit byte (2 chars) */ + uschar *p; + int n = 0; + int i; + + for (i = 0, p = (uschar *) *pp; i < 2 && isxdigit(*p); i++, p++) { + if (isdigit(*p)) + n = 16 * n + *p - '0'; + else if (*p >= 'a' && *p <= 'f') + n = 16 * n + *p - 'a' + 10; + else if (*p >= 'A' && *p <= 'F') + n = 16 * n + *p - 'A' + 10; + } + *pp = (uschar *) p; + return n; +} + +#define isoctdigit(c) ((c) >= '0' && (c) <= '7') /* multiple use of arg */ + +int quoted(uschar **pp) /* pick up next thing after a \\ */ + /* and increment *pp */ +{ + uschar *p = *pp; + int c; + + if ((c = *p++) == 't') + c = '\t'; + else if (c == 'n') + c = '\n'; + else if (c == 'f') + c = '\f'; + else if (c == 'r') + c = '\r'; + else if (c == 'b') + c = '\b'; + else if (c == '\\') + c = '\\'; + else if (c == 'x') { /* hexadecimal goo follows */ + c = hexstr(&p); /* this adds a null if number is invalid */ + } else if (isoctdigit(c)) { /* \d \dd \ddd */ + int n = c - '0'; + if (isoctdigit(*p)) { + n = 8 * n + *p++ - '0'; + if (isoctdigit(*p)) + n = 8 * n + *p++ - '0'; + } + c = n; + } /* else */ + /* c = c; */ + *pp = p; + return c; +} + +char *cclenter(const char *argp) /* add a character class */ +{ + int i, c, c2; + uschar *p = (uschar *) argp; + uschar *op, *bp; + static uschar *buf = 0; + static int bufsz = 100; + + op = p; + if (buf == 0 && (buf = (uschar *) malloc(bufsz)) == NULL) + FATAL("out of space for character class [%.10s...] 1", p); + bp = buf; + for (i = 0; (c = *p++) != 0; ) { + if (c == '\\') { + c = quoted(&p); + } else if (c == '-' && i > 0 && bp[-1] != 0) { + if (*p != 0) { + c = bp[-1]; + c2 = *p++; + if (c2 == '\\') + c2 = quoted(&p); + if (c > c2) { /* empty; ignore */ + bp--; + i--; + continue; + } + while (c < c2) { + if (!adjbuf((char **) &buf, &bufsz, bp-buf+2, 100, (char **) &bp, "cclenter1")) + FATAL("out of space for character class [%.10s...] 2", p); + *bp++ = ++c; + i++; + } + continue; + } + } + if (!adjbuf((char **) &buf, &bufsz, bp-buf+2, 100, (char **) &bp, "cclenter2")) + FATAL("out of space for character class [%.10s...] 3", p); + *bp++ = c; + i++; + } + *bp = 0; + dprintf( ("cclenter: in = |%s|, out = |%s|\n", op, buf) ); + xfree(op); + return (char *) tostring((char *) buf); +} + +void overflo(const char *s) +{ + FATAL("regular expression too big: %.30s...", s); +} + +void cfoll(fa *f, Node *v) /* enter follow set of each leaf of vertex v into lfollow[leaf] */ +{ + int i; + int *p; + + switch (type(v)) { + ELEAF + LEAF + f->re[info(v)].ltype = type(v); + f->re[info(v)].lval.np = right(v); + while (f->accept >= maxsetvec) { /* guessing here! */ + maxsetvec *= 4; + setvec = (int *) realloc(setvec, maxsetvec * sizeof(int)); + tmpset = (int *) realloc(tmpset, maxsetvec * sizeof(int)); + if (setvec == 0 || tmpset == 0) + overflo("out of space in cfoll()"); + } + for (i = 0; i <= f->accept; i++) + setvec[i] = 0; + setcnt = 0; + follow(v); /* computes setvec and setcnt */ + if ((p = (int *) calloc(setcnt+1, sizeof(int))) == NULL) + overflo("out of space building follow set"); + f->re[info(v)].lfollow = p; + *p = setcnt; + for (i = f->accept; i >= 0; i--) + if (setvec[i] == 1) + *++p = i; + break; + UNARY + cfoll(f,left(v)); + break; + case CAT: + case OR: + cfoll(f,left(v)); + cfoll(f,right(v)); + break; + default: /* can't happen */ + FATAL("can't happen: unknown type %d in cfoll", type(v)); + } +} + +int first(Node *p) /* collects initially active leaves of p into setvec */ + /* returns 0 if p matches empty string */ +{ + int b, lp; + + switch (type(p)) { + ELEAF + LEAF + lp = info(p); /* look for high-water mark of subscripts */ + while (setcnt >= maxsetvec || lp >= maxsetvec) { /* guessing here! */ + maxsetvec *= 4; + setvec = (int *) realloc(setvec, maxsetvec * sizeof(int)); + tmpset = (int *) realloc(tmpset, maxsetvec * sizeof(int)); + if (setvec == 0 || tmpset == 0) + overflo("out of space in first()"); + } + if (type(p) == EMPTYRE) { + setvec[lp] = 0; + return(0); + } + if (setvec[lp] != 1) { + setvec[lp] = 1; + setcnt++; + } + if (type(p) == CCL && (*(char *) right(p)) == '\0') + return(0); /* empty CCL */ + else return(1); + case PLUS: + if (first(left(p)) == 0) return(0); + return(1); + case STAR: + case QUEST: + first(left(p)); + return(0); + case CAT: + if (first(left(p)) == 0 && first(right(p)) == 0) return(0); + return(1); + case OR: + b = first(right(p)); + if (first(left(p)) == 0 || b == 0) return(0); + return(1); + } + FATAL("can't happen: unknown type %d in first", type(p)); /* can't happen */ + return(-1); +} + +void follow(Node *v) /* collects leaves that can follow v into setvec */ +{ + Node *p; + + if (type(v) == FINAL) + return; + p = parent(v); + switch (type(p)) { + case STAR: + case PLUS: + first(v); + follow(p); + return; + + case OR: + case QUEST: + follow(p); + return; + + case CAT: + if (v == left(p)) { /* v is left child of p */ + if (first(right(p)) == 0) { + follow(p); + return; + } + } else /* v is right child */ + follow(p); + return; + } +} + +int member(int c, const char *sarg) /* is c in s? */ +{ + uschar *s = (uschar *) sarg; + + while (*s) + if (c == *s++) + return(1); + return(0); +} + +int match(fa *f, const char *p0) /* shortest match ? */ +{ + int s, ns; + uschar *p = (uschar *) p0; + + s = f->reset ? makeinit(f,0) : f->initstat; + if (f->out[s]) + return(1); + do { + /* assert(*p < NCHARS); */ + if ((ns = f->gototab[s][*p]) != 0) + s = ns; + else + s = cgoto(f, s, *p); + if (f->out[s]) + return(1); + } while (*p++ != 0); + return(0); +} + +int pmatch(fa *f, const char *p0) /* longest match, for sub */ +{ + int s, ns; + uschar *p = (uschar *) p0; + uschar *q; + int i, k; + + /* s = f->reset ? makeinit(f,1) : f->initstat; */ + if (f->reset) { + f->initstat = s = makeinit(f,1); + } else { + s = f->initstat; + } + patbeg = (char *) p; + patlen = -1; + do { + q = p; + do { + if (f->out[s]) /* final state */ + patlen = q-p; + /* assert(*q < NCHARS); */ + if ((ns = f->gototab[s][*q]) != 0) + s = ns; + else + s = cgoto(f, s, *q); + if (s == 1) { /* no transition */ + if (patlen >= 0) { + patbeg = (char *) p; + return(1); + } + else + goto nextin; /* no match */ + } + } while (*q++ != 0); + if (f->out[s]) + patlen = q-p-1; /* don't count $ */ + if (patlen >= 0) { + patbeg = (char *) p; + return(1); + } + nextin: + s = 2; + if (f->reset) { + for (i = 2; i <= f->curstat; i++) + xfree(f->posns[i]); + k = *f->posns[0]; + if ((f->posns[2] = (int *) calloc(k+1, sizeof(int))) == NULL) + overflo("out of space in pmatch"); + for (i = 0; i <= k; i++) + (f->posns[2])[i] = (f->posns[0])[i]; + f->initstat = f->curstat = 2; + f->out[2] = f->out[0]; + for (i = 0; i < NCHARS; i++) + f->gototab[2][i] = 0; + } + } while (*p++ != 0); + return (0); +} + +int nematch(fa *f, const char *p0) /* non-empty match, for sub */ +{ + int s, ns; + uschar *p = (uschar *) p0; + uschar *q; + int i, k; + + /* s = f->reset ? makeinit(f,1) : f->initstat; */ + if (f->reset) { + f->initstat = s = makeinit(f,1); + } else { + s = f->initstat; + } + patlen = -1; + while (*p) { + q = p; + do { + if (f->out[s]) /* final state */ + patlen = q-p; + /* assert(*q < NCHARS); */ + if ((ns = f->gototab[s][*q]) != 0) + s = ns; + else + s = cgoto(f, s, *q); + if (s == 1) { /* no transition */ + if (patlen > 0) { + patbeg = (char *) p; + return(1); + } else + goto nnextin; /* no nonempty match */ + } + } while (*q++ != 0); + if (f->out[s]) + patlen = q-p-1; /* don't count $ */ + if (patlen > 0 ) { + patbeg = (char *) p; + return(1); + } + nnextin: + s = 2; + if (f->reset) { + for (i = 2; i <= f->curstat; i++) + xfree(f->posns[i]); + k = *f->posns[0]; + if ((f->posns[2] = (int *) calloc(k+1, sizeof(int))) == NULL) + overflo("out of state space"); + for (i = 0; i <= k; i++) + (f->posns[2])[i] = (f->posns[0])[i]; + f->initstat = f->curstat = 2; + f->out[2] = f->out[0]; + for (i = 0; i < NCHARS; i++) + f->gototab[2][i] = 0; + } + p++; + } + return (0); +} + +Node *reparse(const char *p) /* parses regular expression pointed to by p */ +{ /* uses relex() to scan regular expression */ + Node *np; + + dprintf( ("reparse <%s>\n", p) ); + lastre = prestr = (uschar *) p; /* prestr points to string to be parsed */ + rtok = relex(); + /* GNU compatibility: an empty regexp matches anything */ + if (rtok == '\0') { + /* FATAL("empty regular expression"); previous */ + return(op2(EMPTYRE, NIL, NIL)); + } + np = regexp(); + if (rtok != '\0') + FATAL("syntax error in regular expression %s at %s", lastre, prestr); + return(np); +} + +Node *regexp(void) /* top-level parse of reg expr */ +{ + return (alt(concat(primary()))); +} + +Node *primary(void) +{ + Node *np; + + switch (rtok) { + case CHAR: + np = op2(CHAR, NIL, itonp(rlxval)); + rtok = relex(); + return (unary(np)); + case ALL: + rtok = relex(); + return (unary(op2(ALL, NIL, NIL))); + case EMPTYRE: + rtok = relex(); + return (unary(op2(ALL, NIL, NIL))); + case DOT: + rtok = relex(); + return (unary(op2(DOT, NIL, NIL))); + case CCL: + np = op2(CCL, NIL, (Node*) cclenter((char *) rlxstr)); + rtok = relex(); + return (unary(np)); + case NCCL: + np = op2(NCCL, NIL, (Node *) cclenter((char *) rlxstr)); + rtok = relex(); + return (unary(np)); + case '^': + rtok = relex(); + return (unary(op2(CHAR, NIL, itonp(HAT)))); + case '$': + rtok = relex(); + return (unary(op2(CHAR, NIL, NIL))); + case '(': + rtok = relex(); + if (rtok == ')') { /* special pleading for () */ + rtok = relex(); + return unary(op2(CCL, NIL, (Node *) tostring(""))); + } + np = regexp(); + if (rtok == ')') { + rtok = relex(); + return (unary(np)); + } + else + FATAL("syntax error in regular expression %s at %s", lastre, prestr); + default: + FATAL("illegal primary in regular expression %s at %s", lastre, prestr); + } + return 0; /*NOTREACHED*/ +} + +Node *concat(Node *np) +{ + switch (rtok) { + case CHAR: case DOT: case ALL: case EMPTYRE: case CCL: case NCCL: case '$': case '(': + return (concat(op2(CAT, np, primary()))); + } + return (np); +} + +Node *alt(Node *np) +{ + if (rtok == OR) { + rtok = relex(); + return (alt(op2(OR, np, concat(primary())))); + } + return (np); +} + +Node *unary(Node *np) +{ + switch (rtok) { + case STAR: + rtok = relex(); + return (unary(op2(STAR, np, NIL))); + case PLUS: + rtok = relex(); + return (unary(op2(PLUS, np, NIL))); + case QUEST: + rtok = relex(); + return (unary(op2(QUEST, np, NIL))); + default: + return (np); + } +} + +/* + * Character class definitions conformant to the POSIX locale as + * defined in IEEE P1003.1 draft 7 of June 2001, assuming the source + * and operating character sets are both ASCII (ISO646) or supersets + * thereof. + * + * Note that to avoid overflowing the temporary buffer used in + * relex(), the expanded character class (prior to range expansion) + * must be less than twice the size of their full name. + */ + +/* Because isblank doesn't show up in any of the header files on any + * system i use, it's defined here. if some other locale has a richer + * definition of "blank", define HAS_ISBLANK and provide your own + * version. + * the parentheses here are an attempt to find a path through the maze + * of macro definition and/or function and/or version provided. thanks + * to nelson beebe for the suggestion; let's see if it works everywhere. + */ + +#ifndef HAS_ISBLANK + +int (xisblank)(int c) +{ + return c==' ' || c=='\t'; +} + +#endif + +struct charclass { + const char *cc_name; + int cc_namelen; + int (*cc_func)(int); +} charclasses[] = { + { "alnum", 5, isalnum }, + { "alpha", 5, isalpha }, +#ifndef HAS_ISBLANK + { "blank", 5, isspace }, /* was isblank */ +#else + { "blank", 5, isblank }, +#endif + { "cntrl", 5, iscntrl }, + { "digit", 5, isdigit }, + { "graph", 5, isgraph }, + { "lower", 5, islower }, + { "print", 5, isprint }, + { "punct", 5, ispunct }, + { "space", 5, isspace }, + { "upper", 5, isupper }, + { "xdigit", 6, isxdigit }, + { NULL, 0, NULL }, +}; + + +int relex(void) /* lexical analyzer for reparse */ +{ + int c, n; + int cflag; + static uschar *buf = 0; + static int bufsz = 100; + uschar *bp; + struct charclass *cc; + int i; + + switch (c = *prestr++) { + case '|': return OR; + case '*': return STAR; + case '+': return PLUS; + case '?': return QUEST; + case '.': return DOT; + case '\0': prestr--; return '\0'; + case '^': + case '$': + case '(': + case ')': + return c; + case '\\': + rlxval = quoted(&prestr); + return CHAR; + default: + rlxval = c; + return CHAR; + case '[': + if (buf == 0 && (buf = (uschar *) malloc(bufsz)) == NULL) + FATAL("out of space in reg expr %.10s..", lastre); + bp = buf; + if (*prestr == '^') { + cflag = 1; + prestr++; + } + else + cflag = 0; + n = 2 * strlen((const char *) prestr)+1; + if (!adjbuf((char **) &buf, &bufsz, n, n, (char **) &bp, "relex1")) + FATAL("out of space for reg expr %.10s...", lastre); + for (; ; ) { + if ((c = *prestr++) == '\\') { + *bp++ = '\\'; + if ((c = *prestr++) == '\0') + FATAL("nonterminated character class %.20s...", lastre); + *bp++ = c; + /* } else if (c == '\n') { */ + /* FATAL("newline in character class %.20s...", lastre); */ + } else if (c == '[' && *prestr == ':') { + /* POSIX char class names, Dag-Erling Smorgrav, des@ofug.org */ + for (cc = charclasses; cc->cc_name; cc++) + if (strncmp((const char *) prestr + 1, (const char *) cc->cc_name, cc->cc_namelen) == 0) + break; + if (cc->cc_name != NULL && prestr[1 + cc->cc_namelen] == ':' && + prestr[2 + cc->cc_namelen] == ']') { + prestr += cc->cc_namelen + 3; + for (i = 0; i < NCHARS; i++) { + if (!adjbuf((char **) &buf, &bufsz, bp-buf+1, 100, (char **) &bp, "relex2")) + FATAL("out of space for reg expr %.10s...", lastre); + if (cc->cc_func(i)) { + *bp++ = i; + n++; + } + } + } else + *bp++ = c; + } else if (c == '\0') { + FATAL("nonterminated character class %.20s", lastre); + } else if (bp == buf) { /* 1st char is special */ + *bp++ = c; + } else if (c == ']') { + *bp++ = 0; + rlxstr = (uschar *) tostring((char *) buf); + if (cflag == 0) + return CCL; + else + return NCCL; + } else + *bp++ = c; + } + } +} + +int cgoto(fa *f, int s, int c) +{ + int i, j, k; + int *p, *q; + + assert(c == HAT || c < NCHARS); + while (f->accept >= maxsetvec) { /* guessing here! */ + maxsetvec *= 4; + setvec = (int *) realloc(setvec, maxsetvec * sizeof(int)); + tmpset = (int *) realloc(tmpset, maxsetvec * sizeof(int)); + if (setvec == 0 || tmpset == 0) + overflo("out of space in cgoto()"); + } + for (i = 0; i <= f->accept; i++) + setvec[i] = 0; + setcnt = 0; + /* compute positions of gototab[s,c] into setvec */ + p = f->posns[s]; + for (i = 1; i <= *p; i++) { + if ((k = f->re[p[i]].ltype) != FINAL) { + if ((k == CHAR && c == ptoi(f->re[p[i]].lval.np)) + || (k == DOT && c != 0 && c != HAT) + || (k == ALL && c != 0) + || (k == EMPTYRE && c != 0) + || (k == CCL && member(c, (char *) f->re[p[i]].lval.up)) + || (k == NCCL && !member(c, (char *) f->re[p[i]].lval.up) && c != 0 && c != HAT)) { + q = f->re[p[i]].lfollow; + for (j = 1; j <= *q; j++) { + if (q[j] >= maxsetvec) { + maxsetvec *= 4; + setvec = (int *) realloc(setvec, maxsetvec * sizeof(int)); + tmpset = (int *) realloc(tmpset, maxsetvec * sizeof(int)); + if (setvec == 0 || tmpset == 0) + overflo("cgoto overflow"); + } + if (setvec[q[j]] == 0) { + setcnt++; + setvec[q[j]] = 1; + } + } + } + } + } + /* determine if setvec is a previous state */ + tmpset[0] = setcnt; + j = 1; + for (i = f->accept; i >= 0; i--) + if (setvec[i]) { + tmpset[j++] = i; + } + /* tmpset == previous state? */ + for (i = 1; i <= f->curstat; i++) { + p = f->posns[i]; + if ((k = tmpset[0]) != p[0]) + goto different; + for (j = 1; j <= k; j++) + if (tmpset[j] != p[j]) + goto different; + /* setvec is state i */ + f->gototab[s][c] = i; + return i; + different:; + } + + /* add tmpset to current set of states */ + if (f->curstat >= NSTATES-1) { + f->curstat = 2; + f->reset = 1; + for (i = 2; i < NSTATES; i++) + xfree(f->posns[i]); + } else + ++(f->curstat); + for (i = 0; i < NCHARS; i++) + f->gototab[f->curstat][i] = 0; + xfree(f->posns[f->curstat]); + if ((p = (int *) calloc(setcnt+1, sizeof(int))) == NULL) + overflo("out of space in cgoto"); + + f->posns[f->curstat] = p; + f->gototab[s][c] = f->curstat; + for (i = 0; i <= setcnt; i++) + p[i] = tmpset[i]; + if (setvec[f->accept]) + f->out[f->curstat] = 1; + else + f->out[f->curstat] = 0; + return f->curstat; +} + + +void freefa(fa *f) /* free a finite automaton */ +{ + int i; + + if (f == NULL) + return; + for (i = 0; i <= f->curstat; i++) + xfree(f->posns[i]); + for (i = 0; i <= f->accept; i++) { + xfree(f->re[i].lfollow); + if (f->re[i].ltype == CCL || f->re[i].ltype == NCCL) + xfree((f->re[i].lval.np)); + } + xfree(f->restr); + xfree(f); +} diff --git a/awk/lex.c b/awk/lex.c @@ -0,0 +1,597 @@ +/* $OpenBSD: lex.c,v 1.12 2011/09/28 19:27:18 millert Exp $ */ +/**************************************************************** +Copyright (C) Lucent Technologies 1997 +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 the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name Lucent Technologies or any of +its entities not be used in advertising or publicity pertaining +to distribution of the software without specific, written prior +permission. + +LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES 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. +****************************************************************/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "awk.h" +#include "ytab.h" + +extern YYSTYPE yylval; +extern int infunc; + +int lineno = 1; +int bracecnt = 0; +int brackcnt = 0; +int parencnt = 0; + +typedef struct Keyword { + const char *word; + int sub; + int type; +} Keyword; + +Keyword keywords[] ={ /* keep sorted: binary searched */ + { "BEGIN", XBEGIN, XBEGIN }, + { "END", XEND, XEND }, + { "NF", VARNF, VARNF }, + { "and", FAND, BLTIN }, + { "atan2", FATAN, BLTIN }, + { "break", BREAK, BREAK }, + { "close", CLOSE, CLOSE }, + { "compl", FCOMPL, BLTIN }, + { "continue", CONTINUE, CONTINUE }, + { "cos", FCOS, BLTIN }, + { "delete", DELETE, DELETE }, + { "do", DO, DO }, + { "else", ELSE, ELSE }, + { "exit", EXIT, EXIT }, + { "exp", FEXP, BLTIN }, + { "fflush", FFLUSH, BLTIN }, + { "for", FOR, FOR }, + { "func", FUNC, FUNC }, + { "function", FUNC, FUNC }, + { "getline", GETLINE, GETLINE }, + { "gsub", GSUB, GSUB }, + { "if", IF, IF }, + { "in", IN, IN }, + { "index", INDEX, INDEX }, + { "int", FINT, BLTIN }, + { "length", FLENGTH, BLTIN }, + { "log", FLOG, BLTIN }, + { "lshift", FLSHIFT, BLTIN }, + { "match", MATCHFCN, MATCHFCN }, + { "next", NEXT, NEXT }, + { "nextfile", NEXTFILE, NEXTFILE }, + { "or", FFOR, BLTIN }, + { "print", PRINT, PRINT }, + { "printf", PRINTF, PRINTF }, + { "rand", FRAND, BLTIN }, + { "return", RETURN, RETURN }, + { "rshift", FRSHIFT, BLTIN }, + { "sin", FSIN, BLTIN }, + { "split", SPLIT, SPLIT }, + { "sprintf", SPRINTF, SPRINTF }, + { "sqrt", FSQRT, BLTIN }, + { "srand", FSRAND, BLTIN }, + { "sub", SUB, SUB }, + { "substr", SUBSTR, SUBSTR }, + { "system", FSYSTEM, BLTIN }, + { "tolower", FTOLOWER, BLTIN }, + { "toupper", FTOUPPER, BLTIN }, + { "while", WHILE, WHILE }, + { "xor", FXOR, BLTIN }, +}; + +#define RET(x) { if(dbg)printf("lex %s\n", tokname(x)); return(x); } + +int peek(void); +int gettok(char **, int *); +int binsearch(char *, Keyword *, int); + +int peek(void) +{ + int c = input(); + unput(c); + return c; +} + +int gettok(char **pbuf, int *psz) /* get next input token */ +{ + int c, retc; + char *buf = *pbuf; + int sz = *psz; + char *bp = buf; + + c = input(); + if (c == 0) + return 0; + buf[0] = c; + buf[1] = 0; + if (!isalnum(c) && c != '.' && c != '_') + return c; + + *bp++ = c; + if (isalpha(c) || c == '_') { /* it's a varname */ + for ( ; (c = input()) != 0; ) { + if (bp-buf >= sz) + if (!adjbuf(&buf, &sz, bp-buf+2, 100, &bp, "gettok")) + FATAL( "out of space for name %.10s...", buf ); + if (isalnum(c) || c == '_') + *bp++ = c; + else { + *bp = 0; + unput(c); + break; + } + } + *bp = 0; + retc = 'a'; /* alphanumeric */ + } else { /* maybe it's a number, but could be . */ + char *rem; + /* read input until can't be a number */ + for ( ; (c = input()) != 0; ) { + if (bp-buf >= sz) + if (!adjbuf(&buf, &sz, bp-buf+2, 100, &bp, "gettok")) + FATAL( "out of space for number %.10s...", buf ); + if (isdigit(c) || c == 'e' || c == 'E' + || c == '.' || c == '+' || c == '-') + *bp++ = c; + else { + unput(c); + break; + } + } + *bp = 0; + strtod(buf, &rem); /* parse the number */ + if (rem == buf) { /* it wasn't a valid number at all */ + buf[1] = 0; /* return one character as token */ + retc = buf[0]; /* character is its own type */ + unputstr(rem+1); /* put rest back for later */ + } else { /* some prefix was a number */ + unputstr(rem); /* put rest back for later */ + rem[0] = 0; /* truncate buf after number part */ + retc = '0'; /* type is number */ + } + } + *pbuf = buf; + *psz = sz; + return retc; +} + +int word(char *); +int string(void); +int regexpr(void); +int sc = 0; /* 1 => return a } right now */ +int reg = 0; /* 1 => return a REGEXPR now */ + +int yylex(void) +{ + int c; + static char *buf = 0; + static int bufsize = 5; /* BUG: setting this small causes core dump! */ + + if (buf == 0 && (buf = (char *) malloc(bufsize)) == NULL) + FATAL( "out of space in yylex" ); + if (sc) { + sc = 0; + RET('}'); + } + if (reg) { + reg = 0; + return regexpr(); + } + for (;;) { + c = gettok(&buf, &bufsize); + if (c == 0) + return 0; + if (isalpha(c) || c == '_') + return word(buf); + if (isdigit(c)) { + yylval.cp = setsymtab(buf, tostring(buf), atof(buf), CON|NUM, symtab); + /* should this also have STR set? */ + RET(NUMBER); + } + + yylval.i = c; + switch (c) { + case '\n': /* {EOL} */ + RET(NL); + case '\r': /* assume \n is coming */ + case ' ': /* {WS}+ */ + case '\t': + break; + case '#': /* #.* strip comments */ + while ((c = input()) != '\n' && c != 0) + ; + unput(c); + break; + case ';': + RET(';'); + case '\\': + if (peek() == '\n') { + input(); + } else if (peek() == '\r') { + input(); input(); /* \n */ + lineno++; + } else { + RET(c); + } + break; + case '&': + if (peek() == '&') { + input(); RET(AND); + } else + RET('&'); + case '|': + if (peek() == '|') { + input(); RET(BOR); + } else + RET('|'); + case '!': + if (peek() == '=') { + input(); yylval.i = NE; RET(NE); + } else if (peek() == '~') { + input(); yylval.i = NOTMATCH; RET(MATCHOP); + } else + RET(NOT); + case '~': + yylval.i = MATCH; + RET(MATCHOP); + case '<': + if (peek() == '=') { + input(); yylval.i = LE; RET(LE); + } else { + yylval.i = LT; RET(LT); + } + case '=': + if (peek() == '=') { + input(); yylval.i = EQ; RET(EQ); + } else { + yylval.i = ASSIGN; RET(ASGNOP); + } + case '>': + if (peek() == '=') { + input(); yylval.i = GE; RET(GE); + } else if (peek() == '>') { + input(); yylval.i = APPEND; RET(APPEND); + } else { + yylval.i = GT; RET(GT); + } + case '+': + if (peek() == '+') { + input(); yylval.i = INCR; RET(INCR); + } else if (peek() == '=') { + input(); yylval.i = ADDEQ; RET(ASGNOP); + } else + RET('+'); + case '-': + if (peek() == '-') { + input(); yylval.i = DECR; RET(DECR); + } else if (peek() == '=') { + input(); yylval.i = SUBEQ; RET(ASGNOP); + } else + RET('-'); + case '*': + if (peek() == '=') { /* *= */ + input(); yylval.i = MULTEQ; RET(ASGNOP); + } else if (peek() == '*') { /* ** or **= */ + input(); /* eat 2nd * */ + if (peek() == '=') { + input(); yylval.i = POWEQ; RET(ASGNOP); + } else { + RET(POWER); + } + } else + RET('*'); + case '/': + RET('/'); + case '%': + if (peek() == '=') { + input(); yylval.i = MODEQ; RET(ASGNOP); + } else + RET('%'); + case '^': + if (peek() == '=') { + input(); yylval.i = POWEQ; RET(ASGNOP); + } else + RET(POWER); + + case '$': + /* BUG: awkward, if not wrong */ + c = gettok(&buf, &bufsize); + if (isalpha(c)) { + if (strcmp(buf, "NF") == 0) { /* very special */ + unputstr("(NF)"); + RET(INDIRECT); + } + c = peek(); + if (c == '(' || c == '[' || (infunc && isarg(buf) >= 0)) { + unputstr(buf); + RET(INDIRECT); + } + yylval.cp = setsymtab(buf, "", 0.0, STR|NUM, symtab); + RET(IVAR); + } else if (c == 0) { /* */ + SYNTAX( "unexpected end of input after $" ); + RET(';'); + } else { + unputstr(buf); + RET(INDIRECT); + } + + case '}': + if (--bracecnt < 0) + SYNTAX( "extra }" ); + sc = 1; + RET(';'); + case ']': + if (--brackcnt < 0) + SYNTAX( "extra ]" ); + RET(']'); + case ')': + if (--parencnt < 0) + SYNTAX( "extra )" ); + RET(')'); + case '{': + bracecnt++; + RET('{'); + case '[': + brackcnt++; + RET('['); + case '(': + parencnt++; + RET('('); + + case '"': + return string(); /* BUG: should be like tran.c ? */ + + default: + RET(c); + } + } +} + +int string(void) +{ + int c, n; + char *s, *bp; + static char *buf = 0; + static int bufsz = 500; + + if (buf == 0 && (buf = (char *) malloc(bufsz)) == NULL) + FATAL("out of space for strings"); + for (bp = buf; (c = input()) != '"'; ) { + if (!adjbuf(&buf, &bufsz, bp-buf+2, 500, &bp, "string")) + FATAL("out of space for string %.10s...", buf); + switch (c) { + case '\n': + case '\r': + case 0: + SYNTAX( "non-terminated string %.10s...", buf ); + lineno++; + if (c == 0) /* hopeless */ + FATAL( "giving up" ); + break; + case '\\': + c = input(); + switch (c) { + case '"': *bp++ = '"'; break; + case 'n': *bp++ = '\n'; break; + case 't': *bp++ = '\t'; break; + case 'f': *bp++ = '\f'; break; + case 'r': *bp++ = '\r'; break; + case 'b': *bp++ = '\b'; break; + case 'v': *bp++ = '\v'; break; + case 'a': *bp++ = '\007'; break; + case '\\': *bp++ = '\\'; break; + + case '0': case '1': case '2': /* octal: \d \dd \ddd */ + case '3': case '4': case '5': case '6': case '7': + n = c - '0'; + if ((c = peek()) >= '0' && c < '8') { + n = 8 * n + input() - '0'; + if ((c = peek()) >= '0' && c < '8') + n = 8 * n + input() - '0'; + } + *bp++ = n; + break; + + case 'x': /* hex \x0-9a-fA-F + */ + { char xbuf[100], *px; + for (px = xbuf; (c = input()) != 0 && px-xbuf < 100-2; ) { + if (isdigit(c) + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F')) + *px++ = c; + else + break; + } + *px = 0; + unput(c); + sscanf(xbuf, "%x", (unsigned int *) &n); + *bp++ = n; + break; + } + + default: + *bp++ = c; + break; + } + break; + default: + *bp++ = c; + break; + } + } + *bp = 0; + s = tostring(buf); + *bp++ = ' '; *bp++ = 0; + yylval.cp = setsymtab(buf, s, 0.0, CON|STR|DONTFREE, symtab); + RET(STRING); +} + + +int binsearch(char *w, Keyword *kp, int n) +{ + int cond, low, mid, high; + + low = 0; + high = n - 1; + while (low <= high) { + mid = (low + high) / 2; + if ((cond = strcmp(w, kp[mid].word)) < 0) + high = mid - 1; + else if (cond > 0) + low = mid + 1; + else + return mid; + } + return -1; +} + +int word(char *w) +{ + Keyword *kp; + int c, n; + + n = binsearch(w, keywords, sizeof(keywords)/sizeof(keywords[0])); +/* BUG: this ought to be inside the if; in theory could fault (daniel barrett) */ + kp = keywords + n; + if (n != -1) { /* found in table */ + yylval.i = kp->sub; + switch (kp->type) { /* special handling */ + case BLTIN: + if (kp->sub == FSYSTEM && safe) + SYNTAX( "system is unsafe" ); + RET(kp->type); + case FUNC: + if (infunc) + SYNTAX( "illegal nested function" ); + RET(kp->type); + case RETURN: + if (!infunc) + SYNTAX( "return not in function" ); + RET(kp->type); + case VARNF: + yylval.cp = setsymtab("NF", "", 0.0, NUM, symtab); + RET(VARNF); + default: + RET(kp->type); + } + } + c = peek(); /* look for '(' */ + if (c != '(' && infunc && (n=isarg(w)) >= 0) { + yylval.i = n; + RET(ARG); + } else { + yylval.cp = setsymtab(w, "", 0.0, STR|NUM|DONTFREE, symtab); + if (c == '(') { + RET(CALL); + } else { + RET(VAR); + } + } +} + +void startreg(void) /* next call to yylex will return a regular expression */ +{ + reg = 1; +} + +int regexpr(void) +{ + int c, openclass = 0; + static char *buf = 0; + static int bufsz = 500; + char *bp; + + if (buf == 0 && (buf = (char *) malloc(bufsz)) == NULL) + FATAL("out of space for rex expr"); + bp = buf; + for ( ; ((c = input()) != '/' || openclass == 1) && c != 0; ) { + if (!adjbuf(&buf, &bufsz, bp-buf+3, 500, &bp, "regexpr")) + FATAL("out of space for reg expr %.10s...", buf); + if (c == '\n') { + SYNTAX( "newline in regular expression %.10s...", buf ); + unput('\n'); + break; + } else if (c == '\\') { + *bp++ = '\\'; + *bp++ = input(); + } else { + if (c == '[') + openclass = 1; + else if (c == ']') + openclass = 0; + *bp++ = c; + } + } + *bp = 0; + if (c == 0) + SYNTAX("non-terminated regular expression %.10s...", buf); + yylval.s = tostring(buf); + unput('/'); + RET(REGEXPR); +} + +/* low-level lexical stuff, sort of inherited from lex */ + +char ebuf[300]; +char *ep = ebuf; +char yysbuf[100]; /* pushback buffer */ +char *yysptr = yysbuf; +FILE *yyin = 0; + +int input(void) /* get next lexical input character */ +{ + int c; + extern char *lexprog; + + if (yysptr > yysbuf) + c = (uschar)*--yysptr; + else if (lexprog != NULL) { /* awk '...' */ + if ((c = (uschar)*lexprog) != 0) + lexprog++; + } else /* awk -f ... */ + c = pgetc(); + if (c == '\n') + lineno++; + else if (c == EOF) + c = 0; + if (ep >= ebuf + sizeof ebuf) + ep = ebuf; + return *ep++ = c; +} + +void unput(int c) /* put lexical character back on input */ +{ + if (c == '\n') + lineno--; + if (yysptr >= yysbuf + sizeof(yysbuf)) + FATAL("pushed back too much: %.20s...", yysbuf); + *yysptr++ = c; + if (--ep < ebuf) + ep = ebuf + sizeof(ebuf) - 1; +} + +void unputstr(const char *s) /* put a string back on input */ +{ + int i; + + for (i = strlen(s)-1; i >= 0; i--) + unput(s[i]); +} diff --git a/awk/lib.c b/awk/lib.c @@ -0,0 +1,739 @@ +/* $OpenBSD: lib.c,v 1.20 2011/09/28 19:27:18 millert Exp $ */ +/**************************************************************** +Copyright (C) Lucent Technologies 1997 +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 the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name Lucent Technologies or any of +its entities not be used in advertising or publicity pertaining +to distribution of the software without specific, written prior +permission. + +LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES 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. +****************************************************************/ + +#define DEBUG +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdarg.h> +#include "awk.h" +#include "util.h" +#include "ytab.h" + +FILE *infile = NULL; +char *file = ""; +char *record; +int recsize = RECSIZE; +char *fields; +int fieldssize = RECSIZE; + +Cell **fldtab; /* pointers to Cells */ +char inputFS[100] = " "; + +#define MAXFLD 2 +int nfields = MAXFLD; /* last allocated slot for $i */ + +int donefld; /* 1 = implies rec broken into fields */ +int donerec; /* 1 = record is valid (no flds have changed) */ + +int lastfld = 0; /* last used field */ +int argno = 1; /* current input argument number */ +extern Awkfloat *ARGC; + +static Cell dollar0 = { OCELL, CFLD, NULL, "", 0.0, REC|STR|DONTFREE }; +static Cell dollar1 = { OCELL, CFLD, NULL, "", 0.0, FLD|STR|DONTFREE }; + +void recinit(unsigned int n) +{ + if ( (record = (char *) malloc(n)) == NULL + || (fields = (char *) malloc(n+1)) == NULL + || (fldtab = (Cell **) calloc(nfields+1, sizeof(Cell *))) == NULL + || (fldtab[0] = (Cell *) malloc(sizeof(Cell))) == NULL ) + FATAL("out of space for $0 and fields"); + *fldtab[0] = dollar0; + fldtab[0]->sval = record; + fldtab[0]->nval = tostring("0"); + makefields(1, nfields); +} + +void makefields(int n1, int n2) /* create $n1..$n2 inclusive */ +{ + char temp[50]; + int i; + + for (i = n1; i <= n2; i++) { + fldtab[i] = (Cell *) malloc(sizeof (struct Cell)); + if (fldtab[i] == NULL) + FATAL("out of space in makefields %d", i); + *fldtab[i] = dollar1; + snprintf(temp, sizeof temp, "%d", i); + fldtab[i]->nval = tostring(temp); + } +} + +void initgetrec(void) +{ + int i; + char *p; + + for (i = 1; i < *ARGC; i++) { + p = getargv(i); /* find 1st real filename */ + if (p == NULL || *p == '\0') { /* deleted or zapped */ + argno++; + continue; + } + if (!isclvar(p)) { + setsval(lookup("FILENAME", symtab), p); + return; + } + setclvar(p); /* a commandline assignment before filename */ + argno++; + } + infile = stdin; /* no filenames, so use stdin */ +} + +static int firsttime = 1; + +int getrec(char **pbuf, int *pbufsize, int isrecord) /* get next input record */ +{ /* note: cares whether buf == record */ + int c; + char *buf = *pbuf; + uschar saveb0; + int bufsize = *pbufsize, savebufsize = bufsize; + + if (firsttime) { + firsttime = 0; + initgetrec(); + } + dprintf( ("RS=<%s>, FS=<%s>, ARGC=%g, FILENAME=%s\n", + *RS, *FS, *ARGC, *FILENAME) ); + if (isrecord) { + donefld = 0; + donerec = 1; + } + saveb0 = buf[0]; + buf[0] = 0; + while (argno < *ARGC || infile == stdin) { + dprintf( ("argno=%d, file=|%s|\n", argno, file) ); + if (infile == NULL) { /* have to open a new file */ + file = getargv(argno); + if (file == NULL || *file == '\0') { /* deleted or zapped */ + argno++; + continue; + } + if (isclvar(file)) { /* a var=value arg */ + setclvar(file); + argno++; + continue; + } + *FILENAME = file; + dprintf( ("opening file %s\n", file) ); + if (*file == '-' && *(file+1) == '\0') + infile = stdin; + else if ((infile = fopen(file, "r")) == NULL) + FATAL("can't open file %s", file); + setfval(fnrloc, 0.0); + } + c = readrec(&buf, &bufsize, infile); + if (c != 0 || buf[0] != '\0') { /* normal record */ + if (isrecord) { + if (freeable(fldtab[0])) + xfree(fldtab[0]->sval); + fldtab[0]->sval = buf; /* buf == record */ + fldtab[0]->tval = REC | STR | DONTFREE; + if (is_number(fldtab[0]->sval)) { + fldtab[0]->fval = atof(fldtab[0]->sval); + fldtab[0]->tval |= NUM; + } + } + setfval(nrloc, nrloc->fval+1); + setfval(fnrloc, fnrloc->fval+1); + *pbuf = buf; + *pbufsize = bufsize; + return 1; + } + /* EOF arrived on this file; set up next */ + if (infile != stdin) + fclose(infile); + infile = NULL; + argno++; + } + buf[0] = saveb0; + *pbuf = buf; + *pbufsize = savebufsize; + return 0; /* true end of file */ +} + +void nextfile(void) +{ + if (infile != NULL && infile != stdin) + fclose(infile); + infile = NULL; + argno++; +} + +int readrec(char **pbuf, int *pbufsize, FILE *inf) /* read one record into buf */ +{ + int sep, c; + char *rr, *buf = *pbuf; + int bufsize = *pbufsize; + + if (strlen(*FS) >= sizeof(inputFS)) + FATAL("field separator %.10s... is too long", *FS); + /*fflush(stdout); avoids some buffering problem but makes it 25% slower*/ + strlcpy(inputFS, *FS, sizeof inputFS); /* for subsequent field splitting */ + if ((sep = **RS) == 0) { + sep = '\n'; + while ((c=getc(inf)) == '\n' && c != EOF) /* skip leading \n's */ + ; + if (c != EOF) + ungetc(c, inf); + } + for (rr = buf; ; ) { + for (; (c=getc(inf)) != sep && c != EOF; ) { + if (rr-buf+1 > bufsize) + if (!adjbuf(&buf, &bufsize, 1+rr-buf, recsize, &rr, "readrec 1")) + FATAL("input record `%.30s...' too long", buf); + *rr++ = c; + } + if (**RS == sep || c == EOF) + break; + if ((c = getc(inf)) == '\n' || c == EOF) /* 2 in a row */ + break; + if (!adjbuf(&buf, &bufsize, 2+rr-buf, recsize, &rr, "readrec 2")) + FATAL("input record `%.30s...' too long", buf); + *rr++ = '\n'; + *rr++ = c; + } + if (!adjbuf(&buf, &bufsize, 1+rr-buf, recsize, &rr, "readrec 3")) + FATAL("input record `%.30s...' too long", buf); + *rr = 0; + dprintf( ("readrec saw <%s>, returns %d\n", buf, c == EOF && rr == buf ? 0 : 1) ); + *pbuf = buf; + *pbufsize = bufsize; + return c == EOF && rr == buf ? 0 : 1; +} + +char *getargv(int n) /* get ARGV[n] */ +{ + Cell *x; + char *s, temp[50]; + extern Array *ARGVtab; + + snprintf(temp, sizeof temp, "%d", n); + if (lookup(temp, ARGVtab) == NULL) + return NULL; + x = setsymtab(temp, "", 0.0, STR, ARGVtab); + s = getsval(x); + dprintf( ("getargv(%d) returns |%s|\n", n, s) ); + return s; +} + +void setclvar(char *s) /* set var=value from s */ +{ + char *p; + Cell *q; + + for (p=s; *p != '='; p++) + ; + *p++ = 0; + p = qstring(p, '\0'); + q = setsymtab(s, p, 0.0, STR, symtab); + setsval(q, p); + if (is_number(q->sval)) { + q->fval = atof(q->sval); + q->tval |= NUM; + } + dprintf( ("command line set %s to |%s|\n", s, p) ); +} + + +void fldbld(void) /* create fields from current record */ +{ + /* this relies on having fields[] the same length as $0 */ + /* the fields are all stored in this one array with \0's */ + /* possibly with a final trailing \0 not associated with any field */ + char *r, *fr, sep; + Cell *p; + int i, j, n; + + if (donefld) + return; + if (!isstr(fldtab[0])) + getsval(fldtab[0]); + r = fldtab[0]->sval; + n = strlen(r); + if (n > fieldssize) { + xfree(fields); + if ((fields = (char *) malloc(n+2)) == NULL) /* possibly 2 final \0s */ + FATAL("out of space for fields in fldbld %d", n); + fieldssize = n; + } + fr = fields; + i = 0; /* number of fields accumulated here */ + strlcpy(inputFS, *FS, sizeof(inputFS)); + if (strlen(inputFS) > 1) { /* it's a regular expression */ + i = refldbld(r, inputFS); + } else if ((sep = *inputFS) == ' ') { /* default whitespace */ + for (i = 0; ; ) { + while (*r == ' ' || *r == '\t' || *r == '\n') + r++; + if (*r == 0) + break; + i++; + if (i > nfields) + growfldtab(i); + if (freeable(fldtab[i])) + xfree(fldtab[i]->sval); + fldtab[i]->sval = fr; + fldtab[i]->tval = FLD | STR | DONTFREE; + do + *fr++ = *r++; + while (*r != ' ' && *r != '\t' && *r != '\n' && *r != '\0'); + *fr++ = 0; + } + *fr = 0; + } else if ((sep = *inputFS) == 0) { /* new: FS="" => 1 char/field */ + for (i = 0; *r != 0; r++) { + char buf[2]; + i++; + if (i > nfields) + growfldtab(i); + if (freeable(fldtab[i])) + xfree(fldtab[i]->sval); + buf[0] = *r; + buf[1] = 0; + fldtab[i]->sval = tostring(buf); + fldtab[i]->tval = FLD | STR; + } + *fr = 0; + } else if (*r != 0) { /* if 0, it's a null field */ + /* subtlecase : if length(FS) == 1 && length(RS > 0) + * \n is NOT a field separator (cf awk book 61,84). + * this variable is tested in the inner while loop. + */ + int rtest = '\n'; /* normal case */ + if (strlen(*RS) > 0) + rtest = '\0'; + for (;;) { + i++; + if (i > nfields) + growfldtab(i); + if (freeable(fldtab[i])) + xfree(fldtab[i]->sval); + fldtab[i]->sval = fr; + fldtab[i]->tval = FLD | STR | DONTFREE; + while (*r != sep && *r != rtest && *r != '\0') /* \n is always a separator */ + *fr++ = *r++; + *fr++ = 0; + if (*r++ == 0) + break; + } + *fr = 0; + } + if (i > nfields) + FATAL("record `%.30s...' has too many fields; can't happen", r); + cleanfld(i+1, lastfld); /* clean out junk from previous record */ + lastfld = i; + donefld = 1; + for (j = 1; j <= lastfld; j++) { + p = fldtab[j]; + if(is_number(p->sval)) { + p->fval = atof(p->sval); + p->tval |= NUM; + } + } + setfval(nfloc, (Awkfloat) lastfld); + if (dbg) { + for (j = 0; j <= lastfld; j++) { + p = fldtab[j]; + printf("field %d (%s): |%s|\n", j, p->nval, p->sval); + } + } +} + +void cleanfld(int n1, int n2) /* clean out fields n1 .. n2 inclusive */ +{ /* nvals remain intact */ + Cell *p; + int i; + + for (i = n1; i <= n2; i++) { + p = fldtab[i]; + if (freeable(p)) + xfree(p->sval); + p->sval = ""; + p->tval = FLD | STR | DONTFREE; + } +} + +void newfld(int n) /* add field n after end of existing lastfld */ +{ + if (n > nfields) + growfldtab(n); + cleanfld(lastfld+1, n); + lastfld = n; + setfval(nfloc, (Awkfloat) n); +} + +Cell *fieldadr(int n) /* get nth field */ +{ + if (n < 0) + FATAL("trying to access out of range field %d", n); + if (n > nfields) /* fields after NF are empty */ + growfldtab(n); /* but does not increase NF */ + return(fldtab[n]); +} + +void growfldtab(int n) /* make new fields up to at least $n */ +{ + int nf = 2 * nfields; + size_t s; + + if (n > nf) + nf = n; + s = (nf+1) * (sizeof (struct Cell *)); /* freebsd: how much do we need? */ + if (s / sizeof(struct Cell *) - 1 == nf) /* didn't overflow */ + fldtab = (Cell **) realloc(fldtab, s); + else /* overflow sizeof int */ + xfree(fldtab); /* make it null */ + if (fldtab == NULL) + FATAL("out of space creating %d fields", nf); + makefields(nfields+1, nf); + nfields = nf; +} + +int refldbld(const char *rec, const char *fs) /* build fields from reg expr in FS */ +{ + /* this relies on having fields[] the same length as $0 */ + /* the fields are all stored in this one array with \0's */ + char *fr; + int i, tempstat, n; + fa *pfa; + + n = strlen(rec); + if (n > fieldssize) { + xfree(fields); + if ((fields = (char *) malloc(n+1)) == NULL) + FATAL("out of space for fields in refldbld %d", n); + fieldssize = n; + } + fr = fields; + *fr = '\0'; + if (*rec == '\0') + return 0; + pfa = makedfa(fs, 1); + dprintf( ("into refldbld, rec = <%s>, pat = <%s>\n", rec, fs) ); + tempstat = pfa->initstat; + for (i = 1; ; i++) { + if (i > nfields) + growfldtab(i); + if (freeable(fldtab[i])) + xfree(fldtab[i]->sval); + fldtab[i]->tval = FLD | STR | DONTFREE; + fldtab[i]->sval = fr; + dprintf( ("refldbld: i=%d\n", i) ); + if (nematch(pfa, rec)) { + pfa->initstat = 2; /* horrible coupling to b.c */ + dprintf( ("match %s (%d chars)\n", patbeg, patlen) ); + strncpy(fr, rec, patbeg-rec); + fr += patbeg - rec + 1; + *(fr-1) = '\0'; + rec = patbeg + patlen; + } else { + dprintf( ("no match %s\n", rec) ); + strlcpy(fr, rec, fields + fieldssize - fr); + pfa->initstat = tempstat; + break; + } + } + return i; +} + +void recbld(void) /* create $0 from $1..$NF if necessary */ +{ + int i; + char *r, *p; + + if (donerec == 1) + return; + r = record; + for (i = 1; i <= *NF; i++) { + p = getsval(fldtab[i]); + if (!adjbuf(&record, &recsize, 1+strlen(p)+r-record, recsize, &r, "recbld 1")) + FATAL("created $0 `%.30s...' too long", record); + while ((*r = *p++) != 0) + r++; + if (i < *NF) { + if (!adjbuf(&record, &recsize, 2+strlen(*OFS)+r-record, recsize, &r, "recbld 2")) + FATAL("created $0 `%.30s...' too long", record); + for (p = *OFS; (*r = *p++) != 0; ) + r++; + } + } + if (!adjbuf(&record, &recsize, 2+r-record, recsize, &r, "recbld 3")) + FATAL("built giant record `%.30s...'", record); + *r = '\0'; + dprintf( ("in recbld inputFS=%s, fldtab[0]=%p\n", inputFS, (void*)fldtab[0]) ); + + if (freeable(fldtab[0])) + xfree(fldtab[0]->sval); + fldtab[0]->tval = REC | STR | DONTFREE; + fldtab[0]->sval = record; + + dprintf( ("in recbld inputFS=%s, fldtab[0]=%p\n", inputFS, (void*)fldtab[0]) ); + dprintf( ("recbld = |%s|\n", record) ); + donerec = 1; +} + +int errorflag = 0; + +void yyerror(const char *s) +{ + SYNTAX("%s", s); +} + +void SYNTAX(const char *fmt, ...) +{ + extern char *cmdname, *curfname; + static int been_here = 0; + va_list varg; + + if (been_here++ > 2) + return; + fprintf(stderr, "%s: ", cmdname); + va_start(varg, fmt); + vfprintf(stderr, fmt, varg); + va_end(varg); + fprintf(stderr, " at source line %d", lineno); + if (curfname != NULL) + fprintf(stderr, " in function %s", curfname); + if (compile_time == 1 && cursource() != NULL) + fprintf(stderr, " source file %s", cursource()); + fprintf(stderr, "\n"); + errorflag = 2; + eprint(); +} + +void fpecatch(int sig) +{ + extern Node *curnode; + char buf[1024]; + + snprintf(buf, sizeof buf, "floating point exception\n"); + write(STDERR_FILENO, buf, strlen(buf)); + + if (compile_time != 2 && NR && *NR > 0) { + snprintf(buf, sizeof buf, " input record number %d", (int) (*FNR)); + write(STDERR_FILENO, buf, strlen(buf)); + + if (strcmp(*FILENAME, "-") != 0) { + snprintf(buf, sizeof buf, ", file %s", *FILENAME); + write(STDERR_FILENO, buf, strlen(buf)); + } + write(STDERR_FILENO, "\n", 1); + } + if (compile_time != 2 && curnode) { + snprintf(buf, sizeof buf, " source line number %d", curnode->lineno); + write(STDERR_FILENO, buf, strlen(buf)); + } else if (compile_time != 2 && lineno) { + snprintf(buf, sizeof buf, " source line number %d", lineno); + write(STDERR_FILENO, buf, strlen(buf)); + } + if (compile_time == 1 && cursource() != NULL) { + snprintf(buf, sizeof buf, " source file %s", cursource()); + write(STDERR_FILENO, buf, strlen(buf)); + } + write(STDERR_FILENO, "\n", 1); + if (dbg > 1) /* core dump if serious debugging on */ + abort(); + _exit(1); +} + +extern int bracecnt, brackcnt, parencnt; + +void bracecheck(void) +{ + int c; + static int beenhere = 0; + + if (beenhere++) + return; + while ((c = input()) != EOF && c != '\0') + bclass(c); + bcheck2(bracecnt, '{', '}'); + bcheck2(brackcnt, '[', ']'); + bcheck2(parencnt, '(', ')'); +} + +void bcheck2(int n, int c1, int c2) +{ + if (n == 1) + fprintf(stderr, "\tmissing %c\n", c2); + else if (n > 1) + fprintf(stderr, "\t%d missing %c's\n", n, c2); + else if (n == -1) + fprintf(stderr, "\textra %c\n", c2); + else if (n < -1) + fprintf(stderr, "\t%d extra %c's\n", -n, c2); +} + +void FATAL(const char *fmt, ...) +{ + extern char *cmdname; + va_list varg; + + fflush(stdout); + fprintf(stderr, "%s: ", cmdname); + va_start(varg, fmt); + vfprintf(stderr, fmt, varg); + va_end(varg); + error(); + if (dbg > 1) /* core dump if serious debugging on */ + abort(); + exit(2); +} + +void WARNING(const char *fmt, ...) +{ + extern char *cmdname; + va_list varg; + + fflush(stdout); + fprintf(stderr, "%s: ", cmdname); + va_start(varg, fmt); + vfprintf(stderr, fmt, varg); + va_end(varg); + error(); +} + +void error() +{ + extern Node *curnode; + + fprintf(stderr, "\n"); + if (compile_time != 2 && NR && *NR > 0) { + fprintf(stderr, " input record number %d", (int) (*FNR)); + if (strcmp(*FILENAME, "-") != 0) + fprintf(stderr, ", file %s", *FILENAME); + fprintf(stderr, "\n"); + } + if (compile_time != 2 && curnode) + fprintf(stderr, " source line number %d", curnode->lineno); + else if (compile_time != 2 && lineno) + fprintf(stderr, " source line number %d", lineno); + if (compile_time == 1 && cursource() != NULL) + fprintf(stderr, " source file %s", cursource()); + fprintf(stderr, "\n"); + eprint(); +} + +void eprint(void) /* try to print context around error */ +{ + char *p, *q; + int c; + static int been_here = 0; + extern char ebuf[], *ep; + + if (compile_time == 2 || compile_time == 0 || been_here++ > 0) + return; + p = ep - 1; + if (p > ebuf && *p == '\n') + p--; + for ( ; p > ebuf && *p != '\n' && *p != '\0'; p--) + ; + while (*p == '\n') + p++; + fprintf(stderr, " context is\n\t"); + for (q=ep-1; q>=p && *q!=' ' && *q!='\t' && *q!='\n'; q--) + ; + for ( ; p < q; p++) + if (*p) + putc(*p, stderr); + fprintf(stderr, " >>> "); + for ( ; p < ep; p++) + if (*p) + putc(*p, stderr); + fprintf(stderr, " <<< "); + if (*ep) + while ((c = input()) != '\n' && c != '\0' && c != EOF) { + putc(c, stderr); + bclass(c); + } + putc('\n', stderr); + ep = ebuf; +} + +void bclass(int c) +{ + switch (c) { + case '{': bracecnt++; break; + case '}': bracecnt--; break; + case '[': brackcnt++; break; + case ']': brackcnt--; break; + case '(': parencnt++; break; + case ')': parencnt--; break; + } +} + +double errcheck(double x, const char *s) +{ + + if (errno == EDOM) { + errno = 0; + WARNING("%s argument out of domain", s); + x = 1; + } else if (errno == ERANGE) { + errno = 0; + WARNING("%s result out of range", s); + x = 1; + } + return x; +} + +int isclvar(const char *s) /* is s of form var=something ? */ +{ + const char *os = s; + + if (!isalpha((uschar) *s) && *s != '_') + return 0; + for ( ; *s; s++) + if (!(isalnum((uschar) *s) || *s == '_')) + break; + return *s == '=' && s > os && *(s+1) != '='; +} + +/* strtod is supposed to be a proper test of what's a valid number */ +/* appears to be broken in gcc on linux: thinks 0x123 is a valid FP number */ +/* wrong: violates 4.10.1.4 of ansi C standard */ + +#include <math.h> +int is_number(const char *s) +{ + double r; + char *ep; + errno = 0; + r = strtod(s, &ep); + if (ep == s || r == HUGE_VAL || errno == ERANGE) + return 0; + while (*ep == ' ' || *ep == '\t' || *ep == '\n') + ep++; + if (*ep == '\0') + return 1; + else + return 0; +} diff --git a/awk/main.c b/awk/main.c @@ -0,0 +1,212 @@ +/* $OpenBSD: main.c,v 1.17 2011/09/28 19:27:18 millert Exp $ */ +/**************************************************************** +Copyright (C) Lucent Technologies 1997 +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 the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name Lucent Technologies or any of +its entities not be used in advertising or publicity pertaining +to distribution of the software without specific, written prior +permission. + +LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES 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. +****************************************************************/ + +const char *version = "version 20110810"; + +#define DEBUG +#include <stdio.h> +#include <ctype.h> +#include <locale.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include "awk.h" +#include "ytab.h" + +extern char **environ; +extern int nfields; +extern char *__progname; + +int dbg = 0; +Awkfloat srand_seed = 1; +char *cmdname; /* gets argv[0] for error messages */ +extern FILE *yyin; /* lex input file */ +char *lexprog; /* points to program argument if it exists */ +extern int errorflag; /* non-zero if any syntax errors; set by yyerror */ +int compile_time = 2; /* for error printing: */ + /* 2 = cmdline, 1 = compile, 0 = running */ + +#define MAX_PFILE 20 /* max number of -f's */ + +char *pfile[MAX_PFILE]; /* program filenames from -f's */ +int npfile = 0; /* number of filenames */ +int curpfile = 0; /* current filename */ + +int safe = 0; /* 1 => "safe" mode */ + +int main(int argc, char *argv[]) +{ + const char *fs = NULL; + + setlocale(LC_ALL, ""); + setlocale(LC_NUMERIC, "C"); /* for parsing cmdline & prog */ + cmdname = __progname; + if (argc == 1) { + fprintf(stderr, "usage: %s [-safe] [-V] [-d[n]] [-F fs] " + "[-v var=value] [prog | -f progfile]\n\tfile ...\n", + cmdname); + exit(1); + } + signal(SIGFPE, fpecatch); + + yyin = NULL; + symtab = makesymtab(NSYMTAB); + while (argc > 1 && argv[1][0] == '-' && argv[1][1] != '\0') { + if (strcmp(argv[1], "--") == 0) { /* explicit end of args */ + argc--; + argv++; + break; + } + switch (argv[1][1]) { + case 's': + if (strcmp(argv[1], "-safe") == 0) + safe = 1; + break; + case 'f': /* next argument is program filename */ + if (argv[1][2] != 0) { /* arg is -fsomething */ + if (npfile >= MAX_PFILE - 1) + FATAL("too many -f options"); + pfile[npfile++] = &argv[1][2]; + } else { /* arg is -f something */ + argc--; argv++; + if (argc <= 1) + FATAL("no program filename"); + if (npfile >= MAX_PFILE - 1) + FATAL("too many -f options"); + pfile[npfile++] = argv[1]; + } + break; + case 'F': /* set field separator */ + if (argv[1][2] != 0) { /* arg is -Fsomething */ + if (argv[1][2] == 't' && argv[1][3] == 0) /* wart: t=>\t */ + fs = "\t"; + else if (argv[1][2] != 0) + fs = &argv[1][2]; + } else { /* arg is -F something */ + argc--; argv++; + if (argc > 1 && argv[1][0] == 't' && argv[1][1] == 0) /* wart: t=>\t */ + fs = "\t"; + else if (argc > 1 && argv[1][0] != 0) + fs = &argv[1][0]; + } + if (fs == NULL || *fs == '\0') + WARNING("field separator FS is empty"); + break; + case 'v': /* -v a=1 to be done NOW. one -v for each */ + if (argv[1][2] != 0) { /* arg is -vsomething */ + if (isclvar(&argv[1][2])) + setclvar(&argv[1][2]); + else + FATAL("invalid -v option argument: %s", &argv[1][2]); + } else { /* arg is -v something */ + argc--; argv++; + if (argc <= 1) + FATAL("no variable name"); + if (isclvar(argv[1])) + setclvar(argv[1]); + else + FATAL("invalid -v option argument: %s", argv[1]); + } + break; + case 'd': + dbg = atoi(&argv[1][2]); + if (dbg == 0) + dbg = 1; + printf("awk %s\n", version); + break; + case 'V': /* added for exptools "standard" */ + printf("awk %s\n", version); + exit(0); + break; + default: + WARNING("unknown option %s ignored", argv[1]); + break; + } + argc--; + argv++; + } + /* argv[1] is now the first argument */ + if (npfile == 0) { /* no -f; first argument is program */ + if (argc <= 1) { + if (dbg) + exit(0); + FATAL("no program given"); + } + dprintf( ("program = |%s|\n", argv[1]) ); + lexprog = argv[1]; + argc--; + argv++; + } + recinit(recsize); + syminit(); + compile_time = 1; + argv[0] = cmdname; /* put prog name at front of arglist */ + dprintf( ("argc=%d, argv[0]=%s\n", argc, argv[0]) ); + arginit(argc, argv); + if (!safe) + envinit(environ); + yyparse(); + setlocale(LC_NUMERIC, ""); /* back to whatever it is locally */ + if (fs) + *FS = qstring(fs, '\0'); + dprintf( ("errorflag=%d\n", errorflag) ); + if (errorflag == 0) { + compile_time = 0; + run(winner); + } else + bracecheck(); + return(errorflag); +} + +int pgetc(void) /* get 1 character from awk program */ +{ + int c; + + for (;;) { + if (yyin == NULL) { + if (curpfile >= npfile) + return EOF; + if (strcmp(pfile[curpfile], "-") == 0) + yyin = stdin; + else if ((yyin = fopen(pfile[curpfile], "r")) == NULL) + FATAL("can't open file %s", pfile[curpfile]); + lineno = 1; + } + if ((c = getc(yyin)) != EOF) + return c; + if (yyin != stdin) + fclose(yyin); + yyin = NULL; + curpfile++; + } +} + +char *cursource(void) /* current source file name */ +{ + if (npfile > 0) + return pfile[curpfile]; + else + return NULL; +} diff --git a/awk/maketab.c b/awk/maketab.c @@ -0,0 +1,172 @@ +/* $OpenBSD: maketab.c,v 1.11 2010/06/13 17:58:19 millert Exp $ */ +/**************************************************************** +Copyright (C) Lucent Technologies 1997 +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 the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name Lucent Technologies or any of +its entities not be used in advertising or publicity pertaining +to distribution of the software without specific, written prior +permission. + +LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES 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. +****************************************************************/ + +/* + * this program makes the table to link function names + * and type indices that is used by execute() in run.c. + * it finds the indices in ytab.h, produced by yacc. + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include "awk.h" +#include "ytab.h" + +struct xx +{ int token; + const char *name; + const char *pname; +} proc[] = { + { PROGRAM, "program", NULL }, + { BOR, "boolop", " || " }, + { AND, "boolop", " && " }, + { NOT, "boolop", " !" }, + { NE, "relop", " != " }, + { EQ, "relop", " == " }, + { LE, "relop", " <= " }, + { LT, "relop", " < " }, + { GE, "relop", " >= " }, + { GT, "relop", " > " }, + { ARRAY, "array", NULL }, + { INDIRECT, "indirect", "$(" }, + { SUBSTR, "substr", "substr" }, + { SUB, "sub", "sub" }, + { GSUB, "gsub", "gsub" }, + { INDEX, "sindex", "sindex" }, + { SPRINTF, "awksprintf", "sprintf " }, + { ADD, "arith", " + " }, + { MINUS, "arith", " - " }, + { MULT, "arith", " * " }, + { DIVIDE, "arith", " / " }, + { MOD, "arith", " % " }, + { UMINUS, "arith", " -" }, + { POWER, "arith", " **" }, + { PREINCR, "incrdecr", "++" }, + { POSTINCR, "incrdecr", "++" }, + { PREDECR, "incrdecr", "--" }, + { POSTDECR, "incrdecr", "--" }, + { CAT, "cat", " " }, + { PASTAT, "pastat", NULL }, + { PASTAT2, "dopa2", NULL }, + { MATCH, "matchop", " ~ " }, + { NOTMATCH, "matchop", " !~ " }, + { MATCHFCN, "matchop", "matchop" }, + { INTEST, "intest", "intest" }, + { PRINTF, "awkprintf", "printf" }, + { PRINT, "printstat", "print" }, + { CLOSE, "closefile", "closefile" }, + { DELETE, "awkdelete", "awkdelete" }, + { SPLIT, "split", "split" }, + { ASSIGN, "assign", " = " }, + { ADDEQ, "assign", " += " }, + { SUBEQ, "assign", " -= " }, + { MULTEQ, "assign", " *= " }, + { DIVEQ, "assign", " /= " }, + { MODEQ, "assign", " %= " }, + { POWEQ, "assign", " ^= " }, + { CONDEXPR, "condexpr", " ?: " }, + { IF, "ifstat", "if(" }, + { WHILE, "whilestat", "while(" }, + { FOR, "forstat", "for(" }, + { DO, "dostat", "do" }, + { IN, "instat", "instat" }, + { NEXT, "jump", "next" }, + { NEXTFILE, "jump", "nextfile" }, + { EXIT, "jump", "exit" }, + { BREAK, "jump", "break" }, + { CONTINUE, "jump", "continue" }, + { RETURN, "jump", "ret" }, + { BLTIN, "bltin", "bltin" }, + { CALL, "call", "call" }, + { ARG, "arg", "arg" }, + { VARNF, "getnf", "NF" }, + { GETLINE, "awkgetline", "getline" }, + { 0, "", "" }, +}; + +#define SIZE (LASTTOKEN - FIRSTTOKEN + 1) +const char *table[SIZE]; +char *names[SIZE]; + +int main(int argc, char *argv[]) +{ + const struct xx *p; + int i, n, tok; + char c; + FILE *fp; + char buf[200], name[200], def[200]; + + printf("#include <stdio.h>\n"); + printf("#include \"awk.h\"\n"); + printf("#include \"ytab.h\"\n\n"); + for (i = SIZE; --i >= 0; ) + names[i] = ""; + + if ((fp = fopen("ytab.h", "r")) == NULL) { + fprintf(stderr, "maketab: can't open ytab.h!\n"); + exit(1); + } + printf("static char *printname[%d] = {\n", SIZE); + i = 0; + while (fgets(buf, sizeof buf, fp) != NULL) { + n = sscanf(buf, "%1c %s %s %d", &c, def, name, &tok); + if (n != 4 || c != '#' || strcmp(def, "define") != 0) + continue; /* not a valid #define */ + if (tok < FIRSTTOKEN || tok > LASTTOKEN) { + /* fprintf(stderr, "maketab: funny token %d %s ignored\n", tok, buf); */ + continue; + } + names[tok-FIRSTTOKEN] = (char *) strdup(name); + if (names[tok-FIRSTTOKEN] == NULL) { + fprintf(stderr, "maketab: out of memory\n"); + exit(1); + } + printf("\t(char *) \"%s\",\t/* %d */\n", name, tok); + i++; + } + printf("};\n\n"); + + for (p=proc; p->token!=0; p++) + table[p->token-FIRSTTOKEN] = p->name; + printf("\nCell *(*proctab[%d])(Node **, int) = {\n", SIZE); + for (i=0; i<SIZE; i++) + if (table[i]==0) + printf("\tnullproc,\t/* %s */\n", names[i]); + else + printf("\t%s,\t/* %s */\n", table[i], names[i]); + printf("};\n\n"); + + printf("char *tokname(int n)\n"); /* print a tokname() function */ + printf("{\n"); + printf(" static char buf[100];\n\n"); + printf(" if (n < FIRSTTOKEN || n > LASTTOKEN) {\n"); + printf(" snprintf(buf, sizeof buf, \"token %%d\", n);\n"); + printf(" return buf;\n"); + printf(" }\n"); + printf(" return printname[n-FIRSTTOKEN];\n"); + printf("}\n"); + return 0; +} diff --git a/awk/parse.c b/awk/parse.c @@ -0,0 +1,277 @@ +/* $OpenBSD: parse.c,v 1.6 2002/12/19 21:24:28 millert Exp $ */ +/**************************************************************** +Copyright (C) Lucent Technologies 1997 +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 the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name Lucent Technologies or any of +its entities not be used in advertising or publicity pertaining +to distribution of the software without specific, written prior +permission. + +LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES 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. +****************************************************************/ + +#define DEBUG +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include "awk.h" +#include "ytab.h" + +Node *nodealloc(int n) +{ + Node *x; + + x = (Node *) malloc(sizeof(Node) + (n-1)*sizeof(Node *)); + if (x == NULL) + FATAL("out of space in nodealloc"); + x->nnext = NULL; + x->lineno = lineno; + return(x); +} + +Node *exptostat(Node *a) +{ + a->ntype = NSTAT; + return(a); +} + +Node *node1(int a, Node *b) +{ + Node *x; + + x = nodealloc(1); + x->nobj = a; + x->narg[0]=b; + return(x); +} + +Node *node2(int a, Node *b, Node *c) +{ + Node *x; + + x = nodealloc(2); + x->nobj = a; + x->narg[0] = b; + x->narg[1] = c; + return(x); +} + +Node *node3(int a, Node *b, Node *c, Node *d) +{ + Node *x; + + x = nodealloc(3); + x->nobj = a; + x->narg[0] = b; + x->narg[1] = c; + x->narg[2] = d; + return(x); +} + +Node *node4(int a, Node *b, Node *c, Node *d, Node *e) +{ + Node *x; + + x = nodealloc(4); + x->nobj = a; + x->narg[0] = b; + x->narg[1] = c; + x->narg[2] = d; + x->narg[3] = e; + return(x); +} + +Node *stat1(int a, Node *b) +{ + Node *x; + + x = node1(a,b); + x->ntype = NSTAT; + return(x); +} + +Node *stat2(int a, Node *b, Node *c) +{ + Node *x; + + x = node2(a,b,c); + x->ntype = NSTAT; + return(x); +} + +Node *stat3(int a, Node *b, Node *c, Node *d) +{ + Node *x; + + x = node3(a,b,c,d); + x->ntype = NSTAT; + return(x); +} + +Node *stat4(int a, Node *b, Node *c, Node *d, Node *e) +{ + Node *x; + + x = node4(a,b,c,d,e); + x->ntype = NSTAT; + return(x); +} + +Node *op1(int a, Node *b) +{ + Node *x; + + x = node1(a,b); + x->ntype = NEXPR; + return(x); +} + +Node *op2(int a, Node *b, Node *c) +{ + Node *x; + + x = node2(a,b,c); + x->ntype = NEXPR; + return(x); +} + +Node *op3(int a, Node *b, Node *c, Node *d) +{ + Node *x; + + x = node3(a,b,c,d); + x->ntype = NEXPR; + return(x); +} + +Node *op4(int a, Node *b, Node *c, Node *d, Node *e) +{ + Node *x; + + x = node4(a,b,c,d,e); + x->ntype = NEXPR; + return(x); +} + +Node *celltonode(Cell *a, int b) +{ + Node *x; + + a->ctype = OCELL; + a->csub = b; + x = node1(0, (Node *) a); + x->ntype = NVALUE; + return(x); +} + +Node *rectonode(void) /* make $0 into a Node */ +{ + extern Cell *literal0; + return op1(INDIRECT, celltonode(literal0, CUNK)); +} + +Node *makearr(Node *p) +{ + Cell *cp; + + if (isvalue(p)) { + cp = (Cell *) (p->narg[0]); + if (isfcn(cp)) + SYNTAX( "%s is a function, not an array", cp->nval ); + else if (!isarr(cp)) { + xfree(cp->sval); + cp->sval = (char *) makesymtab(NSYMTAB); + cp->tval = ARR; + } + } + return p; +} + +#define PA2NUM 50 /* max number of pat,pat patterns allowed */ +int paircnt; /* number of them in use */ +int pairstack[PA2NUM]; /* state of each pat,pat */ + +Node *pa2stat(Node *a, Node *b, Node *c) /* pat, pat {...} */ +{ + Node *x; + + x = node4(PASTAT2, a, b, c, itonp(paircnt)); + if (paircnt++ >= PA2NUM) + SYNTAX( "limited to %d pat,pat statements", PA2NUM ); + x->ntype = NSTAT; + return(x); +} + +Node *linkum(Node *a, Node *b) +{ + Node *c; + + if (errorflag) /* don't link things that are wrong */ + return a; + if (a == NULL) + return(b); + else if (b == NULL) + return(a); + for (c = a; c->nnext != NULL; c = c->nnext) + ; + c->nnext = b; + return(a); +} + +void defn(Cell *v, Node *vl, Node *st) /* turn on FCN bit in definition, */ +{ /* body of function, arglist */ + Node *p; + int n; + + if (isarr(v)) { + SYNTAX( "`%s' is an array name and a function name", v->nval ); + return; + } + if (isarg(v->nval) != -1) { + SYNTAX( "`%s' is both function name and argument name", v->nval ); + return; + } + + v->tval = FCN; + v->sval = (char *) st; + n = 0; /* count arguments */ + for (p = vl; p; p = p->nnext) + n++; + v->fval = n; + dprintf( ("defining func %s (%d args)\n", v->nval, n) ); +} + +int isarg(const char *s) /* is s in argument list for current function? */ +{ /* return -1 if not, otherwise arg # */ + extern Node *arglist; + Node *p = arglist; + int n; + + for (n = 0; p != 0; p = p->nnext, n++) + if (strcmp(((Cell *)(p->narg[0]))->nval, s) == 0) + return n; + return -1; +} + +int ptoi(void *p) /* convert pointer to integer */ +{ + return (int) (long) p; /* swearing that p fits, of course */ +} + +Node *itonp(int i) /* and vice versa */ +{ + return (Node *) (long) i; +} diff --git a/awk/proto.h b/awk/proto.h @@ -0,0 +1,196 @@ +/* $OpenBSD: proto.h,v 1.9 2011/09/28 19:27:18 millert Exp $ */ +/**************************************************************** +Copyright (C) Lucent Technologies 1997 +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 the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name Lucent Technologies or any of +its entities not be used in advertising or publicity pertaining +to distribution of the software without specific, written prior +permission. + +LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES 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. +****************************************************************/ + +extern int yywrap(void); +extern void setfname(Cell *); +extern int constnode(Node *); +extern char *strnode(Node *); +extern Node *notnull(Node *); +extern int yyparse(void); + +extern int yylex(void); +extern void startreg(void); +extern int input(void); +extern void unput(int); +extern void unputstr(const char *); +extern int yylook(void); +extern int yyback(int *, int); +extern int yyinput(void); + +extern fa *makedfa(const char *, int); +extern fa *mkdfa(const char *, int); +extern int makeinit(fa *, int); +extern void penter(Node *); +extern void freetr(Node *); +extern int hexstr(uschar **); +extern int quoted(uschar **); +extern char *cclenter(const char *); +extern void overflo(const char *); +extern void cfoll(fa *, Node *); +extern int first(Node *); +extern void follow(Node *); +extern int member(int, const char *); +extern int match(fa *, const char *); +extern int pmatch(fa *, const char *); +extern int nematch(fa *, const char *); +extern Node *reparse(const char *); +extern Node *regexp(void); +extern Node *primary(void); +extern Node *concat(Node *); +extern Node *alt(Node *); +extern Node *unary(Node *); +extern int relex(void); +extern int cgoto(fa *, int, int); +extern void freefa(fa *); + +extern int pgetc(void); +extern char *cursource(void); + +extern Node *nodealloc(int); +extern Node *exptostat(Node *); +extern Node *node1(int, Node *); +extern Node *node2(int, Node *, Node *); +extern Node *node3(int, Node *, Node *, Node *); +extern Node *node4(int, Node *, Node *, Node *, Node *); +extern Node *stat3(int, Node *, Node *, Node *); +extern Node *op2(int, Node *, Node *); +extern Node *op1(int, Node *); +extern Node *stat1(int, Node *); +extern Node *op3(int, Node *, Node *, Node *); +extern Node *op4(int, Node *, Node *, Node *, Node *); +extern Node *stat2(int, Node *, Node *); +extern Node *stat4(int, Node *, Node *, Node *, Node *); +extern Node *celltonode(Cell *, int); +extern Node *rectonode(void); +extern Node *makearr(Node *); +extern Node *pa2stat(Node *, Node *, Node *); +extern Node *linkum(Node *, Node *); +extern void defn(Cell *, Node *, Node *); +extern int isarg(const char *); +extern char *tokname(int); +extern Cell *(*proctab[])(Node **, int); +extern int ptoi(void *); +extern Node *itonp(int); + +extern void syminit(void); +extern void arginit(int, char **); +extern void envinit(char **); +extern Array *makesymtab(int); +extern void freesymtab(Cell *); +extern void freeelem(Cell *, const char *); +extern Cell *setsymtab(const char *, const char *, double, unsigned int, Array *); +extern int hash(const char *, int); +extern void rehash(Array *); +extern Cell *lookup(const char *, Array *); +extern double setfval(Cell *, double); +extern void funnyvar(Cell *, const char *); +extern char *setsval(Cell *, const char *); +extern double getfval(Cell *); +extern char *getsval(Cell *); +extern char *getpssval(Cell *); /* for print */ +extern char *tostring(const char *); +extern char *qstring(const char *, int); + +extern void recinit(unsigned int); +extern void initgetrec(void); +extern void makefields(int, int); +extern void growfldtab(int n); +extern int getrec(char **, int *, int); +extern void nextfile(void); +extern int readrec(char **buf, int *bufsize, FILE *inf); +extern char *getargv(int); +extern void setclvar(char *); +extern void fldbld(void); +extern void cleanfld(int, int); +extern void newfld(int); +extern int refldbld(const char *, const char *); +extern void recbld(void); +extern Cell *fieldadr(int); +extern void yyerror(const char *); +extern void fpecatch(int); +extern void bracecheck(void); +extern void bcheck2(int, int, int); +extern void SYNTAX(const char *, ...); +extern void FATAL(const char *, ...); +extern void WARNING(const char *, ...); +extern void error(void); +extern void eprint(void); +extern void bclass(int); +extern double errcheck(double, const char *); +extern int isclvar(const char *); +extern int is_number(const char *); + +extern int adjbuf(char **pb, int *sz, int min, int q, char **pbp, const char *what); +extern void run(Node *); +extern Cell *execute(Node *); +extern Cell *program(Node **, int); +extern Cell *call(Node **, int); +extern Cell *copycell(Cell *); +extern Cell *arg(Node **, int); +extern Cell *jump(Node **, int); +extern Cell *awkgetline(Node **, int); +extern Cell *getnf(Node **, int); +extern Cell *array(Node **, int); +extern Cell *awkdelete(Node **, int); +extern Cell *intest(Node **, int); +extern Cell *matchop(Node **, int); +extern Cell *boolop(Node **, int); +extern Cell *relop(Node **, int); +extern void tfree(Cell *); +extern Cell *gettemp(void); +extern Cell *field(Node **, int); +extern Cell *indirect(Node **, int); +extern Cell *substr(Node **, int); +extern Cell *sindex(Node **, int); +extern int format(char **, int *, const char *, Node *); +extern Cell *awksprintf(Node **, int); +extern Cell *awkprintf(Node **, int); +extern Cell *arith(Node **, int); +extern double ipow(double, int); +extern Cell *incrdecr(Node **, int); +extern Cell *assign(Node **, int); +extern Cell *cat(Node **, int); +extern Cell *pastat(Node **, int); +extern Cell *dopa2(Node **, int); +extern Cell *split(Node **, int); +extern Cell *condexpr(Node **, int); +extern Cell *ifstat(Node **, int); +extern Cell *whilestat(Node **, int); +extern Cell *dostat(Node **, int); +extern Cell *forstat(Node **, int); +extern Cell *instat(Node **, int); +extern Cell *bltin(Node **, int); +extern Cell *printstat(Node **, int); +extern Cell *nullproc(Node **, int); +extern FILE *redirect(int, Node *); +extern FILE *openfile(int, const char *); +extern const char *filename(FILE *); +extern Cell *closefile(Node **, int); +extern void closeall(void); +extern Cell *sub(Node **, int); +extern Cell *gsub(Node **, int); + +extern FILE *popen(const char *, const char *); +extern int pclose(FILE *); diff --git a/awk/reallocarray.c b/awk/reallocarray.c @@ -0,0 +1,38 @@ +/* $OpenBSD: reallocarray.c,v 1.1 2014/05/08 21:43:49 deraadt Exp $ */ +/* + * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net> + * + * 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 <sys/types.h> +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> + +/* + * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX + * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW + */ +#define MUL_NO_OVERFLOW (1UL << (sizeof(size_t) * 4)) + +void * +reallocarray(void *optr, size_t nmemb, size_t size) +{ + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) { + errno = ENOMEM; + return NULL; + } + return realloc(optr, size * nmemb); +} diff --git a/awk/run.c b/awk/run.c @@ -0,0 +1,2027 @@ +/* $OpenBSD: run.c,v 1.35 2014/10/11 03:07:29 doug Exp $ */ +/**************************************************************** +Copyright (C) Lucent Technologies 1997 +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 the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name Lucent Technologies or any of +its entities not be used in advertising or publicity pertaining +to distribution of the software without specific, written prior +permission. + +LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES 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. +****************************************************************/ + +#define DEBUG +#include <stdio.h> +#include <ctype.h> +#include <setjmp.h> +#include <limits.h> +#include <math.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include "awk.h" +#include "util.h" +#include "ytab.h" + +#define tempfree(x) if (istemp(x)) tfree(x); else + +/* +#undef tempfree + +void tempfree(Cell *p) { + if (p->ctype == OCELL && (p->csub < CUNK || p->csub > CFREE)) { + WARNING("bad csub %d in Cell %d %s", + p->csub, p->ctype, p->sval); + } + if (istemp(p)) + tfree(p); +} +*/ + +/* do we really need these? */ +/* #ifdef _NFILE */ +/* #ifndef FOPEN_MAX */ +/* #define FOPEN_MAX _NFILE */ +/* #endif */ +/* #endif */ +/* */ +/* #ifndef FOPEN_MAX */ +/* #define FOPEN_MAX 40 */ /* max number of open files */ +/* #endif */ +/* */ +/* #ifndef RAND_MAX */ +/* #define RAND_MAX 32767 */ /* all that ansi guarantees */ +/* #endif */ + +jmp_buf env; +int use_srandom; +extern int pairstack[]; +extern Awkfloat srand_seed; + +Node *winner = NULL; /* root of parse tree */ +Cell *tmps; /* free temporary cells for execution */ + +static Cell truecell ={ OBOOL, BTRUE, 0, 0, 1.0, NUM }; +Cell *True = &truecell; +static Cell falsecell ={ OBOOL, BFALSE, 0, 0, 0.0, NUM }; +Cell *False = &falsecell; +static Cell breakcell ={ OJUMP, JBREAK, 0, 0, 0.0, NUM }; +Cell *jbreak = &breakcell; +static Cell contcell ={ OJUMP, JCONT, 0, 0, 0.0, NUM }; +Cell *jcont = &contcell; +static Cell nextcell ={ OJUMP, JNEXT, 0, 0, 0.0, NUM }; +Cell *jnext = &nextcell; +static Cell nextfilecell ={ OJUMP, JNEXTFILE, 0, 0, 0.0, NUM }; +Cell *jnextfile = &nextfilecell; +static Cell exitcell ={ OJUMP, JEXIT, 0, 0, 0.0, NUM }; +Cell *jexit = &exitcell; +static Cell retcell ={ OJUMP, JRET, 0, 0, 0.0, NUM }; +Cell *jret = &retcell; +static Cell tempcell ={ OCELL, CTEMP, 0, "", 0.0, NUM|STR|DONTFREE }; + +Node *curnode = NULL; /* the node being executed, for debugging */ + +void stdinit(void); +void flush_all(void); + +/* buffer memory management */ +int adjbuf(char **pbuf, int *psiz, int minlen, int quantum, char **pbptr, + const char *whatrtn) +/* pbuf: address of pointer to buffer being managed + * psiz: address of buffer size variable + * minlen: minimum length of buffer needed + * quantum: buffer size quantum + * pbptr: address of movable pointer into buffer, or 0 if none + * whatrtn: name of the calling routine if failure should cause fatal error + * + * return 0 for realloc failure, !=0 for success + */ +{ + if (minlen > *psiz) { + char *tbuf; + int rminlen = quantum ? minlen % quantum : 0; + int boff = pbptr ? *pbptr - *pbuf : 0; + /* round up to next multiple of quantum */ + if (rminlen) + minlen += quantum - rminlen; + tbuf = (char *) realloc(*pbuf, minlen); + dprintf( ("adjbuf %s: %d %d (pbuf=%p, tbuf=%p)\n", whatrtn, *psiz, minlen, *pbuf, tbuf) ); + if (tbuf == NULL) { + if (whatrtn) + FATAL("out of memory in %s", whatrtn); + return 0; + } + *pbuf = tbuf; + *psiz = minlen; + if (pbptr) + *pbptr = tbuf + boff; + } + return 1; +} + +void run(Node *a) /* execution of parse tree starts here */ +{ + stdinit(); + execute(a); + closeall(); +} + +Cell *execute(Node *u) /* execute a node of the parse tree */ +{ + Cell *(*proc)(Node **, int); + Cell *x; + Node *a; + + if (u == NULL) + return(True); + for (a = u; ; a = a->nnext) { + curnode = a; + if (isvalue(a)) { + x = (Cell *) (a->narg[0]); + if (isfld(x) && !donefld) + fldbld(); + else if (isrec(x) && !donerec) + recbld(); + return(x); + } + if (notlegal(a->nobj)) /* probably a Cell* but too risky to print */ + FATAL("illegal statement"); + proc = proctab[a->nobj-FIRSTTOKEN]; + x = (*proc)(a->narg, a->nobj); + if (isfld(x) && !donefld) + fldbld(); + else if (isrec(x) && !donerec) + recbld(); + if (isexpr(a)) + return(x); + if (isjump(x)) + return(x); + if (a->nnext == NULL) + return(x); + tempfree(x); + } +} + + +Cell *program(Node **a, int n) /* execute an awk program */ +{ /* a[0] = BEGIN, a[1] = body, a[2] = END */ + Cell *x; + + if (setjmp(env) != 0) + goto ex; + if (a[0]) { /* BEGIN */ + x = execute(a[0]); + if (isexit(x)) + return(True); + if (isjump(x)) + FATAL("illegal break, continue, next or nextfile from BEGIN"); + tempfree(x); + } + if (a[1] || a[2]) + while (getrec(&record, &recsize, 1) > 0) { + x = execute(a[1]); + if (isexit(x)) + break; + tempfree(x); + } + ex: + if (setjmp(env) != 0) /* handles exit within END */ + goto ex1; + if (a[2]) { /* END */ + x = execute(a[2]); + if (isbreak(x) || isnext(x) || iscont(x)) + FATAL("illegal break, continue, next or nextfile from END"); + tempfree(x); + } + ex1: + return(True); +} + +struct Frame { /* stack frame for awk function calls */ + int nargs; /* number of arguments in this call */ + Cell *fcncell; /* pointer to Cell for function */ + Cell **args; /* pointer to array of arguments after execute */ + Cell *retval; /* return value */ +}; + +#define NARGS 50 /* max args in a call */ + +struct Frame *frame = NULL; /* base of stack frames; dynamically allocated */ +int nframe = 0; /* number of frames allocated */ +struct Frame *fp = NULL; /* frame pointer. bottom level unused */ + +Cell *call(Node **a, int n) /* function call. very kludgy and fragile */ +{ + static Cell newcopycell = { OCELL, CCOPY, 0, "", 0.0, NUM|STR|DONTFREE }; + int i, ncall, ndef; + int freed = 0; /* handles potential double freeing when fcn & param share a tempcell */ + Node *x; + Cell *args[NARGS], *oargs[NARGS]; /* BUG: fixed size arrays */ + Cell *y, *z, *fcn; + char *s; + + fcn = execute(a[0]); /* the function itself */ + s = fcn->nval; + if (!isfcn(fcn)) + FATAL("calling undefined function %s", s); + if (frame == NULL) { + fp = frame = (struct Frame *) calloc(nframe += 100, sizeof(struct Frame)); + if (frame == NULL) + FATAL("out of space for stack frames calling %s", s); + } + for (ncall = 0, x = a[1]; x != NULL; x = x->nnext) /* args in call */ + ncall++; + ndef = (int) fcn->fval; /* args in defn */ + dprintf( ("calling %s, %d args (%d in defn), fp=%d\n", s, ncall, ndef, (int) (fp-frame)) ); + if (ncall > ndef) + WARNING("function %s called with %d args, uses only %d", + s, ncall, ndef); + if (ncall + ndef > NARGS) + FATAL("function %s has %d arguments, limit %d", s, ncall+ndef, NARGS); + for (i = 0, x = a[1]; x != NULL; i++, x = x->nnext) { /* get call args */ + dprintf( ("evaluate args[%d], fp=%d:\n", i, (int) (fp-frame)) ); + y = execute(x); + oargs[i] = y; + dprintf( ("args[%d]: %s %f <%s>, t=%o\n", + i, NN(y->nval), y->fval, isarr(y) ? "(array)" : NN(y->sval), y->tval) ); + if (isfcn(y)) + FATAL("can't use function %s as argument in %s", y->nval, s); + if (isarr(y)) + args[i] = y; /* arrays by ref */ + else + args[i] = copycell(y); + tempfree(y); + } + for ( ; i < ndef; i++) { /* add null args for ones not provided */ + args[i] = gettemp(); + *args[i] = newcopycell; + } + fp++; /* now ok to up frame */ + if (fp >= frame + nframe) { + int dfp = fp - frame; /* old index */ + frame = (struct Frame *) + realloc((char *) frame, (nframe += 100) * sizeof(struct Frame)); + if (frame == NULL) + FATAL("out of space for stack frames in %s", s); + fp = frame + dfp; + } + fp->fcncell = fcn; + fp->args = args; + fp->nargs = ndef; /* number defined with (excess are locals) */ + fp->retval = gettemp(); + + dprintf( ("start exec of %s, fp=%d\n", s, (int) (fp-frame)) ); + y = execute((Node *)(fcn->sval)); /* execute body */ + dprintf( ("finished exec of %s, fp=%d\n", s, (int) (fp-frame)) ); + + for (i = 0; i < ndef; i++) { + Cell *t = fp->args[i]; + if (isarr(t)) { + if (t->csub == CCOPY) { + if (i >= ncall) { + freesymtab(t); + t->csub = CTEMP; + tempfree(t); + } else { + oargs[i]->tval = t->tval; + oargs[i]->tval &= ~(STR|NUM|DONTFREE); + oargs[i]->sval = t->sval; + tempfree(t); + } + } + } else if (t != y) { /* kludge to prevent freeing twice */ + t->csub = CTEMP; + tempfree(t); + } else if (t == y && t->csub == CCOPY) { + t->csub = CTEMP; + tempfree(t); + freed = 1; + } + } + tempfree(fcn); + if (isexit(y) || isnext(y)) + return y; + if (freed == 0) { + tempfree(y); /* don't free twice! */ + } + z = fp->retval; /* return value */ + dprintf( ("%s returns %g |%s| %o\n", s, getfval(z), getsval(z), z->tval) ); + fp--; + return(z); +} + +Cell *copycell(Cell *x) /* make a copy of a cell in a temp */ +{ + Cell *y; + + y = gettemp(); + y->csub = CCOPY; /* prevents freeing until call is over */ + y->nval = x->nval; /* BUG? */ + if (isstr(x)) + y->sval = tostring(x->sval); + y->fval = x->fval; + y->tval = x->tval & ~(CON|FLD|REC|DONTFREE); /* copy is not constant or field */ + /* is DONTFREE right? */ + return y; +} + +Cell *arg(Node **a, int n) /* nth argument of a function */ +{ + + n = ptoi(a[0]); /* argument number, counting from 0 */ + dprintf( ("arg(%d), fp->nargs=%d\n", n, fp->nargs) ); + if (n+1 > fp->nargs) + FATAL("argument #%d of function %s was not supplied", + n+1, fp->fcncell->nval); + return fp->args[n]; +} + +Cell *jump(Node **a, int n) /* break, continue, next, nextfile, return */ +{ + Cell *y; + + switch (n) { + case EXIT: + if (a[0] != NULL) { + y = execute(a[0]); + errorflag = (int) getfval(y); + tempfree(y); + } + longjmp(env, 1); + case RETURN: + if (a[0] != NULL) { + y = execute(a[0]); + if ((y->tval & (STR|NUM)) == (STR|NUM)) { + setsval(fp->retval, getsval(y)); + fp->retval->fval = getfval(y); + fp->retval->tval |= NUM; + } + else if (y->tval & STR) + setsval(fp->retval, getsval(y)); + else if (y->tval & NUM) + setfval(fp->retval, getfval(y)); + else /* can't happen */ + FATAL("bad type variable %d", y->tval); + tempfree(y); + } + return(jret); + case NEXT: + return(jnext); + case NEXTFILE: + nextfile(); + return(jnextfile); + case BREAK: + return(jbreak); + case CONTINUE: + return(jcont); + default: /* can't happen */ + FATAL("illegal jump type %d", n); + } + return 0; /* not reached */ +} + +Cell *awkgetline(Node **a, int n) /* get next line from specific input */ +{ /* a[0] is variable, a[1] is operator, a[2] is filename */ + Cell *r, *x; + extern Cell **fldtab; + FILE *fp; + char *buf; + int bufsize = recsize; + int mode; + + if ((buf = (char *) malloc(bufsize)) == NULL) + FATAL("out of memory in getline"); + + fflush(stdout); /* in case someone is waiting for a prompt */ + r = gettemp(); + if (a[1] != NULL) { /* getline < file */ + x = execute(a[2]); /* filename */ + mode = ptoi(a[1]); + if (mode == '|') /* input pipe */ + mode = LE; /* arbitrary flag */ + fp = openfile(mode, getsval(x)); + tempfree(x); + if (fp == NULL) + n = -1; + else + n = readrec(&buf, &bufsize, fp); + if (n <= 0) { + ; + } else if (a[0] != NULL) { /* getline var <file */ + x = execute(a[0]); + setsval(x, buf); + tempfree(x); + } else { /* getline <file */ + setsval(fldtab[0], buf); + if (is_number(fldtab[0]->sval)) { + fldtab[0]->fval = atof(fldtab[0]->sval); + fldtab[0]->tval |= NUM; + } + } + } else { /* bare getline; use current input */ + if (a[0] == NULL) /* getline */ + n = getrec(&record, &recsize, 1); + else { /* getline var */ + n = getrec(&buf, &bufsize, 0); + x = execute(a[0]); + setsval(x, buf); + tempfree(x); + } + } + setfval(r, (Awkfloat) n); + free(buf); + return r; +} + +Cell *getnf(Node **a, int n) /* get NF */ +{ + if (donefld == 0) + fldbld(); + return (Cell *) a[0]; +} + +Cell *array(Node **a, int n) /* a[0] is symtab, a[1] is list of subscripts */ +{ + Cell *x, *y, *z; + char *s; + Node *np; + char *buf; + int bufsz = recsize; + int nsub = strlen(*SUBSEP); + + if ((buf = (char *) malloc(bufsz)) == NULL) + FATAL("out of memory in array"); + + x = execute(a[0]); /* Cell* for symbol table */ + buf[0] = 0; + for (np = a[1]; np; np = np->nnext) { + y = execute(np); /* subscript */ + s = getsval(y); + if (!adjbuf(&buf, &bufsz, strlen(buf)+strlen(s)+nsub+1, recsize, 0, "array")) + FATAL("out of memory for %s[%s...]", x->nval, buf); + strlcat(buf, s, bufsz); + if (np->nnext) + strlcat(buf, *SUBSEP, bufsz); + tempfree(y); + } + if (!isarr(x)) { + dprintf( ("making %s into an array\n", NN(x->nval)) ); + if (freeable(x)) + xfree(x->sval); + x->tval &= ~(STR|NUM|DONTFREE); + x->tval |= ARR; + x->sval = (char *) makesymtab(NSYMTAB); + } + z = setsymtab(buf, "", 0.0, STR|NUM, (Array *) x->sval); + z->ctype = OCELL; + z->csub = CVAR; + tempfree(x); + free(buf); + return(z); +} + +Cell *awkdelete(Node **a, int n) /* a[0] is symtab, a[1] is list of subscripts */ +{ + Cell *x, *y; + Node *np; + char *s; + int nsub = strlen(*SUBSEP); + + x = execute(a[0]); /* Cell* for symbol table */ + if (!isarr(x)) + return True; + if (a[1] == 0) { /* delete the elements, not the table */ + freesymtab(x); + x->tval &= ~STR; + x->tval |= ARR; + x->sval = (char *) makesymtab(NSYMTAB); + } else { + int bufsz = recsize; + char *buf; + if ((buf = (char *) malloc(bufsz)) == NULL) + FATAL("out of memory in adelete"); + buf[0] = 0; + for (np = a[1]; np; np = np->nnext) { + y = execute(np); /* subscript */ + s = getsval(y); + if (!adjbuf(&buf, &bufsz, strlen(buf)+strlen(s)+nsub+1, recsize, 0, "awkdelete")) + FATAL("out of memory deleting %s[%s...]", x->nval, buf); + strlcat(buf, s, bufsz); + if (np->nnext) + strlcat(buf, *SUBSEP, bufsz); + tempfree(y); + } + freeelem(x, buf); + free(buf); + } + tempfree(x); + return True; +} + +Cell *intest(Node **a, int n) /* a[0] is index (list), a[1] is symtab */ +{ + Cell *x, *ap, *k; + Node *p; + char *buf; + char *s; + int bufsz = recsize; + int nsub = strlen(*SUBSEP); + + ap = execute(a[1]); /* array name */ + if (!isarr(ap)) { + dprintf( ("making %s into an array\n", ap->nval) ); + if (freeable(ap)) + xfree(ap->sval); + ap->tval &= ~(STR|NUM|DONTFREE); + ap->tval |= ARR; + ap->sval = (char *) makesymtab(NSYMTAB); + } + if ((buf = (char *) malloc(bufsz)) == NULL) { + FATAL("out of memory in intest"); + } + buf[0] = 0; + for (p = a[0]; p; p = p->nnext) { + x = execute(p); /* expr */ + s = getsval(x); + if (!adjbuf(&buf, &bufsz, strlen(buf)+strlen(s)+nsub+1, recsize, 0, "intest")) + FATAL("out of memory deleting %s[%s...]", x->nval, buf); + strlcat(buf, s, bufsz); + tempfree(x); + if (p->nnext) + strlcat(buf, *SUBSEP, bufsz); + } + k = lookup(buf, (Array *) ap->sval); + tempfree(ap); + free(buf); + if (k == NULL) + return(False); + else + return(True); +} + + +Cell *matchop(Node **a, int n) /* ~ and match() */ +{ + Cell *x, *y; + char *s, *t; + int i; + fa *pfa; + int (*mf)(fa *, const char *) = match, mode = 0; + + if (n == MATCHFCN) { + mf = pmatch; + mode = 1; + } + x = execute(a[1]); /* a[1] = target text */ + s = getsval(x); + if (a[0] == 0) /* a[1] == 0: already-compiled reg expr */ + i = (*mf)((fa *) a[2], s); + else { + y = execute(a[2]); /* a[2] = regular expr */ + t = getsval(y); + pfa = makedfa(t, mode); + i = (*mf)(pfa, s); + tempfree(y); + } + tempfree(x); + if (n == MATCHFCN) { + int start = patbeg - s + 1; + if (patlen < 0) + start = 0; + setfval(rstartloc, (Awkfloat) start); + setfval(rlengthloc, (Awkfloat) patlen); + x = gettemp(); + x->tval = NUM; + x->fval = start; + return x; + } else if ((n == MATCH && i == 1) || (n == NOTMATCH && i == 0)) + return(True); + else + return(False); +} + + +Cell *boolop(Node **a, int n) /* a[0] || a[1], a[0] && a[1], !a[0] */ +{ + Cell *x, *y; + int i; + + x = execute(a[0]); + i = istrue(x); + tempfree(x); + switch (n) { + case BOR: + if (i) return(True); + y = execute(a[1]); + i = istrue(y); + tempfree(y); + if (i) return(True); + else return(False); + case AND: + if ( !i ) return(False); + y = execute(a[1]); + i = istrue(y); + tempfree(y); + if (i) return(True); + else return(False); + case NOT: + if (i) return(False); + else return(True); + default: /* can't happen */ + FATAL("unknown boolean operator %d", n); + } + return 0; /*NOTREACHED*/ +} + +Cell *relop(Node **a, int n) /* a[0 < a[1], etc. */ +{ + int i; + Cell *x, *y; + Awkfloat j; + + x = execute(a[0]); + y = execute(a[1]); + if (x->tval&NUM && y->tval&NUM) { + j = x->fval - y->fval; + i = j<0? -1: (j>0? 1: 0); + } else { + i = strcmp(getsval(x), getsval(y)); + } + tempfree(x); + tempfree(y); + switch (n) { + case LT: if (i<0) return(True); + else return(False); + case LE: if (i<=0) return(True); + else return(False); + case NE: if (i!=0) return(True); + else return(False); + case EQ: if (i == 0) return(True); + else return(False); + case GE: if (i>=0) return(True); + else return(False); + case GT: if (i>0) return(True); + else return(False); + default: /* can't happen */ + FATAL("unknown relational operator %d", n); + } + return 0; /*NOTREACHED*/ +} + +void tfree(Cell *a) /* free a tempcell */ +{ + if (freeable(a)) { + dprintf( ("freeing %s %s %o\n", NN(a->nval), NN(a->sval), a->tval) ); + xfree(a->sval); + } + if (a == tmps) + FATAL("tempcell list is curdled"); + a->cnext = tmps; + tmps = a; +} + +Cell *gettemp(void) /* get a tempcell */ +{ int i; + Cell *x; + + if (!tmps) { + tmps = (Cell *) calloc(100, sizeof(Cell)); + if (!tmps) + FATAL("out of space for temporaries"); + for(i = 1; i < 100; i++) + tmps[i-1].cnext = &tmps[i]; + tmps[i-1].cnext = 0; + } + x = tmps; + tmps = x->cnext; + *x = tempcell; + return(x); +} + +Cell *indirect(Node **a, int n) /* $( a[0] ) */ +{ + Awkfloat val; + Cell *x; + int m; + char *s; + + x = execute(a[0]); + val = getfval(x); /* freebsd: defend against super large field numbers */ + if ((Awkfloat)INT_MAX < val) + FATAL("trying to access out of range field %s", x->nval); + m = (int) val; + if (m == 0 && !is_number(s = getsval(x))) /* suspicion! */ + FATAL("illegal field $(%s), name \"%s\"", s, x->nval); + /* BUG: can x->nval ever be null??? */ + tempfree(x); + x = fieldadr(m); + x->ctype = OCELL; /* BUG? why are these needed? */ + x->csub = CFLD; + return(x); +} + +Cell *substr(Node **a, int nnn) /* substr(a[0], a[1], a[2]) */ +{ + int k, m, n; + char *s; + int temp; + Cell *x, *y, *z = 0; + + x = execute(a[0]); + y = execute(a[1]); + if (a[2] != 0) + z = execute(a[2]); + s = getsval(x); + k = strlen(s) + 1; + if (k <= 1) { + tempfree(x); + tempfree(y); + if (a[2] != 0) { + tempfree(z); + } + x = gettemp(); + setsval(x, ""); + return(x); + } + m = (int) getfval(y); + if (m <= 0) + m = 1; + else if (m > k) + m = k; + tempfree(y); + if (a[2] != 0) { + n = (int) getfval(z); + tempfree(z); + } else + n = k - 1; + if (n < 0) + n = 0; + else if (n > k - m) + n = k - m; + dprintf( ("substr: m=%d, n=%d, s=%s\n", m, n, s) ); + y = gettemp(); + temp = s[n+m-1]; /* with thanks to John Linderman */ + s[n+m-1] = '\0'; + setsval(y, s + m - 1); + s[n+m-1] = temp; + tempfree(x); + return(y); +} + +Cell *sindex(Node **a, int nnn) /* index(a[0], a[1]) */ +{ + Cell *x, *y, *z; + char *s1, *s2, *p1, *p2, *q; + Awkfloat v = 0.0; + + x = execute(a[0]); + s1 = getsval(x); + y = execute(a[1]); + s2 = getsval(y); + + z = gettemp(); + for (p1 = s1; *p1 != '\0'; p1++) { + for (q=p1, p2=s2; *p2 != '\0' && *q == *p2; q++, p2++) + ; + if (*p2 == '\0') { + v = (Awkfloat) (p1 - s1 + 1); /* origin 1 */ + break; + } + } + tempfree(x); + tempfree(y); + setfval(z, v); + return(z); +} + +#define MAXNUMSIZE 50 + +int format(char **pbuf, int *pbufsize, const char *s, Node *a) /* printf-like conversions */ +{ + char *fmt; + char *p, *t; + const char *os; + Cell *x; + int flag = 0, n; + int fmtwd; /* format width */ + int fmtsz = recsize; + char *buf = *pbuf; + int bufsize = *pbufsize; + + os = s; + p = buf; + if ((fmt = (char *) malloc(fmtsz)) == NULL) + FATAL("out of memory in format()"); + while (*s) { + adjbuf(&buf, &bufsize, MAXNUMSIZE+1+p-buf, recsize, &p, "format1"); + if (*s != '%') { + *p++ = *s++; + continue; + } + if (*(s+1) == '%') { + *p++ = '%'; + s += 2; + continue; + } + /* have to be real careful in case this is a huge number, eg, %100000d */ + fmtwd = atoi(s+1); + if (fmtwd < 0) + fmtwd = -fmtwd; + adjbuf(&buf, &bufsize, fmtwd+1+p-buf, recsize, &p, "format2"); + for (t = fmt; (*t++ = *s) != '\0'; s++) { + if (!adjbuf(&fmt, &fmtsz, MAXNUMSIZE+1+t-fmt, recsize, &t, "format3")) + FATAL("format item %.30s... ran format() out of memory", os); + if (isalpha((uschar)*s) && *s != 'l' && *s != 'h' && *s != 'L') + break; /* the ansi panoply */ + if (*s == '*') { + if (a == NULL) + FATAL("not enough args in printf(%s)", os); + x = execute(a); + a = a->nnext; + snprintf(t-1, fmt + fmtsz - (t-1), "%d", fmtwd=(int) getfval(x)); + if (fmtwd < 0) + fmtwd = -fmtwd; + adjbuf(&buf, &bufsize, fmtwd+1+p-buf, recsize, &p, "format"); + t = fmt + strlen(fmt); + tempfree(x); + } + } + *t = '\0'; + if (fmtwd < 0) + fmtwd = -fmtwd; + adjbuf(&buf, &bufsize, fmtwd+1+p-buf, recsize, &p, "format4"); + + switch (*s) { + case 'f': case 'e': case 'g': case 'E': case 'G': + flag = 'f'; + break; + case 'd': case 'i': + flag = 'd'; + if(*(s-1) == 'l') break; + *(t-1) = 'l'; + *t = 'd'; + *++t = '\0'; + break; + case 'o': case 'x': case 'X': case 'u': + flag = *(s-1) == 'l' ? 'd' : 'u'; + break; + case 's': + flag = 's'; + break; + case 'c': + flag = 'c'; + break; + default: + WARNING("weird printf conversion %s", fmt); + flag = '?'; + break; + } + if (a == NULL) + FATAL("not enough args in printf(%s)", os); + x = execute(a); + a = a->nnext; + n = MAXNUMSIZE; + if (fmtwd > n) + n = fmtwd; + adjbuf(&buf, &bufsize, 1+n+p-buf, recsize, &p, "format5"); + switch (flag) { + case '?': /* unknown, so dump it too */ + snprintf(p, buf + bufsize - p, "%s", fmt); + t = getsval(x); + n = strlen(t); + if (fmtwd > n) + n = fmtwd; + adjbuf(&buf, &bufsize, 1+strlen(p)+n+p-buf, recsize, &p, "format6"); + p += strlen(p); + snprintf(p, buf + bufsize - p, "%s", t); + break; + case 'f': snprintf(p, buf + bufsize - p, fmt, getfval(x)); break; + case 'd': snprintf(p, buf + bufsize - p, fmt, (long) getfval(x)); break; + case 'u': snprintf(p, buf + bufsize - p, fmt, (int) getfval(x)); break; + case 's': + t = getsval(x); + n = strlen(t); + if (fmtwd > n) + n = fmtwd; + if (!adjbuf(&buf, &bufsize, 1+n+p-buf, recsize, &p, "format7")) + FATAL("huge string/format (%d chars) in printf %.30s... ran format() out of memory", n, t); + snprintf(p, buf + bufsize - p, fmt, t); + break; + case 'c': + if (isnum(x)) { + if (getfval(x)) + snprintf(p, buf + bufsize - p, fmt, (int) getfval(x)); + else { + *p++ = '\0'; /* explicit null byte */ + *p = '\0'; /* next output will start here */ + } + } else + snprintf(p, buf + bufsize - p, fmt, getsval(x)[0]); + break; + default: + FATAL("can't happen: bad conversion %c in format()", flag); + } + tempfree(x); + p += strlen(p); + s++; + } + *p = '\0'; + free(fmt); + for ( ; a; a = a->nnext) /* evaluate any remaining args */ + execute(a); + *pbuf = buf; + *pbufsize = bufsize; + return p - buf; +} + +Cell *awksprintf(Node **a, int n) /* sprintf(a[0]) */ +{ + Cell *x; + Node *y; + char *buf; + int bufsz=3*recsize; + + if ((buf = (char *) malloc(bufsz)) == NULL) + FATAL("out of memory in awksprintf"); + y = a[0]->nnext; + x = execute(a[0]); + if (format(&buf, &bufsz, getsval(x), y) == -1) + FATAL("sprintf string %.30s... too long. can't happen.", buf); + tempfree(x); + x = gettemp(); + x->sval = buf; + x->tval = STR; + return(x); +} + +Cell *awkprintf(Node **a, int n) /* printf */ +{ /* a[0] is list of args, starting with format string */ + /* a[1] is redirection operator, a[2] is redirection file */ + FILE *fp; + Cell *x; + Node *y; + char *buf; + int len; + int bufsz=3*recsize; + + if ((buf = (char *) malloc(bufsz)) == NULL) + FATAL("out of memory in awkprintf"); + y = a[0]->nnext; + x = execute(a[0]); + if ((len = format(&buf, &bufsz, getsval(x), y)) == -1) + FATAL("printf string %.30s... too long. can't happen.", buf); + tempfree(x); + if (a[1] == NULL) { + /* fputs(buf, stdout); */ + fwrite(buf, len, 1, stdout); + if (ferror(stdout)) + FATAL("write error on stdout"); + } else { + fp = redirect(ptoi(a[1]), a[2]); + /* fputs(buf, fp); */ + fwrite(buf, len, 1, fp); + fflush(fp); + if (ferror(fp)) + FATAL("write error on %s", filename(fp)); + } + free(buf); + return(True); +} + +Cell *arith(Node **a, int n) /* a[0] + a[1], etc. also -a[0] */ +{ + Awkfloat i, j = 0; + double v; + Cell *x, *y, *z; + + x = execute(a[0]); + i = getfval(x); + tempfree(x); + if (n != UMINUS) { + y = execute(a[1]); + j = getfval(y); + tempfree(y); + } + z = gettemp(); + switch (n) { + case ADD: + i += j; + break; + case MINUS: + i -= j; + break; + case MULT: + i *= j; + break; + case DIVIDE: + if (j == 0) + FATAL("division by zero"); + i /= j; + break; + case MOD: + if (j == 0) + FATAL("division by zero in mod"); + modf(i/j, &v); + i = i - j * v; + break; + case UMINUS: + i = -i; + break; + case POWER: + if (j >= 0 && modf(j, &v) == 0.0) /* pos integer exponent */ + i = ipow(i, (int) j); + else + i = errcheck(pow(i, j), "pow"); + break; + default: /* can't happen */ + FATAL("illegal arithmetic operator %d", n); + } + setfval(z, i); + return(z); +} + +double ipow(double x, int n) /* x**n. ought to be done by pow, but isn't always */ +{ + double v; + + if (n <= 0) + return 1; + v = ipow(x, n/2); + if (n % 2 == 0) + return v * v; + else + return x * v * v; +} + +Cell *incrdecr(Node **a, int n) /* a[0]++, etc. */ +{ + Cell *x, *z; + int k; + Awkfloat xf; + + x = execute(a[0]); + xf = getfval(x); + k = (n == PREINCR || n == POSTINCR) ? 1 : -1; + if (n == PREINCR || n == PREDECR) { + setfval(x, xf + k); + return(x); + } + z = gettemp(); + setfval(z, xf); + setfval(x, xf + k); + tempfree(x); + return(z); +} + +Cell *assign(Node **a, int n) /* a[0] = a[1], a[0] += a[1], etc. */ +{ /* this is subtle; don't muck with it. */ + Cell *x, *y; + Awkfloat xf, yf; + double v; + + y = execute(a[1]); + x = execute(a[0]); + if (n == ASSIGN) { /* ordinary assignment */ + if (x == y && !(x->tval & (FLD|REC))) /* self-assignment: */ + ; /* leave alone unless it's a field */ + else if ((y->tval & (STR|NUM)) == (STR|NUM)) { + setsval(x, getsval(y)); + x->fval = getfval(y); + x->tval |= NUM; + } + else if (isstr(y)) + setsval(x, getsval(y)); + else if (isnum(y)) + setfval(x, getfval(y)); + else + funnyvar(y, "read value of"); + tempfree(y); + return(x); + } + xf = getfval(x); + yf = getfval(y); + switch (n) { + case ADDEQ: + xf += yf; + break; + case SUBEQ: + xf -= yf; + break; + case MULTEQ: + xf *= yf; + break; + case DIVEQ: + if (yf == 0) + FATAL("division by zero in /="); + xf /= yf; + break; + case MODEQ: + if (yf == 0) + FATAL("division by zero in %%="); + modf(xf/yf, &v); + xf = xf - yf * v; + break; + case POWEQ: + if (yf >= 0 && modf(yf, &v) == 0.0) /* pos integer exponent */ + xf = ipow(xf, (int) yf); + else + xf = errcheck(pow(xf, yf), "pow"); + break; + default: + FATAL("illegal assignment operator %d", n); + break; + } + tempfree(y); + setfval(x, xf); + return(x); +} + +Cell *cat(Node **a, int q) /* a[0] cat a[1] */ +{ + Cell *x, *y, *z; + int n1, n2; + char *s; + size_t len; + + x = execute(a[0]); + y = execute(a[1]); + getsval(x); + getsval(y); + n1 = strlen(x->sval); + n2 = strlen(y->sval); + len = n1 + n2 + 1; + s = (char *) malloc(len); + if (s == NULL) + FATAL("out of space concatenating %.15s... and %.15s...", + x->sval, y->sval); + strlcpy(s, x->sval, len); + strlcpy(s+n1, y->sval, len - n1); + tempfree(x); + tempfree(y); + z = gettemp(); + z->sval = s; + z->tval = STR; + return(z); +} + +Cell *pastat(Node **a, int n) /* a[0] { a[1] } */ +{ + Cell *x; + + if (a[0] == 0) + x = execute(a[1]); + else { + x = execute(a[0]); + if (istrue(x)) { + tempfree(x); + x = execute(a[1]); + } + } + return x; +} + +Cell *dopa2(Node **a, int n) /* a[0], a[1] { a[2] } */ +{ + Cell *x; + int pair; + + pair = ptoi(a[3]); + if (pairstack[pair] == 0) { + x = execute(a[0]); + if (istrue(x)) + pairstack[pair] = 1; + tempfree(x); + } + if (pairstack[pair] == 1) { + x = execute(a[1]); + if (istrue(x)) + pairstack[pair] = 0; + tempfree(x); + x = execute(a[2]); + return(x); + } + return(False); +} + +Cell *split(Node **a, int nnn) /* split(a[0], a[1], a[2]); a[3] is type */ +{ + Cell *x = 0, *y, *ap; + char *s; + int sep; + char *t, temp, num[50], *fs = 0; + int n, tempstat, arg3type; + + y = execute(a[0]); /* source string */ + s = getsval(y); + arg3type = ptoi(a[3]); + if (a[2] == 0) /* fs string */ + fs = *FS; + else if (arg3type == STRING) { /* split(str,arr,"string") */ + x = execute(a[2]); + fs = getsval(x); + } else if (arg3type == REGEXPR) + fs = "(regexpr)"; /* split(str,arr,/regexpr/) */ + else + FATAL("illegal type of split"); + sep = *fs; + ap = execute(a[1]); /* array name */ + freesymtab(ap); + dprintf( ("split: s=|%s|, a=%s, sep=|%s|\n", s, NN(ap->nval), fs) ); + ap->tval &= ~STR; + ap->tval |= ARR; + ap->sval = (char *) makesymtab(NSYMTAB); + + n = 0; + if (arg3type == REGEXPR && strlen((char*)((fa*)a[2])->restr) == 0) { + /* split(s, a, //); have to arrange that it looks like empty sep */ + arg3type = 0; + fs = ""; + sep = 0; + } + if (*s != '\0' && (strlen(fs) > 1 || arg3type == REGEXPR)) { /* reg expr */ + fa *pfa; + if (arg3type == REGEXPR) { /* it's ready already */ + pfa = (fa *) a[2]; + } else { + pfa = makedfa(fs, 1); + } + if (nematch(pfa,s)) { + tempstat = pfa->initstat; + pfa->initstat = 2; + do { + n++; + snprintf(num, sizeof num, "%d", n); + temp = *patbeg; + *patbeg = '\0'; + if (is_number(s)) + setsymtab(num, s, atof(s), STR|NUM, (Array *) ap->sval); + else + setsymtab(num, s, 0.0, STR, (Array *) ap->sval); + *patbeg = temp; + s = patbeg + patlen; + if (*(patbeg+patlen-1) == 0 || *s == 0) { + n++; + snprintf(num, sizeof num, "%d", n); + setsymtab(num, "", 0.0, STR, (Array *) ap->sval); + pfa->initstat = tempstat; + goto spdone; + } + } while (nematch(pfa,s)); + pfa->initstat = tempstat; /* bwk: has to be here to reset */ + /* cf gsub and refldbld */ + } + n++; + snprintf(num, sizeof num, "%d", n); + if (is_number(s)) + setsymtab(num, s, atof(s), STR|NUM, (Array *) ap->sval); + else + setsymtab(num, s, 0.0, STR, (Array *) ap->sval); + spdone: + pfa = NULL; + } else if (sep == ' ') { + for (n = 0; ; ) { + while (*s == ' ' || *s == '\t' || *s == '\n') + s++; + if (*s == 0) + break; + n++; + t = s; + do + s++; + while (*s!=' ' && *s!='\t' && *s!='\n' && *s!='\0'); + temp = *s; + *s = '\0'; + snprintf(num, sizeof num, "%d", n); + if (is_number(t)) + setsymtab(num, t, atof(t), STR|NUM, (Array *) ap->sval); + else + setsymtab(num, t, 0.0, STR, (Array *) ap->sval); + *s = temp; + if (*s != 0) + s++; + } + } else if (sep == 0) { /* new: split(s, a, "") => 1 char/elem */ + for (n = 0; *s != 0; s++) { + char buf[2]; + n++; + snprintf(num, sizeof num, "%d", n); + buf[0] = *s; + buf[1] = 0; + if (isdigit((uschar)buf[0])) + setsymtab(num, buf, atof(buf), STR|NUM, (Array *) ap->sval); + else + setsymtab(num, buf, 0.0, STR, (Array *) ap->sval); + } + } else if (*s != 0) { + for (;;) { + n++; + t = s; + while (*s != sep && *s != '\n' && *s != '\0') + s++; + temp = *s; + *s = '\0'; + snprintf(num, sizeof num, "%d", n); + if (is_number(t)) + setsymtab(num, t, atof(t), STR|NUM, (Array *) ap->sval); + else + setsymtab(num, t, 0.0, STR, (Array *) ap->sval); + *s = temp; + if (*s++ == 0) + break; + } + } + tempfree(ap); + tempfree(y); + if (a[2] != 0 && arg3type == STRING) { + tempfree(x); + } + x = gettemp(); + x->tval = NUM; + x->fval = n; + return(x); +} + +Cell *condexpr(Node **a, int n) /* a[0] ? a[1] : a[2] */ +{ + Cell *x; + + x = execute(a[0]); + if (istrue(x)) { + tempfree(x); + x = execute(a[1]); + } else { + tempfree(x); + x = execute(a[2]); + } + return(x); +} + +Cell *ifstat(Node **a, int n) /* if (a[0]) a[1]; else a[2] */ +{ + Cell *x; + + x = execute(a[0]); + if (istrue(x)) { + tempfree(x); + x = execute(a[1]); + } else if (a[2] != 0) { + tempfree(x); + x = execute(a[2]); + } + return(x); +} + +Cell *whilestat(Node **a, int n) /* while (a[0]) a[1] */ +{ + Cell *x; + + for (;;) { + x = execute(a[0]); + if (!istrue(x)) + return(x); + tempfree(x); + x = execute(a[1]); + if (isbreak(x)) { + x = True; + return(x); + } + if (isnext(x) || isexit(x) || isret(x)) + return(x); + tempfree(x); + } +} + +Cell *dostat(Node **a, int n) /* do a[0]; while(a[1]) */ +{ + Cell *x; + + for (;;) { + x = execute(a[0]); + if (isbreak(x)) + return True; + if (isnext(x) || isexit(x) || isret(x)) + return(x); + tempfree(x); + x = execute(a[1]); + if (!istrue(x)) + return(x); + tempfree(x); + } +} + +Cell *forstat(Node **a, int n) /* for (a[0]; a[1]; a[2]) a[3] */ +{ + Cell *x; + + x = execute(a[0]); + tempfree(x); + for (;;) { + if (a[1]!=0) { + x = execute(a[1]); + if (!istrue(x)) return(x); + else tempfree(x); + } + x = execute(a[3]); + if (isbreak(x)) /* turn off break */ + return True; + if (isnext(x) || isexit(x) || isret(x)) + return(x); + tempfree(x); + x = execute(a[2]); + tempfree(x); + } +} + +Cell *instat(Node **a, int n) /* for (a[0] in a[1]) a[2] */ +{ + Cell *x, *vp, *arrayp, *cp, *ncp; + Array *tp; + int i; + + vp = execute(a[0]); + arrayp = execute(a[1]); + if (!isarr(arrayp)) { + return True; + } + tp = (Array *) arrayp->sval; + tempfree(arrayp); + for (i = 0; i < tp->size; i++) { /* this routine knows too much */ + for (cp = tp->tab[i]; cp != NULL; cp = ncp) { + setsval(vp, cp->nval); + ncp = cp->cnext; + x = execute(a[2]); + if (isbreak(x)) { + tempfree(vp); + return True; + } + if (isnext(x) || isexit(x) || isret(x)) { + tempfree(vp); + return(x); + } + tempfree(x); + } + } + return True; +} + +Cell *bltin(Node **a, int n) /* builtin functions. a[0] is type, a[1] is arg list */ +{ + Cell *x, *y; + Awkfloat u; + int t; + Awkfloat tmp; + char *p, *buf; + Node *nextarg; + FILE *fp; + + t = ptoi(a[0]); + x = execute(a[1]); + nextarg = a[1]->nnext; + switch (t) { + case FLENGTH: + if (isarr(x)) + u = ((Array *) x->sval)->nelem; /* GROT. should be function*/ + else + u = strlen(getsval(x)); + break; + case FLOG: + u = errcheck(log(getfval(x)), "log"); break; + case FINT: + modf(getfval(x), &u); break; + case FEXP: + u = errcheck(exp(getfval(x)), "exp"); break; + case FSQRT: + u = errcheck(sqrt(getfval(x)), "sqrt"); break; + case FSIN: + u = sin(getfval(x)); break; + case FCOS: + u = cos(getfval(x)); break; + case FATAN: + if (nextarg == 0) { + WARNING("atan2 requires two arguments; returning 1.0"); + u = 1.0; + } else { + y = execute(a[1]->nnext); + u = atan2(getfval(x), getfval(y)); + tempfree(y); + nextarg = nextarg->nnext; + } + break; + case FCOMPL: + u = ~((int)getfval(x)); + break; + case FAND: + if (nextarg == 0) { + WARNING("and requires two arguments; returning 0"); + u = 0; + break; + } + y = execute(a[1]->nnext); + u = ((int)getfval(x)) & ((int)getfval(y)); + tempfree(y); + nextarg = nextarg->nnext; + break; + case FFOR: + if (nextarg == 0) { + WARNING("or requires two arguments; returning 0"); + u = 0; + break; + } + y = execute(a[1]->nnext); + u = ((int)getfval(x)) | ((int)getfval(y)); + tempfree(y); + nextarg = nextarg->nnext; + break; + case FXOR: + if (nextarg == 0) { + WARNING("or requires two arguments; returning 0"); + u = 0; + break; + } + y = execute(a[1]->nnext); + u = ((int)getfval(x)) ^ ((int)getfval(y)); + tempfree(y); + nextarg = nextarg->nnext; + break; + case FLSHIFT: + if (nextarg == 0) { + WARNING("or requires two arguments; returning 0"); + u = 0; + break; + } + y = execute(a[1]->nnext); + u = ((int)getfval(x)) << ((int)getfval(y)); + tempfree(y); + nextarg = nextarg->nnext; + break; + case FRSHIFT: + if (nextarg == 0) { + WARNING("or requires two arguments; returning 0"); + u = 0; + break; + } + y = execute(a[1]->nnext); + u = ((int)getfval(x)) >> ((int)getfval(y)); + tempfree(y); + nextarg = nextarg->nnext; + break; + case FSYSTEM: + fflush(stdout); /* in case something is buffered already */ + u = (Awkfloat) system(getsval(x)) / 256; /* 256 is unix-dep */ + break; + case FRAND: + if (use_srandom) + u = (Awkfloat) (random() % RAND_MAX) / RAND_MAX; + else + u = (Awkfloat)arc4random() / 0xffffffff; + break; + case FSRAND: + if (isrec(x)) /* no argument provided, want arc4random() */ + use_srandom = 0; + else { + use_srandom = 1; + u = getfval(x); + tmp = u; + srandom((unsigned int) u); + u = srand_seed; + srand_seed = tmp; + } + break; + case FTOUPPER: + case FTOLOWER: + buf = tostring(getsval(x)); + if (t == FTOUPPER) { + for (p = buf; *p; p++) + if (islower((uschar) *p)) + *p = toupper((uschar)*p); + } else { + for (p = buf; *p; p++) + if (isupper((uschar) *p)) + *p = tolower((uschar)*p); + } + tempfree(x); + x = gettemp(); + setsval(x, buf); + free(buf); + return x; + case FFLUSH: + if (isrec(x) || strlen(getsval(x)) == 0) { + flush_all(); /* fflush() or fflush("") -> all */ + u = 0; + } else if ((fp = openfile(FFLUSH, getsval(x))) == NULL) + u = EOF; + else + u = fflush(fp); + break; + default: /* can't happen */ + FATAL("illegal function type %d", t); + break; + } + tempfree(x); + x = gettemp(); + setfval(x, u); + if (nextarg != 0) { + WARNING("warning: function has too many arguments"); + for ( ; nextarg; nextarg = nextarg->nnext) + execute(nextarg); + } + return(x); +} + +Cell *printstat(Node **a, int n) /* print a[0] */ +{ + Node *x; + Cell *y; + FILE *fp; + + if (a[1] == 0) /* a[1] is redirection operator, a[2] is file */ + fp = stdout; + else + fp = redirect(ptoi(a[1]), a[2]); + for (x = a[0]; x != NULL; x = x->nnext) { + y = execute(x); + fputs(getpssval(y), fp); + tempfree(y); + if (x->nnext == NULL) + fputs(*ORS, fp); + else + fputs(*OFS, fp); + } + if (a[1] != 0) + fflush(fp); + if (ferror(fp)) + FATAL("write error on %s", filename(fp)); + return(True); +} + +Cell *nullproc(Node **a, int n) +{ + n = n; + a = a; + return 0; +} + + +FILE *redirect(int a, Node *b) /* set up all i/o redirections */ +{ + FILE *fp; + Cell *x; + char *fname; + + x = execute(b); + fname = getsval(x); + fp = openfile(a, fname); + if (fp == NULL) + FATAL("can't open file %s", fname); + tempfree(x); + return fp; +} + +struct files { + FILE *fp; + const char *fname; + int mode; /* '|', 'a', 'w' => LE/LT, GT */ +} *files; + +int nfiles; + +void stdinit(void) /* in case stdin, etc., are not constants */ +{ + nfiles = FOPEN_MAX; + files = calloc(nfiles, sizeof(*files)); + if (files == NULL) + FATAL("can't allocate file memory for %u files", nfiles); + files[0].fp = stdin; + files[0].fname = "/dev/stdin"; + files[0].mode = LT; + files[1].fp = stdout; + files[1].fname = "/dev/stdout"; + files[1].mode = GT; + files[2].fp = stderr; + files[2].fname = "/dev/stderr"; + files[2].mode = GT; +} + +FILE *openfile(int a, const char *us) +{ + const char *s = us; + int i, m; + FILE *fp = 0; + + if (*s == '\0') + FATAL("null file name in print or getline"); + for (i=0; i < nfiles; i++) + if (files[i].fname && strcmp(s, files[i].fname) == 0) { + if (a == files[i].mode || (a==APPEND && files[i].mode==GT)) + return files[i].fp; + if (a == FFLUSH) + return files[i].fp; + } + if (a == FFLUSH) /* didn't find it, so don't create it! */ + return NULL; + + for (i=0; i < nfiles; i++) + if (files[i].fp == 0) + break; + if (i >= nfiles) { + struct files *nf; + int nnf = nfiles + FOPEN_MAX; + nf = reallocarray(files, nnf, sizeof(*nf)); + if (nf == NULL) + FATAL("cannot grow files for %s and %d files", s, nnf); + memset(&nf[nfiles], 0, FOPEN_MAX * sizeof(*nf)); + nfiles = nnf; + files = nf; + } + fflush(stdout); /* force a semblance of order */ + m = a; + if (a == GT) { + fp = fopen(s, "w"); + } else if (a == APPEND) { + fp = fopen(s, "a"); + m = GT; /* so can mix > and >> */ + } else if (a == '|') { /* output pipe */ + fp = popen(s, "w"); + } else if (a == LE) { /* input pipe */ + fp = popen(s, "r"); + } else if (a == LT) { /* getline <file */ + fp = strcmp(s, "-") == 0 ? stdin : fopen(s, "r"); /* "-" is stdin */ + } else /* can't happen */ + FATAL("illegal redirection %d", a); + if (fp != NULL) { + files[i].fname = tostring(s); + files[i].fp = fp; + files[i].mode = m; + } + return fp; +} + +const char *filename(FILE *fp) +{ + int i; + + for (i = 0; i < nfiles; i++) + if (fp == files[i].fp) + return files[i].fname; + return "???"; +} + +Cell *closefile(Node **a, int n) +{ + Cell *x; + int i, stat; + + n = n; + x = execute(a[0]); + getsval(x); + stat = -1; + for (i = 0; i < nfiles; i++) { + if (files[i].fname && strcmp(x->sval, files[i].fname) == 0) { + if (ferror(files[i].fp)) + WARNING( "i/o error occurred on %s", files[i].fname ); + if (files[i].mode == '|' || files[i].mode == LE) + stat = pclose(files[i].fp); + else + stat = fclose(files[i].fp); + if (stat == EOF) + WARNING( "i/o error occurred closing %s", files[i].fname ); + if (i > 2) /* don't do /dev/std... */ + xfree(files[i].fname); + files[i].fname = NULL; /* watch out for ref thru this */ + files[i].fp = NULL; + } + } + tempfree(x); + x = gettemp(); + setfval(x, (Awkfloat) stat); + return(x); +} + +void closeall(void) +{ + int i, stat; + + for (i = 0; i < FOPEN_MAX; i++) { + if (files[i].fp) { + if (ferror(files[i].fp)) + WARNING( "i/o error occurred on %s", files[i].fname ); + if (files[i].mode == '|' || files[i].mode == LE) + stat = pclose(files[i].fp); + else + stat = fclose(files[i].fp); + if (stat == EOF) + WARNING( "i/o error occurred while closing %s", files[i].fname ); + } + } +} + +void flush_all(void) +{ + int i; + + for (i = 0; i < nfiles; i++) + if (files[i].fp) + fflush(files[i].fp); +} + +void backsub(char **pb_ptr, char **sptr_ptr); + +Cell *sub(Node **a, int nnn) /* substitute command */ +{ + char *sptr, *pb, *q; + Cell *x, *y, *result; + char *t, *buf; + fa *pfa; + int bufsz = recsize; + + if ((buf = (char *) malloc(bufsz)) == NULL) + FATAL("out of memory in sub"); + x = execute(a[3]); /* target string */ + t = getsval(x); + if (a[0] == 0) /* 0 => a[1] is already-compiled regexpr */ + pfa = (fa *) a[1]; /* regular expression */ + else { + y = execute(a[1]); + pfa = makedfa(getsval(y), 1); + tempfree(y); + } + y = execute(a[2]); /* replacement string */ + result = False; + if (pmatch(pfa, t)) { + sptr = t; + adjbuf(&buf, &bufsz, 1+patbeg-sptr, recsize, 0, "sub"); + pb = buf; + while (sptr < patbeg) + *pb++ = *sptr++; + sptr = getsval(y); + while (*sptr != 0) { + adjbuf(&buf, &bufsz, 5+pb-buf, recsize, &pb, "sub"); + if (*sptr == '\\') { + backsub(&pb, &sptr); + } else if (*sptr == '&') { + sptr++; + adjbuf(&buf, &bufsz, 1+patlen+pb-buf, recsize, &pb, "sub"); + for (q = patbeg; q < patbeg+patlen; ) + *pb++ = *q++; + } else + *pb++ = *sptr++; + } + *pb = '\0'; + if (pb > buf + bufsz) + FATAL("sub result1 %.30s too big; can't happen", buf); + sptr = patbeg + patlen; + if ((patlen == 0 && *patbeg) || (patlen && *(sptr-1))) { + adjbuf(&buf, &bufsz, 1+strlen(sptr)+pb-buf, 0, &pb, "sub"); + while ((*pb++ = *sptr++) != 0) + ; + } + if (pb > buf + bufsz) + FATAL("sub result2 %.30s too big; can't happen", buf); + setsval(x, buf); /* BUG: should be able to avoid copy */ + result = True; + } + tempfree(x); + tempfree(y); + free(buf); + return result; +} + +Cell *gsub(Node **a, int nnn) /* global substitute */ +{ + Cell *x, *y; + char *rptr, *sptr, *t, *pb, *q; + char *buf; + fa *pfa; + int mflag, tempstat, num; + int bufsz = recsize; + + if ((buf = (char *) malloc(bufsz)) == NULL) + FATAL("out of memory in gsub"); + mflag = 0; /* if mflag == 0, can replace empty string */ + num = 0; + x = execute(a[3]); /* target string */ + t = getsval(x); + if (a[0] == 0) /* 0 => a[1] is already-compiled regexpr */ + pfa = (fa *) a[1]; /* regular expression */ + else { + y = execute(a[1]); + pfa = makedfa(getsval(y), 1); + tempfree(y); + } + y = execute(a[2]); /* replacement string */ + if (pmatch(pfa, t)) { + tempstat = pfa->initstat; + pfa->initstat = 2; + pb = buf; + rptr = getsval(y); + do { + if (patlen == 0 && *patbeg != 0) { /* matched empty string */ + if (mflag == 0) { /* can replace empty */ + num++; + sptr = rptr; + while (*sptr != 0) { + adjbuf(&buf, &bufsz, 5+pb-buf, recsize, &pb, "gsub"); + if (*sptr == '\\') { + backsub(&pb, &sptr); + } else if (*sptr == '&') { + sptr++; + adjbuf(&buf, &bufsz, 1+patlen+pb-buf, recsize, &pb, "gsub"); + for (q = patbeg; q < patbeg+patlen; ) + *pb++ = *q++; + } else + *pb++ = *sptr++; + } + } + if (*t == 0) /* at end */ + goto done; + adjbuf(&buf, &bufsz, 2+pb-buf, recsize, &pb, "gsub"); + *pb++ = *t++; + if (pb > buf + bufsz) /* BUG: not sure of this test */ + FATAL("gsub result0 %.30s too big; can't happen", buf); + mflag = 0; + } + else { /* matched nonempty string */ + num++; + sptr = t; + adjbuf(&buf, &bufsz, 1+(patbeg-sptr)+pb-buf, recsize, &pb, "gsub"); + while (sptr < patbeg) + *pb++ = *sptr++; + sptr = rptr; + while (*sptr != 0) { + adjbuf(&buf, &bufsz, 5+pb-buf, recsize, &pb, "gsub"); + if (*sptr == '\\') { + backsub(&pb, &sptr); + } else if (*sptr == '&') { + sptr++; + adjbuf(&buf, &bufsz, 1+patlen+pb-buf, recsize, &pb, "gsub"); + for (q = patbeg; q < patbeg+patlen; ) + *pb++ = *q++; + } else + *pb++ = *sptr++; + } + t = patbeg + patlen; + if (patlen == 0 || *t == 0 || *(t-1) == 0) + goto done; + if (pb > buf + bufsz) + FATAL("gsub result1 %.30s too big; can't happen", buf); + mflag = 1; + } + } while (pmatch(pfa,t)); + sptr = t; + adjbuf(&buf, &bufsz, 1+strlen(sptr)+pb-buf, 0, &pb, "gsub"); + while ((*pb++ = *sptr++) != 0) + ; + done: if (pb < buf + bufsz) + *pb = '\0'; + else if (*(pb-1) != '\0') + FATAL("gsub result2 %.30s truncated; can't happen", buf); + setsval(x, buf); /* BUG: should be able to avoid copy + free */ + pfa->initstat = tempstat; + } + tempfree(x); + tempfree(y); + x = gettemp(); + x->tval = NUM; + x->fval = num; + free(buf); + return(x); +} + +void backsub(char **pb_ptr, char **sptr_ptr) /* handle \\& variations */ +{ /* sptr[0] == '\\' */ + char *pb = *pb_ptr, *sptr = *sptr_ptr; + + if (sptr[1] == '\\') { + if (sptr[2] == '\\' && sptr[3] == '&') { /* \\\& -> \& */ + *pb++ = '\\'; + *pb++ = '&'; + sptr += 4; + } else if (sptr[2] == '&') { /* \\& -> \ + matched */ + *pb++ = '\\'; + sptr += 2; + } else { /* \\x -> \\x */ + *pb++ = *sptr++; + *pb++ = *sptr++; + } + } else if (sptr[1] == '&') { /* literal & */ + sptr++; + *pb++ = *sptr++; + } else /* literal \ */ + *pb++ = *sptr++; + + *pb_ptr = pb; + *sptr_ptr = sptr; +} diff --git a/awk/strlcat.c b/awk/strlcat.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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 <string.h> +#include <sys/types.h> + +#include "util.h" + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t +strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + return(dlen + (s - src)); /* count does not include NUL */ +} diff --git a/awk/strlcpy.c b/awk/strlcpy.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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 <string.h> +#include <sys/types.h> + +#include "util.h" + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + return(s - src - 1); /* count does not include NUL */ +} diff --git a/awk/tran.c b/awk/tran.c @@ -0,0 +1,458 @@ +/* $OpenBSD: tran.c,v 1.15 2011/09/28 19:27:18 millert Exp $ */ +/**************************************************************** +Copyright (C) Lucent Technologies 1997 +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 the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name Lucent Technologies or any of +its entities not be used in advertising or publicity pertaining +to distribution of the software without specific, written prior +permission. + +LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES 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. +****************************************************************/ + +#define DEBUG +#include <stdio.h> +#include <math.h> +#include <ctype.h> +#include <string.h> +#include <stdlib.h> +#include "awk.h" +#include "ytab.h" + +#define FULLTAB 2 /* rehash when table gets this x full */ +#define GROWTAB 4 /* grow table by this factor */ + +Array *symtab; /* main symbol table */ + +char **FS; /* initial field sep */ +char **RS; /* initial record sep */ +char **OFS; /* output field sep */ +char **ORS; /* output record sep */ +char **OFMT; /* output format for numbers */ +char **CONVFMT; /* format for conversions in getsval */ +Awkfloat *NF; /* number of fields in current record */ +Awkfloat *NR; /* number of current record */ +Awkfloat *FNR; /* number of current record in current file */ +char **FILENAME; /* current filename argument */ +Awkfloat *ARGC; /* number of arguments from command line */ +char **SUBSEP; /* subscript separator for a[i,j,k]; default \034 */ +Awkfloat *RSTART; /* start of re matched with ~; origin 1 (!) */ +Awkfloat *RLENGTH; /* length of same */ + +Cell *fsloc; /* FS */ +Cell *nrloc; /* NR */ +Cell *nfloc; /* NF */ +Cell *fnrloc; /* FNR */ +Array *ARGVtab; /* symbol table containing ARGV[...] */ +Array *ENVtab; /* symbol table containing ENVIRON[...] */ +Cell *rstartloc; /* RSTART */ +Cell *rlengthloc; /* RLENGTH */ +Cell *symtabloc; /* SYMTAB */ + +Cell *nullloc; /* a guaranteed empty cell */ +Node *nullnode; /* zero&null, converted into a node for comparisons */ +Cell *literal0; + +extern Cell **fldtab; + +void syminit(void) /* initialize symbol table with builtin vars */ +{ + literal0 = setsymtab("0", "0", 0.0, NUM|STR|CON|DONTFREE, symtab); + /* this is used for if(x)... tests: */ + nullloc = setsymtab("$zero&null", "", 0.0, NUM|STR|CON|DONTFREE, symtab); + nullnode = celltonode(nullloc, CCON); + + fsloc = setsymtab("FS", " ", 0.0, STR|DONTFREE, symtab); + FS = &fsloc->sval; + RS = &setsymtab("RS", "\n", 0.0, STR|DONTFREE, symtab)->sval; + OFS = &setsymtab("OFS", " ", 0.0, STR|DONTFREE, symtab)->sval; + ORS = &setsymtab("ORS", "\n", 0.0, STR|DONTFREE, symtab)->sval; + OFMT = &setsymtab("OFMT", "%.6g", 0.0, STR|DONTFREE, symtab)->sval; + CONVFMT = &setsymtab("CONVFMT", "%.6g", 0.0, STR|DONTFREE, symtab)->sval; + FILENAME = &setsymtab("FILENAME", "", 0.0, STR|DONTFREE, symtab)->sval; + nfloc = setsymtab("NF", "", 0.0, NUM, symtab); + NF = &nfloc->fval; + nrloc = setsymtab("NR", "", 0.0, NUM, symtab); + NR = &nrloc->fval; + fnrloc = setsymtab("FNR", "", 0.0, NUM, symtab); + FNR = &fnrloc->fval; + SUBSEP = &setsymtab("SUBSEP", "\034", 0.0, STR|DONTFREE, symtab)->sval; + rstartloc = setsymtab("RSTART", "", 0.0, NUM, symtab); + RSTART = &rstartloc->fval; + rlengthloc = setsymtab("RLENGTH", "", 0.0, NUM, symtab); + RLENGTH = &rlengthloc->fval; + symtabloc = setsymtab("SYMTAB", "", 0.0, ARR, symtab); + symtabloc->sval = (char *) symtab; +} + +void arginit(int ac, char **av) /* set up ARGV and ARGC */ +{ + Cell *cp; + int i; + char temp[50]; + + ARGC = &setsymtab("ARGC", "", (Awkfloat) ac, NUM, symtab)->fval; + cp = setsymtab("ARGV", "", 0.0, ARR, symtab); + ARGVtab = makesymtab(NSYMTAB); /* could be (int) ARGC as well */ + cp->sval = (char *) ARGVtab; + for (i = 0; i < ac; i++) { + snprintf(temp, sizeof temp, "%d", i); + if (is_number(*av)) + setsymtab(temp, *av, atof(*av), STR|NUM, ARGVtab); + else + setsymtab(temp, *av, 0.0, STR, ARGVtab); + av++; + } +} + +void envinit(char **envp) /* set up ENVIRON variable */ +{ + Cell *cp; + char *p; + + cp = setsymtab("ENVIRON", "", 0.0, ARR, symtab); + ENVtab = makesymtab(NSYMTAB); + cp->sval = (char *) ENVtab; + for ( ; *envp; envp++) { + if ((p = strchr(*envp, '=')) == NULL) + continue; + if( p == *envp ) /* no left hand side name in env string */ + continue; + *p++ = 0; /* split into two strings at = */ + if (is_number(p)) + setsymtab(*envp, p, atof(p), STR|NUM, ENVtab); + else + setsymtab(*envp, p, 0.0, STR, ENVtab); + p[-1] = '='; /* restore in case env is passed down to a shell */ + } +} + +Array *makesymtab(int n) /* make a new symbol table */ +{ + Array *ap; + Cell **tp; + + ap = (Array *) malloc(sizeof(Array)); + tp = (Cell **) calloc(n, sizeof(Cell *)); + if (ap == NULL || tp == NULL) + FATAL("out of space in makesymtab"); + ap->nelem = 0; + ap->size = n; + ap->tab = tp; + return(ap); +} + +void freesymtab(Cell *ap) /* free a symbol table */ +{ + Cell *cp, *temp; + Array *tp; + int i; + + if (!isarr(ap)) + return; + tp = (Array *) ap->sval; + if (tp == NULL) + return; + for (i = 0; i < tp->size; i++) { + for (cp = tp->tab[i]; cp != NULL; cp = temp) { + xfree(cp->nval); + if (freeable(cp)) + xfree(cp->sval); + temp = cp->cnext; /* avoids freeing then using */ + free(cp); + tp->nelem--; + } + tp->tab[i] = 0; + } + if (tp->nelem != 0) + WARNING("can't happen: inconsistent element count freeing %s", ap->nval); + free(tp->tab); + free(tp); +} + +void freeelem(Cell *ap, const char *s) /* free elem s from ap (i.e., ap["s"] */ +{ + Array *tp; + Cell *p, *prev = NULL; + int h; + + tp = (Array *) ap->sval; + h = hash(s, tp->size); + for (p = tp->tab[h]; p != NULL; prev = p, p = p->cnext) + if (strcmp(s, p->nval) == 0) { + if (prev == NULL) /* 1st one */ + tp->tab[h] = p->cnext; + else /* middle somewhere */ + prev->cnext = p->cnext; + if (freeable(p)) + xfree(p->sval); + free(p->nval); + free(p); + tp->nelem--; + return; + } +} + +Cell *setsymtab(const char *n, const char *s, Awkfloat f, unsigned t, Array *tp) +{ + int h; + Cell *p; + + if (n != NULL && (p = lookup(n, tp)) != NULL) { + dprintf( ("setsymtab found %p: n=%s s=\"%s\" f=%g t=%o\n", + (void*)p, NN(p->nval), NN(p->sval), p->fval, p->tval) ); + return(p); + } + p = (Cell *) malloc(sizeof(Cell)); + if (p == NULL) + FATAL("out of space for symbol table at %s", n); + p->nval = tostring(n); + p->sval = s ? tostring(s) : tostring(""); + p->fval = f; + p->tval = t; + p->csub = CUNK; + p->ctype = OCELL; + tp->nelem++; + if (tp->nelem > FULLTAB * tp->size) + rehash(tp); + h = hash(n, tp->size); + p->cnext = tp->tab[h]; + tp->tab[h] = p; + dprintf( ("setsymtab set %p: n=%s s=\"%s\" f=%g t=%o\n", + (void*)p, p->nval, p->sval, p->fval, p->tval) ); + return(p); +} + +int hash(const char *s, int n) /* form hash value for string s */ +{ + unsigned hashval; + + for (hashval = 0; *s != '\0'; s++) + hashval = (*s + 31 * hashval); + return hashval % n; +} + +void rehash(Array *tp) /* rehash items in small table into big one */ +{ + int i, nh, nsz; + Cell *cp, *op, **np; + + nsz = GROWTAB * tp->size; + np = (Cell **) calloc(nsz, sizeof(Cell *)); + if (np == NULL) /* can't do it, but can keep running. */ + return; /* someone else will run out later. */ + for (i = 0; i < tp->size; i++) { + for (cp = tp->tab[i]; cp; cp = op) { + op = cp->cnext; + nh = hash(cp->nval, nsz); + cp->cnext = np[nh]; + np[nh] = cp; + } + } + free(tp->tab); + tp->tab = np; + tp->size = nsz; +} + +Cell *lookup(const char *s, Array *tp) /* look for s in tp */ +{ + Cell *p; + int h; + + h = hash(s, tp->size); + for (p = tp->tab[h]; p != NULL; p = p->cnext) + if (strcmp(s, p->nval) == 0) + return(p); /* found it */ + return(NULL); /* not found */ +} + +Awkfloat setfval(Cell *vp, Awkfloat f) /* set float val of a Cell */ +{ + int fldno; + + if ((vp->tval & (NUM | STR)) == 0) + funnyvar(vp, "assign to"); + if (isfld(vp)) { + donerec = 0; /* mark $0 invalid */ + fldno = atoi(vp->nval); + if (fldno > *NF) + newfld(fldno); + dprintf( ("setting field %d to %g\n", fldno, f) ); + } else if (isrec(vp)) { + donefld = 0; /* mark $1... invalid */ + donerec = 1; + } + if (freeable(vp)) + xfree(vp->sval); /* free any previous string */ + vp->tval &= ~STR; /* mark string invalid */ + vp->tval |= NUM; /* mark number ok */ + dprintf( ("setfval %p: %s = %g, t=%o\n", (void*)vp, NN(vp->nval), f, vp->tval) ); + return vp->fval = f; +} + +void funnyvar(Cell *vp, const char *rw) +{ + if (isarr(vp)) + FATAL("can't %s %s; it's an array name.", rw, vp->nval); + if (vp->tval & FCN) + FATAL("can't %s %s; it's a function.", rw, vp->nval); + WARNING("funny variable %p: n=%s s=\"%s\" f=%g t=%o", + vp, vp->nval, vp->sval, vp->fval, vp->tval); +} + +char *setsval(Cell *vp, const char *s) /* set string val of a Cell */ +{ + char *t; + int fldno; + + dprintf( ("starting setsval %p: %s = \"%s\", t=%o, r,f=%d,%d\n", + (void*)vp, NN(vp->nval), s, vp->tval, donerec, donefld) ); + if ((vp->tval & (NUM | STR)) == 0) + funnyvar(vp, "assign to"); + if (isfld(vp)) { + donerec = 0; /* mark $0 invalid */ + fldno = atoi(vp->nval); + if (fldno > *NF) + newfld(fldno); + dprintf( ("setting field %d to %s (%p)\n", fldno, s, s) ); + } else if (isrec(vp)) { + donefld = 0; /* mark $1... invalid */ + donerec = 1; + } + t = tostring(s); /* in case it's self-assign */ + if (freeable(vp)) + xfree(vp->sval); + vp->tval &= ~NUM; + vp->tval |= STR; + vp->tval &= ~DONTFREE; + dprintf( ("setsval %p: %s = \"%s (%p) \", t=%o r,f=%d,%d\n", + (void*)vp, NN(vp->nval), t,t, vp->tval, donerec, donefld) ); + return(vp->sval = t); +} + +Awkfloat getfval(Cell *vp) /* get float val of a Cell */ +{ + if ((vp->tval & (NUM | STR)) == 0) + funnyvar(vp, "read value of"); + if (isfld(vp) && donefld == 0) + fldbld(); + else if (isrec(vp) && donerec == 0) + recbld(); + if (!isnum(vp)) { /* not a number */ + vp->fval = atof(vp->sval); /* best guess */ + if (is_number(vp->sval) && !(vp->tval&CON)) + vp->tval |= NUM; /* make NUM only sparingly */ + } + dprintf( ("getfval %p: %s = %g, t=%o\n", + (void*)vp, NN(vp->nval), vp->fval, vp->tval) ); + return(vp->fval); +} + +static char *get_str_val(Cell *vp, char **fmt) /* get string val of a Cell */ +{ + int n; + double dtemp; + + if ((vp->tval & (NUM | STR)) == 0) + funnyvar(vp, "read value of"); + if (isfld(vp) && donefld == 0) + fldbld(); + else if (isrec(vp) && donerec == 0) + recbld(); + if (isstr(vp) == 0) { + if (freeable(vp)) + xfree(vp->sval); + if (modf(vp->fval, &dtemp) == 0) /* it's integral */ + n = asprintf(&vp->sval, "%.30g", vp->fval); + else + n = asprintf(&vp->sval, *fmt, vp->fval); + if (n == -1) + FATAL("out of space in get_str_val"); + vp->tval &= ~DONTFREE; + vp->tval |= STR; + } + dprintf( ("getsval %p: %s = \"%s (%p)\", t=%o\n", + (void*)vp, NN(vp->nval), vp->sval, vp->sval, vp->tval) ); + return(vp->sval); +} + +char *getsval(Cell *vp) /* get string val of a Cell */ +{ + return get_str_val(vp, CONVFMT); +} + +char *getpssval(Cell *vp) /* get string val of a Cell for print */ +{ + return get_str_val(vp, OFMT); +} + + +char *tostring(const char *s) /* make a copy of string s */ +{ + char *p; + + p = strdup(s); + if (p == NULL) + FATAL("out of space in tostring on %s", s); + return p; +} + +char *qstring(const char *is, int delim) /* collect string up to next delim */ +{ + const char *os = is; + int c, n; + uschar *s = (uschar *) is; + uschar *buf, *bp; + + if ((buf = (uschar *) malloc(strlen(is)+3)) == NULL) + FATAL( "out of space in qstring(%s)", s); + for (bp = buf; (c = *s) != delim; s++) { + if (c == '\n') + SYNTAX( "newline in string %.20s...", os ); + else if (c != '\\') + *bp++ = c; + else { /* \something */ + c = *++s; + if (c == 0) { /* \ at end */ + *bp++ = '\\'; + break; /* for loop */ + } + switch (c) { + case '\\': *bp++ = '\\'; break; + case 'n': *bp++ = '\n'; break; + case 't': *bp++ = '\t'; break; + case 'b': *bp++ = '\b'; break; + case 'f': *bp++ = '\f'; break; + case 'r': *bp++ = '\r'; break; + default: + if (!isdigit(c)) { + *bp++ = c; + break; + } + n = c - '0'; + if (isdigit(s[1])) { + n = 8 * n + *++s - '0'; + if (isdigit(s[1])) + n = 8 * n + *++s - '0'; + } + *bp++ = n; + break; + } + } + } + *bp++ = 0; + return (char *) buf; +} diff --git a/awk/util.h b/awk/util.h @@ -0,0 +1,10 @@ +#include <sys/stat.h> + +#include <stdint.h> + +uint32_t arc4random(void); + +void *reallocarray(void *, size_t, size_t); + +size_t strlcat(char *, const char *, size_t); +size_t strlcpy(char *, const char *, size_t); diff --git a/sbase/LICENSE b/sbase/LICENSE @@ -0,0 +1,57 @@ +MIT/X Consortium License + +© 2011 Connor Lane Smith <cls@lubutu.com> +© 2011-2015 Dimitris Papastamos <sin@2f30.org> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +Authors/contributors include: + +© 2011 Kamil Cholewiński <harry666t@gmail.com> +© 2011 Rob Pilling <robpilling@gmail.com> +© 2011 Hiltjo Posthuma <hiltjo@codemadness.org> +© 2011 pancake <pancake@youterm.com> +© 2011 Random832 <random832@fastmail.us> +© 2012 William Haddon <william@haddonthethird.net> +© 2012 Kurt H. Maier <khm@intma.in> +© 2012 Christoph Lohmann <20h@r-36.net> +© 2012 David Galos <galosd83@students.rowan.edu> +© 2012 Robert Ransom <rransom.8774@gmail.com> +© 2013 Jakob Kramer <jakob.kramer@gmx.de> +© 2013 Anselm R Garbe <anselm@garbe.us> +© 2013 Truls Becken <truls.becken@gmail.com> +© 2013 dsp <dsp@2f30.org> +© 2013 Markus Teich <markus.teich@stusta.mhn.de> +© 2013 Jesse Ogle <jesse.p.ogle@gmail.com> +© 2013 Lorenzo Cogotti <miciamail@hotmail.it> +© 2013 Federico G. Benavento <benavento@gmail.com> +© 2013 Roberto E. Vargas Caballero <k0ga@shike2.com> +© 2013 Christian Hesse <mail@eworm.de> +© 2013 Markus Wichmann <nullplan@gmx.net> +© 2014 Silvan Jegen <s.jegen@gmail.com> +© 2014 Laslo Hunhold <dev@frign.de> +© 2014 Daniel Bainton <dpb@driftaway.org> +© 2014 Tuukka Kataja <stuge@xor.fi> +© 2014 Jeffrey Picard <jeff@jeffreypicard.com> +© 2014 Evan Gates <evan.gates@gmail.com> +© 2014 Michael Forney <mforney@mforney.org> +© 2014 Ari Malinen <ari.malinen@gmail.com> +© 2014 Brandon Mulcahy <brandon@jangler.info> +© 2014 Adria Garriga <rhaps0dy@installgentoo.com> +© 2014 Greg Reagle <greg.reagle@umbc.edu> diff --git a/sbase/Makefile b/sbase/Makefile @@ -0,0 +1,205 @@ +include config.mk + +.SUFFIXES: +.SUFFIXES: .o .c + +HDR =\ + arg.h\ + compat.h\ + crypt.h\ + fs.h\ + md5.h\ + queue.h\ + runetypebody.h\ + sha1.h\ + sha256.h\ + sha512.h\ + text.h\ + utf.h\ + util.h + +LIBUTF = libutf.a +LIBUTFSRC =\ + libutf/chartorunearr.c\ + libutf/readrune.c\ + libutf/rune.c\ + libutf/runetype.c\ + libutf/utf.c\ + libutf/writerune.c + +LIBUTIL = libutil.a +LIBUTILSRC =\ + libutil/agetcwd.c\ + libutil/apathmax.c\ + libutil/concat.c\ + libutil/cp.c\ + libutil/crypt.c\ + libutil/ealloc.c\ + libutil/enmasse.c\ + libutil/eprintf.c\ + libutil/eregcomp.c\ + libutil/estrtod.c\ + libutil/estrtol.c\ + libutil/fnck.c\ + libutil/getlines.c\ + libutil/human.c\ + libutil/md5.c\ + libutil/mode.c\ + libutil/putword.c\ + libutil/recurse.c\ + libutil/rm.c\ + libutil/sha1.c\ + libutil/sha256.c\ + libutil/sha512.c\ + libutil/strcasestr.c\ + libutil/strlcat.c\ + libutil/strlcpy.c + +LIB = $(LIBUTF) $(LIBUTIL) + +BIN =\ + basename\ + cal\ + cat\ + chgrp\ + chmod\ + chown\ + chroot\ + cksum\ + cmp\ + cols\ + comm\ + cp\ + cron\ + cut\ + date\ + dirname\ + du\ + echo\ + env\ + expand\ + expr\ + false\ + fold\ + grep\ + head\ + hostname\ + kill\ + link\ + ln\ + logger\ + logname\ + ls\ + md5sum\ + mkdir\ + mkfifo\ + mktemp\ + mv\ + nice\ + nl\ + nohup\ + paste\ + printenv\ + printf\ + pwd\ + readlink\ + renice\ + rm\ + rmdir\ + seq\ + setsid\ + sha1sum\ + sha256sum\ + sha512sum\ + sleep\ + sort\ + split\ + sponge\ + strings\ + sync\ + tail\ + tar\ + tee\ + test\ + touch\ + tr\ + true\ + tty\ + uname\ + unexpand\ + uniq\ + unlink\ + uudecode\ + uuencode\ + wc\ + xargs\ + yes + +LIBUTFOBJ = $(LIBUTFSRC:.c=.o) +LIBUTILOBJ = $(LIBUTILSRC:.c=.o) +OBJ = $(BIN:=.o) $(LIBUTFOBJ) $(LIBUTILOBJ) +SRC = $(BIN:=.c) +MAN = $(BIN:=.1) + +all: $(BIN) + +$(BIN): $(LIB) $(@:=.o) + +$(OBJ): $(HDR) config.mk + +.o: + $(LD) $(LDFLAGS) -o $@ $< $(LIB) + +.c.o: + $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -c $< + +$(LIBUTF): $(LIBUTFOBJ) + $(AR) -r -c $@ $? + $(RANLIB) $@ + +$(LIBUTIL): $(LIBUTILOBJ) + $(AR) -r -c $@ $? + $(RANLIB) $@ + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin + cd $(DESTDIR)$(PREFIX)/bin && chmod 755 $(BIN) + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + for m in $(MAN); do sed "s/VERSION/$(VERSION)/g" < "$$m" > $(DESTDIR)$(MANPREFIX)/man1/"$$m"; done + cd $(DESTDIR)$(MANPREFIX)/man1 && chmod 644 $(MAN) + +uninstall: + cd $(DESTDIR)$(PREFIX)/bin && rm -f $(BIN) + cd $(DESTDIR)$(MANPREFIX)/man1 && rm -f $(MAN) + +dist: clean + mkdir -p sbase-$(VERSION) + cp -r LICENSE Makefile README TODO config.mk $(SRC) $(MAN) libutf libutil $(HDR) sbase-$(VERSION) + tar -cf sbase-$(VERSION).tar sbase-$(VERSION) + gzip sbase-$(VERSION).tar + rm -rf sbase-$(VERSION) + +sbase-box: $(LIB) $(SRC) + mkdir -p build + cp $(HDR) build + for f in $(SRC); do sed "s/^main(/`basename $$f .c`_&/" < $$f > build/$$f; done + echo '#include <libgen.h>' > build/$@.c + echo '#include <stdio.h>' >> build/$@.c + echo '#include <stdlib.h>' >> build/$@.c + echo '#include <string.h>' >> build/$@.c + echo '#include "util.h"' >> build/$@.c + for f in $(SRC); do echo "int `basename $$f .c`_main(int, char **);" >> build/$@.c; done + echo 'int main(int argc, char *argv[]) { char *s = basename(argv[0]); if(!strcmp(s,"sbase-box")) { argc--; argv++; s = basename(argv[0]); } if(0) ;' >> build/$@.c + for f in $(SRC); do echo "else if(!strcmp(s, \"`basename $$f .c`\")) return `basename $$f .c`_main(argc, argv);" >> build/$@.c; done + echo 'else {' >> build/$@.c + for f in $(SRC); do echo "printf(\"`basename $$f .c`\"); putchar(' ');" >> build/$@.c; done + echo "putchar(0xa); }; return 0; }" >> build/$@.c + $(LD) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ build/*.c $(LIB) + rm -r build + +clean: + rm -f $(BIN) $(OBJ) $(LIB) sbase-box sbase-$(VERSION).tar.gz + +.PHONY: + all install uninstall dist sbase-box clean diff --git a/sbase/README b/sbase/README @@ -0,0 +1,120 @@ +sbase - suckless unix tools +=========================== + +sbase is a collection of unix tools that are inherently portable +across UNIX and UNIX-like systems. + +The following tools are implemented ('*' == finished, '#' == UTF-8 support, +'=' == implicit UTF-8 support): + + UTILITY POSIX 2008 COMPLIANT MISSING OPTIONS + ------- -------------------- --------------- +=* basename yes none +=* cal yes none +=* cat yes none += chgrp no -h, -H, -L, -P +=* chmod yes none += chown no -h, -H, -L, -P += chroot non-posix none +=* cksum yes none + * cmp yes none + * cols non-posix none +=* comm yes none += cp no -H, -i, -L +=* cron non-posix none + * cut yes none += date yes none += dirname yes none += du no -H, -L, -x += echo yes none += env yes none +# expand yes none + expr yes none +=* false yes none + fold yes none + * grep yes none + head yes none += hostname non-posix none +=* kill yes none += link yes none += ln yes none +=* logger yes none += logname yes none += ls no -C, -R, -q, -u + md5sum non-posix none += mkdir yes none += mkfifo yes none += mktemp non-posix none += mv no -i += nice yes none += nl no -d, -f, -h, -l, -n, -p, -v, -w += nohup yes none + paste yes none += printenv non-posix none + printf stolen stolen +=* pwd yes none += readlink non-posix none += renice yes none += rm no -i += rmdir no -p += sleep yes none += setsid non-posix none + sort no -m, -o, -d, -f, -i + split yes none += sponge non-posix none + strings no -a, -n, -t += sync non-posix none += tail no -c, -f += tar non-posix none += tee no -i + test yes none += touch no -r +#* tr yes none +=* true yes none += tty yes none += uudecode no -o += uuencode no -m += uname yes none +# unexpand yes none += uniq no -f, -s += unlink yes none + seq non-posix none += sha1sum non-posix none += sha256sum non-posix none += sha512sum non-posix none + wc yes none += xargs no -I, -L, -p, -s, -t, -x += yes yes none + +The complement of sbase is ubase[1] which is Linux-specific and +provides all the non-portable tools. Together they are intended to +form a base system similar to busybox but much smaller and suckless. + +Building +-------- + +You need GNU make to build sbase on OpenBSD. + +To build sbase, simply type make. You may have to fiddle with +config.mk depending on your system. + +You can also build sbase-box, which generates a single binary +containing all the required tools. You can then symlink the +individual tools to sbase-box. + +Ideally you will want to statically link sbase. If you are on Linux +we recommend using musl-libc[2]. + +Portability +----------- + +sbase has been compiled on a variety of different operating systems, +including Linux, *BSD, OSX, Haiku, Solaris, SCO OpenServer and others. + +Various combinations of operating systems and architectures have also +been built. + +You can build sbase with gcc, clang, tcc, nwcc and pcc. + +[1] http://git.suckless.org/ubase/ +[2] http://www.musl-libc.org/ diff --git a/sbase/TODO b/sbase/TODO @@ -0,0 +1,42 @@ +The following list of commands is taken from the toybox roadmap[0] and +has been stripped down accordingly. Commands that belong to ubase[1] +are not listed here as well as commands that fall outside the scope of +sbase such as vi and sh are also not listed here. + +at +awk +bc +diff +ed +file +find +getconf +install +join +make +od +patch +pathchk +sed +stty +tabs +tput + +The following program(s) have been imported from OpenBSD and need +replacing or cleaning up: + +printf + +If you are looking for some work to do on sbase, another option is to +pick a utility of interest and check the POSIX specification to see if +there are any features missing. + +We also need to add support for UTF-8. We have imported libutf from +suckless.org. As a first step we should rework existing tools that use +wide characters to use the libutf library. + +Update manpages to use mdoc(7). Look at grep.1 or at the OpenBSD +manpages for more information. + +[0] http://landley.net/toybox/roadmap.html +[1] http://git.suckless.org/ubase/ diff --git a/sbase/arg.h b/sbase/arg.h @@ -0,0 +1,63 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][1]\ + && argv[0][0] == '-';\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +/* Handles obsolete -NUM syntax */ +#define ARGNUM case '0':\ + case '1':\ + case '2':\ + case '3':\ + case '4':\ + case '5':\ + case '6':\ + case '7':\ + case '8':\ + case '9' + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define ARGNUMF(base) (brk_ = 1, estrtol(argv[0], (base))) + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/sbase/basename.1 b/sbase/basename.1 @@ -0,0 +1,26 @@ +.Dd November 21, 2014 +.Dt BASENAME 1 sbase\-VERSION +.Os +.Sh NAME +.Nm basename +.Nd strip leading path component +.Sh SYNOPSIS +.Nm basename +.Ar string +.Op Ar suffix +.Sh DESCRIPTION +.Nm +prints the +.Ar string +with any leading path components, and the +.Ar suffix , +removed. +.Sh SEE ALSO +.Xr dirname 1 , +.Xr basename 3 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. diff --git a/sbase/basename.c b/sbase/basename.c @@ -0,0 +1,41 @@ +/* See LICENSE file for copyright and license details. */ +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +static void usage(void); + +void +usage(void) +{ + eprintf("usage: %s name [suffix]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *p; + size_t off; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + p = strlen(argv[0]) ? basename(argv[0]) : "."; + if (argc == 2 && *p != '/') { + if (strlen(argv[1]) < strlen(p)) { + off = strlen(p) - strlen(argv[1]); + if (strcmp(&p[off], argv[1]) == 0) + p[off] = '\0'; + } + } + puts(p); + return 0; +} diff --git a/sbase/cal.1 b/sbase/cal.1 @@ -0,0 +1,67 @@ +.Dd January 18, 2015 +.Dt CAL 1 sbase\-VERSION +.Sh NAME +.Nm cal +.Nd show calendar +.Sh SYNOPSIS +.Nm cal +.Op Fl 1 | Fl 3 | Fl y | Fl n Ar nmonths +.Op Fl s | Fl m | Fl f Ar firstday +.Op Fl c Ar columns +.Oo Oo Ar month Oc Ar year Oc +.Sh DESCRIPTION +Print +.Ar nmonths +calendars side by side beginning with +.Ar month +and +.Ar year . +Each row of calendars contains at most +.Ar columns +calendars. The date formatting is obtained using +.Xr localtime 3 . +.Pp +If +.Ar year +is given without +.Ar month , +print the whole year, unless overridden by other options. +.Pp +The Julian calendar is used until Sep 2, 1752. The Gregorian calendar is used +starting the next day on Sep 14, 1752. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl 1 +Output current month. This is the default. +.It Fl 3 +Output previous, current and next month. +.It Fl n Ar nmonths +Output in total +.Ar nmonths +starting from the current month. +.It Fl y Ar year +Output an entire +.Ar year . +.It Fl s +Output Sunday as first day of week. +.It Fl m +Output Monday as first day of week. +.It Fl f Ar firstday +Output +.Ar firstday +(0 is Sunday, 6 is Saturday) as first day of week. +.It Fl c Ar columns +Set number of calendars in a row. The default is 3. +.El +.Sh SEE ALSO +.Xr localtime 3 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. +.Pp +The flags +.Op Fl 13ynsmfc +are an extension to that specification. diff --git a/sbase/cal.c b/sbase/cal.c @@ -0,0 +1,216 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +#include "util.h" + +enum { JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC }; +enum caltype { JULIAN, GREGORIAN }; +enum { TRANS_YEAR = 1752, TRANS_MONTH = SEP, TRANS_DAY = 2 }; + +static int +isleap(int year, enum caltype cal) +{ + if (cal == GREGORIAN) { + if (year % 400 == 0) + return 1; + if (year % 100 == 0) + return 0; + return (year % 4 == 0); + } + else { /* cal == Julian */ + return (year % 4 == 0); + } +} + +static int +monthlength(int year, int month, enum caltype cal) +{ + int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + + return (month == FEB && isleap(year,cal)) ? 29 : mdays[month]; +} + +/* From http://www.tondering.dk/claus/cal/chrweek.php#calcdow */ +static int +dayofweek(int year, int month, int dom, enum caltype cal) +{ + int m, y, a; + + month += 1; /* in this formula, 1 <= month <= 12 */ + a = (14 - month) / 12; + y = year - a; + m = month + 12 * a - 2; + + if (cal == GREGORIAN) + return (dom + y + y / 4 - y / 100 + y / 400 + (31 * m) / 12) % 7; + else /* cal == Julian */ + return (5 + dom + y + y / 4 + (31 * m) / 12) % 7; +} + +static void +printgrid(int year, int month, int fday, int line) +{ + enum caltype cal; + int trans; /* are we in the transition from Julian to Gregorian? */ + int offset, dom, d = 0; + + if (year < TRANS_YEAR || (year == TRANS_YEAR && month <= TRANS_MONTH)) + cal = JULIAN; + else + cal = GREGORIAN; + trans = (year == TRANS_YEAR && month == TRANS_MONTH); + offset = dayofweek(year, month, 1, cal) - fday; + if (offset < 0) + offset += 7; + if (line == 1) { + for ( ; d < offset; ++d) + printf(" "); + dom = 1; + } else { + dom = 8 - offset + (line - 2) * 7; + if (trans && !(line == 2 && fday == 3)) + dom += 11; + } + for ( ; d < 7 && dom <= monthlength(year, month, cal); ++d, ++dom) { + printf("%2d ", dom); + if (trans && dom==TRANS_DAY) + dom += 11; + } + for ( ; d < 7; ++d) + printf(" "); +} + +static void +drawcal(int year, int month, int ncols, int nmons, int fday) +{ + char *smon[] = {" January", " February", " March", " April", + " May", " June", " July", " August", + "September", " October", " November", " December" }; + char *days[] = { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", }; + int m, n, col, cur_year, cur_month, line, dow; + + for (m = 0; m < nmons; ) { + n = m; + for (col = 0; m < nmons && col < ncols; ++col, ++m) { + cur_year = year + m / 12; + cur_month = month + m % 12; + if (cur_month > 11) { + cur_month -= 12; + cur_year += 1; + } + printf(" %s %d ", smon[cur_month], cur_year); + printf(" "); + } + printf("\n"); + for (col = 0, m = n; m < nmons && col < ncols; ++col, ++m) { + for (dow = fday; dow < (fday + 7); ++dow) + printf("%s ", days[dow % 7]); + printf(" "); + } + printf("\n"); + for (line = 1; line <= 6; ++line) { + for (col = 0, m = n; m < nmons && col < ncols; ++col, ++m) { + cur_year = year + m / 12; + cur_month = month + m % 12; + if (cur_month > 11) { + cur_month -= 12; + cur_year += 1; + } + printgrid(cur_year, cur_month, fday, line); + printf(" "); + } + printf("\n"); + } + } +} + +static void +usage(void) +{ + eprintf("usage: %s [-1 | -3 | -y | -n nmonths] " + "[-s | -m | -f firstday] [-c columns] [[month] year]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int year, month, ncols, nmons, fday; + struct tm *ltime; + time_t now; + + now = time(NULL); + ltime = localtime(&now); + year = ltime->tm_year + 1900; + month = ltime->tm_mon + 1; + fday = 0; + + ncols = 3; + nmons = 0; + + ARGBEGIN { + case '1': + nmons = 1; + break; + case '3': + nmons = 3; + month -= 1; + if (month == 0) { + month = 12; + year--; + } + break; + case 'c': + ncols = estrtol(EARGF(usage()), 0); + break; + case 'f': + fday = estrtol(EARGF(usage()), 0); + break; + case 'm': /* Monday */ + fday = 1; + break; + case 'n': + nmons = estrtol(EARGF(usage()), 0); + break; + case 's': /* Sunday */ + fday = 0; + break; + case 'y': + month = 1; + nmons = 12; + break; + default: + usage(); + } ARGEND; + + if (nmons == 0) { + if (argc == 1) { + month = 1; + nmons = 12; + } else { + nmons = 1; + } + } + + switch (argc) { + case 2: + month = estrtol(argv[0], 0); + argv++; + case 1: + year = estrtol(argv[0], 0); + break; + case 0: + break; + default: + usage(); + } + + if (ncols < 0 || month < 1 || month > 12 || nmons < 1 || fday < 0 || fday > 6) { + usage(); + } + + drawcal(year, month - 1, ncols, nmons, fday); + + return 0; +} diff --git a/sbase/cat.1 b/sbase/cat.1 @@ -0,0 +1,29 @@ +.Dd January 16, 2015 +.Dt CAT 1 sbase\-VERSION +.Sh NAME +.Nm cat +.Nd concatenate files +.Sh SYNOPSIS +.Nm cat +.Op Fl u +.Op Ar file ... +.Sh DESCRIPTION +.Nm +reads each +.Ar file +in sequence and writes it to stdout. If no +.Ar file +is given, +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl u +Unbuffered output +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. diff --git a/sbase/cat.c b/sbase/cat.c @@ -0,0 +1,45 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "text.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-u] [file...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + int ret = 0; + + ARGBEGIN { + case 'u': + setbuf(stdout, NULL); + break; + default: + usage(); + } ARGEND; + + if (argc == 0) { + concat(stdin, "<stdin>", stdout, "<stdout>"); + } else { + for (; argc; argc--, argv++) { + if (argv[0][0] == '-' && !argv[0][1]) + argv[0] = "/dev/fd/0"; + if (!(fp = fopen(argv[0], "r"))) { + weprintf("fopen %s:", argv[0]); + ret = 1; + continue; + } + concat(fp, argv[0], stdout, "<stdout>"); + fclose(fp); + } + } + return ret; +} diff --git a/sbase/chgrp.1 b/sbase/chgrp.1 @@ -0,0 +1,22 @@ +.TH CHGRP 1 sbase\-VERSION +.SH NAME +chgrp \- change the file group ownership +.SH SYNOPSIS +.B chgrp +.RB [ \-R ] +.I groupname +.I file... +.SH DESCRIPTION +.B chgrp +sets the group id of the files specified by +.IR file +to the gid of the group named +.IR group. +If the +.IR R +flag is specified, this process is recursively applied to +everything in +.IR file. + +.SH SEE ALSO +.IR chown (1) chown (2) chmod (1) chmod (2) getgrnam (3) diff --git a/sbase/chgrp.c b/sbase/chgrp.c @@ -0,0 +1,70 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <grp.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "util.h" + +static int gid; +static int status; +static int rflag; +static struct stat st; + +static void +usage(void) +{ + eprintf("usage: chgrp [-R] groupname file...\n"); +} + +static void +chgrp(const char *path) +{ + if (chown(path, st.st_uid, gid) < 0) { + weprintf("chown %s:", path); + status = 1; + } + if (rflag) + recurse(path, chgrp); +} + +int +main(int argc, char *argv[]) +{ + struct group *gr; + + ARGBEGIN { + case 'R': + rflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc < 2) + usage(); + + errno = 0; + gr = getgrnam(argv[0]); + if (!gr) { + if (errno) + eprintf("getgrnam %s:", argv[0]); + else + eprintf("getgrnam %s: no such group\n", argv[0]); + } + gid = gr->gr_gid; + + while (*++argv) { + if (stat(*argv, &st) < 0) { + weprintf("stat %s:", *argv); + status = 1; + continue; + } + chgrp(*argv); + } + return status; +} diff --git a/sbase/chmod.1 b/sbase/chmod.1 @@ -0,0 +1,66 @@ +.Dd January 17, 2015 +.Dt CHMOD 1 sbase\-VERSION +.Sh NAME +.Nm chmod +.Nd change file mode +.Sh SYNOPSIS +.Nm chmod +.Op Fl R +.Ar mode +.Op Ar file ... +.Sh DESCRIPTION +.Nm +changes the file mode of the given files. +.Pp +If +.Ar mode +is +.Em octal +"[sog]e" +.Bl -tag -width Ds +.It s +.Xr sticky 1 => s += 1 +.Pp +.Xr setgid 2 => s += 2 +.Pp +.Xr setuid 4 => s += 4 +.It o|g|e +owner | group | everyone +.Pp +.Xr execute 1 => o|g|e += 1 +.Pp +.Xr write 2 => o|g|e += 2 +.Pp +.Xr read 4 => o|g|e += 4 +.El +.Pp +Leading zeroes may be omitted. +.Pp +If +.Ar mode +is +.Em symbolic +"[ugoa]*[+-=][rwxst]*" +.Bl -tag -width Ds +.It u|g|o|a +owner | group | other (non-group) | everyone +.It +|-|= +add | remove | set +.It r|w|x|s|t +read | write | execute | setuid and setgid | sticky +.El +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl R +Change modes recursively +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. +.Pp +The +.Op Fl R +flag is an extension to that specification. diff --git a/sbase/chmod.c b/sbase/chmod.c @@ -0,0 +1,74 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "util.h" + +static int rflag = 0; +static char *modestr = ""; +static mode_t mask = 0; +static int ret = 0; + +void +chmodr(const char *path) +{ + struct stat st; + mode_t m; + + if (stat(path, &st) < 0) { + weprintf("stat %s:", path); + ret = 1; + return; + } + + m = parsemode(modestr, st.st_mode, mask); + if (chmod(path, m) < 0) { + weprintf("chmod %s:", path); + ret = 1; + } + if (rflag) + recurse(path, chmodr); +} + +static void +usage(void) +{ + eprintf("usage: %s [-R] mode [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + size_t i; + + argv0 = argv[0]; + for (i = 1; i < argc && argv[i][0] == '-'; i++) { + switch (argv[i][1]) { + case 'R': + rflag = 1; + break; + case 'r': case 'w': case 'x': case 's': case 't': + /* + * -[rwxst] are valid modes so do not interpret + * them as options - in any case we are done if + * we hit this case + */ + goto done; + default: + usage(); + } + } +done: + mask = getumask(); + modestr = argv[i]; + + if (argc - i - 1 < 1) + usage(); + + for (++i; i < argc; i++) + chmodr(argv[i]); + + return ret; +} diff --git a/sbase/chown.1 b/sbase/chown.1 @@ -0,0 +1,20 @@ +.TH CHOWN 1 sbase\-VERSION +.SH NAME +chown \- change file ownership +.SH SYNOPSIS +.B chown +.RB [ \-Rr ] +.RI [ owner ][: group ] +.RI [ file ...] +.SH DESCRIPTION +.B chown +changes the user or group ownership for the given files. +.SH OPTIONS +.TP +.B \-R +equivalent to -r. +.TP +.B \-r +change directory ownership recursively. +.SH SEE ALSO +.IR chown (2) diff --git a/sbase/chown.c b/sbase/chown.c @@ -0,0 +1,90 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <grp.h> +#include <pwd.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static void chownpwgr(const char *); + +static int rflag = 0; +static uid_t uid = -1; +static gid_t gid = -1; +static int ret = 0; + +static void +usage(void) +{ + eprintf("usage: %s [-Rr] [owner][:[group]] file...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *owner, *group, *end; + struct passwd *pw; + struct group *gr; + + ARGBEGIN { + case 'R': + case 'r': + rflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc == 0) + usage(); + + owner = argv[0]; + argv++; + argc--; + if ((group = strchr(owner, ':'))) + *group++ = '\0'; + + if (owner && *owner) { + errno = 0; + pw = getpwnam(owner); + if (pw) { + uid = pw->pw_uid; + } else { + if (errno != 0) + eprintf("getpwnam %s:", owner); + uid = strtoul(owner, &end, 10); + if (*end != '\0') + eprintf("getpwnam %s: no such user\n", owner); + } + } + if (group && *group) { + errno = 0; + gr = getgrnam(group); + if (gr) { + gid = gr->gr_gid; + } else { + if (errno != 0) + eprintf("getgrnam %s:", group); + gid = strtoul(group, &end, 10); + if (*end != '\0') + eprintf("getgrnam %s: no such group\n", group); + } + } + for (; argc > 0; argc--, argv++) + chownpwgr(argv[0]); + + return ret; +} + +void +chownpwgr(const char *path) +{ + if (chown(path, uid, gid) < 0) { + weprintf("chown %s:", path); + ret = 1; + } + if (rflag) + recurse(path, chownpwgr); +} diff --git a/sbase/chroot.1 b/sbase/chroot.1 @@ -0,0 +1,26 @@ +.TH CHROOT 1 sbase\-VERSION +.SH NAME +chroot \- invoke a command with a different root directory +.SH SYNOPSIS +.B chroot +.IR dir +.RI [ command +.RI [ arg ...]] + +.SH DESCRIPTION +.B chroot +runs +.IR command +after changing the root directory to +.IR dir +with the +.B chroot +system call, and changing the working directory to the new root. + +If +.IR command +is not specified, an interactive shell is started in the new root. + +.SH SEE ALSO +.IR chroot (2) +.IR chdir (2) diff --git a/sbase/chroot.c b/sbase/chroot.c @@ -0,0 +1,50 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: chroot dir [command [arg...]]\n"); +} + +int +main(int argc, char *argv[]) +{ + char *shell[] = { "/bin/sh", "-i", NULL }, *aux, *p; + int savederrno; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + if ((aux = getenv("SHELL"))) + shell[0] = aux; + + if (chroot(argv[0]) < 0) + eprintf("chroot %s:", argv[0]); + + if (chdir("/") < 0) + eprintf("chdir:"); + + if (argc == 1) { + p = *shell; + execvp(*shell, shell); + } else { + p = argv[1]; + execvp(argv[1], argv+1); + } + + savederrno = errno; + weprintf("execvp %s:", p); + _exit(savederrno == ENOENT ? 127 : 126); + /* unreachable */ + return 0; +} diff --git a/sbase/cksum.1 b/sbase/cksum.1 @@ -0,0 +1,27 @@ +.Dd January 17, 2015 +.Dt CKSUM 1 sbase\-VERSION +.Sh NAME +.Nm cksum +.Nd compute file checksum +.Sh SYNOPSIS +.Nm cksum +.Op Ar file ... +.Sh DESCRIPTION +.Nm +calculates a cyclic redundancy check (CRC) of +.Ar file +according to +.St -iso8802-3 +and writes it, the file size in bytes and path to stdout. +.Pp +If no +.Ar file +is given, +.Nm +reads from stdin. +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. diff --git a/sbase/cksum.c b/sbase/cksum.c @@ -0,0 +1,118 @@ +/* See LICENSE file for copyright and license details. */ +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +static const unsigned long crctab[] = { 0x00000000, +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 +}; + +static void +cksum(FILE *fp, const char *s) +{ + unsigned char buf[BUFSIZ]; + uint32_t ck = 0; + size_t len = 0; + size_t i, n; + + while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) { + for (i = 0; i < n; i++) + ck = (ck << 8) ^ crctab[(ck >> 24) ^ buf[i]]; + len += n; + } + if (ferror(fp)) + eprintf("%s: read error:", s ? s : "<stdin>"); + + for (i = len; i > 0; i >>= 8) + ck = (ck << 8) ^ crctab[(ck >> 24) ^ (i & 0xFF)]; + + printf("%"PRIu32" %lu", ~ck, (unsigned long)len); + if (s) + printf(" %s", s); + putchar('\n'); +} + +static void +usage(void) +{ + eprintf("usage: %s [files ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc == 0) + cksum(stdin, NULL); + else { + for (; argc > 0; argc--, argv++) { + if (!(fp = fopen(argv[0], "r"))) { + weprintf("fopen %s:", argv[0]); + continue; + } + cksum(fp, argv[0]); + fclose(fp); + } + } + return 0; +} diff --git a/sbase/cmp.1 b/sbase/cmp.1 @@ -0,0 +1,42 @@ +.Dd November 21, 2014 +.Dt CMP 1 sbase\-VERSION +.Os +.Sh NAME +.Nm cmp +.Nd compare two files +.Sh SYNOPSIS +.Nm cmp +.Op Fl l | Fl s +.Ar file1 file2 +.Sh DESCRIPTION +.Nm +compares two files byte by byte. If the files differ, +.Nm +prints the byte and +line number at which the difference occurred. +.Pp +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl l +Print the byte number, and the differing bytes (in octal), for each difference. +.It Fl s +Print nothing, only returns status. +.El +.Sh EXIT STATUS +.Bl -tag -width Ds +.It 0 +The files are identical. +.It 1 +The files are different. +.It > 1 +An error occured. +.El +.Sh SEE ALSO +.Xr comm 1 , +.Xr diff 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. diff --git a/sbase/cmp.c b/sbase/cmp.c @@ -0,0 +1,86 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +enum { Same = 0, Diff = 1, Error = 2 }; + +static void +usage(void) +{ + enprintf(Error, "usage: %s [-l | -s] file1 file2\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int lflag = 0; + int sflag = 0; + int same = 1; + int b[2], i; + long line = 1, n = 1; + FILE *fp[2]; + + ARGBEGIN { + case 'l': + lflag = 1; + break; + case 's': + sflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc != 2 || (lflag && sflag)) + usage(); + + if (argv[0][0] == '-' && !argv[0][1]) + argv[0] = "/dev/fd/0"; + fp[0] = fopen(argv[0], "r"); + if (!fp[0]) { + if (!sflag) + weprintf("fopen %s:", argv[0]); + exit(Error); + } + + if (argv[1][0] == '-' && !argv[1][1]) + argv[1] = "/dev/fd/0"; + fp[1] = fopen(argv[1], "r"); + if (!fp[1]) { + if (!sflag) + weprintf("fopen %s:", argv[1]); + exit(Error); + } + + for (n = 1; ; n++) { + b[0] = getc(fp[0]); + b[1] = getc(fp[1]); + if (b[0] == EOF && b[1] == EOF) + break; + if (b[0] == '\n' && b[1] == '\n') + line++; + if (b[0] == b[1]) + continue; + for (i = 0; i < 2; i++) { + if (b[i] == EOF) { + if (!sflag) + fprintf(stderr, "cmp: EOF on %s\n", + !argv[i] ? "<stdin>" : argv[i]); + exit(Diff); + } + } + if (!lflag) { + if (!sflag) + printf("%s %s differ: char %ld, line %ld\n", + argv[0], !argv[1] ? "<stdin>" : argv[1], n, line); + exit(Diff); + } else { + printf("%ld %o %o\n", n, b[0], b[1]); + same = 0; + } + } + return same ? Same : Diff; +} diff --git a/sbase/cols.1 b/sbase/cols.1 @@ -0,0 +1,41 @@ +.Dd December 8, 2014 +.Dt COLS 1 sbase\-VERSION +.Os +.Sh NAME +.Nm cols +.Nd columnize output +.Sh SYNOPSIS +.Nm cols +.Op Fl c Ar chars +.Op Ar file ... +.Sh DESCRIPTION +.Nm +reads each file in sequence and writes them to stdout, +in as many vertical columns as will fit in +.Ar chars +character columns. +If no file is given, cols reads from stdin. +.Sh OPTIONS +.Bl -tag -width xxxxxxxx +.It Fl c Ar chars +Specifies the maximum number of character columns to use +(unless the input contains lines longer than +.Ar chars +characters). By default cols tries to figure out the width +of the output device, if that fails it defaults to 65 +chars. +.El +.Sh BUGS +This implementation of +.Nm +assumes that each UTF-8 code point occupies one character cell, +and thus mishandles TAB characters (among others). +.Pp +.Nm +currently mangles files which contain embedded NULs. +.Sh HISTORY +.Nm +is similar to the mc(1) command on Plan 9. It was renamed to +.Nm +to avoid the name collision with the popular file manager +Midnight Commander. diff --git a/sbase/cols.c b/sbase/cols.c @@ -0,0 +1,94 @@ +/* See LICENSE file for copyright and license details. */ +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include "text.h" +#include "utf.h" +#include "util.h" + +static long chars = 65; +static int cflag; +static struct linebuf b = EMPTY_LINEBUF; + +static long n_columns; +static long n_rows; + +static void +usage(void) +{ + eprintf("usage: %s [-c chars] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + long i, l, col; + size_t len, bytes; + int maxlen = 0; + struct winsize w; + FILE *fp; + + ARGBEGIN { + case 'c': + cflag = 1; + chars = estrtol(EARGF(usage()), 0); + if (chars < 3) + eprintf("%d: too few character columns"); + break; + default: + usage(); + } ARGEND; + + if (cflag == 0) { + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + if (w.ws_col != 0) + chars = w.ws_col; + } + + if (argc == 0) { + getlines(stdin, &b); + } else for (; argc > 0; argc--, argv++) { + if (!(fp = fopen(argv[0], "r"))) + eprintf("fopen %s:", argv[0]); + getlines(fp, &b); + fclose(fp); + } + + for (l = 0; l < b.nlines; ++l) { + len = utflen(b.lines[l]); + bytes = strlen(b.lines[l]); + if (len > 0 && b.lines[l][bytes-1] == '\n') { + b.lines[l][bytes-1] = '\0'; + --len; + } + if (len > maxlen) + maxlen = len; + if (maxlen > (chars - 1) / 2) + break; + } + + n_columns = (chars + 1) / (maxlen + 1); + if (n_columns <= 1) { + for (l = 0; l < b.nlines; ++l) { + fputs(b.lines[l], stdout); + } + return 0; + } + + n_rows = (b.nlines + (n_columns - 1)) / n_columns; + for (i = 0; i < n_rows; ++i) { + for (l = i, col = 1; l < b.nlines; l += n_rows, ++col) { + len = utflen(b.lines[l]); + fputs(b.lines[l], stdout); + if (col < n_columns) + printf("%*s", maxlen + 1 - (int)len, ""); + } + fputs("\n", stdout); + } + + return 0; +} diff --git a/sbase/comm.1 b/sbase/comm.1 @@ -0,0 +1,43 @@ +.Dd January 18, 2015 +.Dt COMM 1 sbase\-VERSION +.Sh NAME +.Nm comm +.Nd select or reject lines common to two files +.Sh SYNOPSIS +.Nm comm +.Op Fl 123 +.Ar file1 +.Ar file2 +.Sh DESCRIPTION +.Nm +reads +.Ar file1 +and +.Ar file2, +which should both be sorted lexically, and writes three text columns +to stdout: +.Bl -tag -width Ds +.It 1 +Lines only in +.Ar file1 . +.It 2 +Lines only in +.Ar file2 . +.It 3 +Common lines. +.El +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl 1 | Fl 2 | Fl 3 +Suppress column 1 | 2 | 3 +.El +.Sh SEE ALSO +.Xr cmp 1 , +.Xr sort 1 , +.Xr uniq 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. diff --git a/sbase/comm.c b/sbase/comm.c @@ -0,0 +1,107 @@ +/* See LICENSE file for copyright and license details. */ +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +#define CLAMP(x, l, h) MIN(h, MAX(l, x)) + +static int show = 0x07; + +static void +printline(int pos, char *line) +{ + int i; + + if (!(show & (0x1 << pos))) + return; + + for (i = 0; i < pos; i++) { + if (show & (0x1 << i)) + putchar('\t'); + } + printf("%s", line); +} + +static char * +nextline(char *buf, int n, FILE *f, char *name) +{ + buf = fgets(buf, n, f); + if (!buf && !feof(f)) + eprintf("%s: read error:", name); + if (buf && !strchr(buf, '\n')) + eprintf("%s: line too long\n", name); + return buf; +} + +static void +finish(int pos, FILE *f, char *name) +{ + char buf[LINE_MAX + 1]; + + while (nextline(buf, sizeof(buf), f, name)) + printline(pos, buf); + exit(1); +} + +static void +usage(void) +{ + eprintf("usage: %s [-123] file1 file2\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int i, diff = 0; + FILE *fp[2]; + char lines[2][LINE_MAX + 1]; + + ARGBEGIN { + case '1': + case '2': + case '3': + show &= 0x07 ^ (1 << (ARGC() - '1')); + break; + default: + usage(); + } ARGEND; + + if (argc != 2) + usage(); + + for (i = 0; i < LEN(fp); i++) { + if (argv[i][0] == '-' && !argv[i][1]) + argv[i] = "/dev/fd/0"; + if (!(fp[i] = fopen(argv[i], "r"))) + eprintf("fopen %s:", argv[i]); + } + + for (;;) { + if (diff <= 0) { + lines[0][0] = '\0'; + if (!nextline(lines[0], sizeof(lines[0]), + fp[0], argv[0])) { + if (lines[1][0] != '\0') + printline(1, lines[1]); + finish(1, fp[1], argv[1]); + } + } + if (diff >= 0) { + lines[1][0] = '\0'; + if (!nextline(lines[1], sizeof(lines[1]), + fp[1], argv[1])) { + if (lines[0][0] != '\0') + printline(0, lines[0]); + finish(0, fp[0], argv[0]); + } + } + diff = strcmp(lines[0], lines[1]); + diff = CLAMP(diff, -1, 1); + printline((2-diff) % 3, lines[MAX(0, diff)]); + } + + return 0; +} diff --git a/sbase/compat.h b/sbase/compat.h @@ -0,0 +1,6 @@ +/* See LICENSE file for copyright and license details. */ +#include <limits.h> + +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX +#endif diff --git a/sbase/config.mk b/sbase/config.mk @@ -0,0 +1,15 @@ +# sbase version +VERSION = 0.0 + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +CC = cc +LD = $(CC) +AR = ar +RANLIB = ranlib + +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 +CFLAGS = -std=c99 -Wall -pedantic +LDFLAGS = -s diff --git a/sbase/cp.1 b/sbase/cp.1 @@ -0,0 +1,42 @@ +.TH CP 1 sbase\-VERSION +.SH NAME +cp \- copy files and directories +.SH SYNOPSIS +.B cp +.RB [ \-Rr ] +.I file +.RI [ name ] +.P +.B cp +.RB [ \-aPpRrv ] +.RI [ file ...] +.RI [ directory ] +.SH DESCRIPTION +.B cp +copies a given file, naming it the given name. If multiple files are listed +they will be copied into the given directory. +.SH OPTIONS +.TP +.B \-a +preserve mode, timestamp, links and permissions. +Implies \-d, \-p, \-r. +.TP +.B \-P +don't dereference symbolic links. +.TP +.B \-p +preserve mode, timestamp and permissions. +.TP +.B \-f +if an existing destination file cannot be opened, remove it and try again. +.TP +.B \-R +equivalent to -r. +.TP +.B \-r +copies directories recursively. If this flag is not specified, directories are +not copied. +.TP +.B \-v +print names of source and destination per file to stdout. In the format: +"source \-> destination". diff --git a/sbase/cp.c b/sbase/cp.c @@ -0,0 +1,51 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdlib.h> +#include <sys/stat.h> + +#include "fs.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-adfpRrv] source... dest\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct stat st; + + ARGBEGIN { + case 'a': + /* implies -dpr */ + cp_aflag = cp_Pflag = cp_pflag = cp_rflag = 1; + break; + case 'P': + cp_Pflag = 1; + break; + case 'p': + cp_pflag = 1; + break; + case 'f': + cp_fflag = 1; + break; + case 'R': + case 'r': + cp_rflag = 1; + break; + case 'v': + cp_vflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc < 2) + usage(); + + if (argc > 2 && !(stat(argv[argc-1], &st) == 0 && S_ISDIR(st.st_mode))) + eprintf("%s: not a directory\n", argv[argc-1]); + enmasse(argc, argv, cp); + return cp_status; +} diff --git a/sbase/cron.1 b/sbase/cron.1 @@ -0,0 +1,24 @@ +.Dd December 16, 2014 +.Dt CRON 1 sbase\-VERSION +.Os +.Sh NAME +.Nm cron +.Nd clock daemon +.Sh SYNOPSIS +.Nm cron +.Op Fl f Ar file +.Op Fl n +.Sh DESCRIPTION +.Nm +schedules commands to be run at specified dates and times. +.Pp +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl f Ar file +Use the specified +.Ar file +instead of the default +.Ar /etc/crontab . +.It Fl n +Do not daemonize. +.El diff --git a/sbase/cron.c b/sbase/cron.c @@ -0,0 +1,494 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/types.h> +#include <sys/wait.h> + +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> + +#include "queue.h" +#include "util.h" + +struct field { + /* [low, high] */ + long low; + long high; + /* for every `div' units */ + long div; +}; + +struct ctabentry { + struct field min; + struct field hour; + struct field mday; + struct field mon; + struct field wday; + char *cmd; + TAILQ_ENTRY(ctabentry) entry; +}; + +struct jobentry { + char *cmd; + pid_t pid; + TAILQ_ENTRY(jobentry) entry; +}; + +char *argv0; +static sig_atomic_t chldreap; +static sig_atomic_t reload; +static sig_atomic_t quit; +static TAILQ_HEAD(, ctabentry) ctabhead = TAILQ_HEAD_INITIALIZER(ctabhead); +static TAILQ_HEAD(, jobentry) jobhead = TAILQ_HEAD_INITIALIZER(jobhead); +static char *config = "/etc/crontab"; +static char *pidfile = "/var/run/crond.pid"; +static int nflag; + +static void +loginfo(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + if (nflag == 0) + vsyslog(LOG_INFO, fmt, ap); + else + vfprintf(stdout, fmt, ap); + fflush(stdout); + va_end(ap); +} + +static void +logwarn(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + if (nflag == 0) + vsyslog(LOG_WARNING, fmt, ap); + else + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static void +logerr(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + if (nflag == 0) + vsyslog(LOG_ERR, fmt, ap); + else + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static void +runjob(char *cmd) +{ + struct jobentry *je; + time_t t; + pid_t pid; + + t = time(NULL); + + /* If command is already running, skip it */ + TAILQ_FOREACH(je, &jobhead, entry) { + if (strcmp(je->cmd, cmd) == 0) { + loginfo("already running %s pid: %d at %s", + je->cmd, je->pid, ctime(&t)); + return; + } + } + + pid = fork(); + if (pid < 0) { + logerr("error: failed to fork job: %s time: %s", + cmd, ctime(&t)); + return; + } else if (pid == 0) { + setsid(); + loginfo("run: %s pid: %d at %s", + cmd, getpid(), ctime(&t)); + execl("/bin/sh", "/bin/sh", "-c", cmd, (char *)NULL); + logerr("error: failed to execute job: %s time: %s", + cmd, ctime(&t)); + _exit(1); + } else { + je = emalloc(sizeof(*je)); + je->cmd = estrdup(cmd); + je->pid = pid; + TAILQ_INSERT_TAIL(&jobhead, je, entry); + } +} + +static void +waitjob(void) +{ + struct jobentry *je, *tmp; + int status; + time_t t; + pid_t pid; + + t = time(NULL); + + while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { + je = NULL; + TAILQ_FOREACH(tmp, &jobhead, entry) { + if (tmp->pid == pid) { + je = tmp; + break; + } + } + if (je) { + TAILQ_REMOVE(&jobhead, je, entry); + free(je->cmd); + free(je); + } + if (WIFEXITED(status) == 1) + loginfo("complete: pid: %d returned: %d time: %s", + pid, WEXITSTATUS(status), ctime(&t)); + else if (WIFSIGNALED(status) == 1) + loginfo("complete: pid: %d terminated by signal: %s time: %s", + pid, strsignal(WTERMSIG(status)), ctime(&t)); + else if (WIFSTOPPED(status) == 1) + loginfo("complete: pid: %d stopped by signal: %s time: %s", + pid, strsignal(WSTOPSIG(status)), ctime(&t)); + } +} + +static int +isleap(int year) +{ + if (year % 400 == 0) + return 1; + if (year % 100 == 0) + return 0; + return (year % 4 == 0); +} + +static int +daysinmon(int mon, int year) +{ + int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + if (year < 1900) + year += 1900; + if (isleap(year)) + days[1] = 29; + return days[mon]; +} + +static int +matchentry(struct ctabentry *cte, struct tm *tm) +{ + struct { + struct field *f; + int tm; + int len; + } matchtbl[] = { + { .f = &cte->min, .tm = tm->tm_min, .len = 60 }, + { .f = &cte->hour, .tm = tm->tm_hour, .len = 24 }, + { .f = &cte->mday, .tm = tm->tm_mday, .len = daysinmon(tm->tm_mon, tm->tm_year) }, + { .f = &cte->mon, .tm = tm->tm_mon, .len = 12 }, + { .f = &cte->wday, .tm = tm->tm_wday, .len = 7 }, + }; + size_t i; + + for (i = 0; i < LEN(matchtbl); i++) { + if (matchtbl[i].f->high == -1) { + if (matchtbl[i].f->low == -1) { + continue; + } else if (matchtbl[i].f->div > 0) { + if (matchtbl[i].tm > 0) { + if (matchtbl[i].tm % matchtbl[i].f->div == 0) + continue; + } else { + if (matchtbl[i].len % matchtbl[i].f->div == 0) + continue; + } + } else if (matchtbl[i].f->low == matchtbl[i].tm) { + continue; + } + } else if (matchtbl[i].f->low <= matchtbl[i].tm && + matchtbl[i].f->high >= matchtbl[i].tm) { + continue; + } + break; + } + if (i != LEN(matchtbl)) + return 0; + return 1; +} + +static int +parsefield(const char *field, long low, long high, struct field *f) +{ + long min, max, div; + char *e1, *e2; + + if (strcmp(field, "*") == 0) { + f->low = -1; + f->high = -1; + return 0; + } + + div = -1; + max = -1; + min = strtol(field, &e1, 10); + + switch (e1[0]) { + case '-': + e1++; + errno = 0; + max = strtol(e1, &e2, 10); + if (e2[0] != '\0' || errno != 0) + return -1; + break; + case '*': + e1++; + if (e1[0] != '/') + return -1; + e1++; + errno = 0; + div = strtol(e1, &e2, 10); + if (e2[0] != '\0' || errno != 0) + return -1; + break; + case '\0': + break; + default: + return -1; + } + + if (min < low || min > high) + return -1; + if (max != -1) + if (max < low || max > high) + return -1; + if (div != -1) + if (div < low || div > high) + return -1; + + f->low = min; + f->high = max; + f->div = div; + return 0; +} + +static void +unloadentries(void) +{ + struct ctabentry *cte, *tmp; + + for (cte = TAILQ_FIRST(&ctabhead); cte; cte = tmp) { + tmp = TAILQ_NEXT(cte, entry); + TAILQ_REMOVE(&ctabhead, cte, entry); + free(cte->cmd); + free(cte); + } +} + +static int +loadentries(void) +{ + struct ctabentry *cte; + FILE *fp; + char *line = NULL, *p, *col; + int r = 0, y; + size_t size = 0; + ssize_t len; + + if ((fp = fopen(config, "r")) == NULL) { + logerr("error: can't open %s: %s\n", config, strerror(errno)); + return -1; + } + + for (y = 0; (len = getline(&line, &size, fp)) != -1; y++) { + p = line; + if (line[0] == '#' || line[0] == '\n' || line[0] == '\0') + continue; + + cte = emalloc(sizeof(*cte)); + + col = strsep(&p, "\t"); + if (!col || parsefield(col, 0, 59, &cte->min) < 0) { + logerr("error: failed to parse `min' field on line %d\n", + y + 1); + free(cte); + r = -1; + break; + } + + col = strsep(&p, "\t"); + if (!col || parsefield(col, 0, 23, &cte->hour) < 0) { + logerr("error: failed to parse `hour' field on line %d\n", + y + 1); + free(cte); + r = -1; + break; + } + + col = strsep(&p, "\t"); + if (!col || parsefield(col, 1, 31, &cte->mday) < 0) { + logerr("error: failed to parse `mday' field on line %d\n", + y + 1); + free(cte); + r = -1; + break; + } + + col = strsep(&p, "\t"); + if (!col || parsefield(col, 1, 12, &cte->mon) < 0) { + logerr("error: failed to parse `mon' field on line %d\n", + y + 1); + free(cte); + r = -1; + break; + } + + col = strsep(&p, "\t"); + if (!col || parsefield(col, 0, 6, &cte->wday) < 0) { + logerr("error: failed to parse `wday' field on line %d\n", + y + 1); + free(cte); + r = -1; + break; + } + + col = strsep(&p, "\n"); + if (!col) { + logerr("error: missing `cmd' field on line %d\n", + y + 1); + free(cte); + r = -1; + break; + } + cte->cmd = estrdup(col); + + TAILQ_INSERT_TAIL(&ctabhead, cte, entry); + } + + if (r < 0) + unloadentries(); + + free(line); + fclose(fp); + + return r; +} + +static void +reloadentries(void) +{ + unloadentries(); + if (loadentries() < 0) + logwarn("warning: discarding old crontab entries\n"); +} + +static void +sighandler(int sig) +{ + switch (sig) { + case SIGCHLD: + chldreap = 1; + break; + case SIGHUP: + reload = 1; + break; + case SIGTERM: + quit = 1; + break; + } +} + +static void +usage(void) +{ + eprintf("usage: %s [-f file] [-n]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + struct ctabentry *cte; + time_t t; + struct tm *tm; + struct sigaction sa; + + ARGBEGIN { + case 'n': + nflag = 1; + break; + case 'f': + config = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + + if (argc > 0) + usage(); + + if (nflag == 0) { + openlog(argv[0], LOG_CONS | LOG_PID, LOG_CRON); + if (daemon(1, 0) < 0) { + logerr("error: failed to daemonize %s\n", strerror(errno)); + return 1; + } + if ((fp = fopen(pidfile, "w"))) { + fprintf(fp, "%d\n", getpid()); + fclose(fp); + } + } + + sa.sa_handler = sighandler; + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + loadentries(); + + while (1) { + t = time(NULL); + sleep(60 - t % 60); + + if (quit == 1) { + if (nflag == 0) + unlink(pidfile); + unloadentries(); + /* Don't wait or kill forked processes, just exit */ + break; + } + + if (reload == 1 || chldreap == 1) { + if (reload == 1) { + reloadentries(); + reload = 0; + } + if (chldreap == 1) { + waitjob(); + chldreap = 0; + } + continue; + } + + TAILQ_FOREACH(cte, &ctabhead, entry) { + t = time(NULL); + tm = localtime(&t); + if (matchentry(cte, tm) == 1) + runjob(cte->cmd); + } + } + + if (nflag == 0) + closelog(); + + return 0; +} diff --git a/sbase/crypt.h b/sbase/crypt.h @@ -0,0 +1,12 @@ +/* See LICENSE file for copyright and license details. */ +struct crypt_ops { + void (*init)(void *); + void (*update)(void *, const void *, unsigned long); + void (*sum)(void *, uint8_t *); + void *s; +}; + +int cryptcheck(char *, int, char **, struct crypt_ops *, uint8_t *, size_t); +int cryptmain(int, char **, struct crypt_ops *, uint8_t *, size_t); +int cryptsum(struct crypt_ops *, FILE *, const char *, uint8_t *); +void mdprint(const uint8_t *, const char *, size_t); diff --git a/sbase/cut.1 b/sbase/cut.1 @@ -0,0 +1,70 @@ +.Dd January 18, 2015 +.Dt CUT 1 sbase\-VERSION +.Sh NAME +.Nm cut +.Nd extract columns of data +.Sh SYNOPSIS +.Nm cut +.Fl b Ar list +.Op Fl n +.Op Ar file ... +.Nm cut +.Fl c Ar list +.Op Ar file ... +.Nm cut +.Fl f Ar list +.Op Fl d Ar delim +.Op Fl s +.Op Ar file ... +.Sh DESCRIPTION +.Nm +out bytes, characters or delimited fields from each line of +.Ar file +and write to stdout. +.Pp +If no +.Ar file +is given or +.Ar file +is '-', +.Nm +reads from stdin. +.Pp +.Ar list +is a comma or space separated list of numbers and ranges starting +from 1. Ranges have the form 'N-M'. If N or M is missing, +beginning or end of line is assumed. Numbers and ranges +may be repeated, overlapping and in any order. +.Pp +Selected input is written in the same order it is read +and is written exactly once. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl b Ar list | Fl c Ar list +.Ar list +specifies byte | character positions. +.It Fl n +Do not split multibyte characters. A character is written when its +last byte is selected. +.It Fl f Ar list +.Ar list +specifies field numbers. Lines not containing field +delimiters are passed through, unless +.Fl s +is specified. +.It Fl d Ar delim +Use first byte of +.Ar delim +as field delimiter. Default is \et. +.It Fl s +Suppress lines not containing field delimiters. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. +.Pp +The possibility of separating numbers and ranges with a space +is an extension to that specification. diff --git a/sbase/cut.c b/sbase/cut.c @@ -0,0 +1,181 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "text.h" +#include "util.h" + +typedef struct Range { + size_t min, max; + struct Range *next; +} Range; + +static Range *list = NULL; +static char mode = 0; +static char delim = '\t'; +static int nflag = 0; +static int sflag = 0; + +static void +insert(Range *r) +{ + Range *l, *p, *t; + + for (p = NULL, l = list; l; p = l, l = l->next) { + if (r->max && r->max + 1 < l->min) { + r->next = l; + break; + } else if (!l->max || r->min < l->max + 2) { + l->min = MIN(r->min, l->min); + for (p = l, t = l->next; t; p = t, t = t->next) + if (r->max && r->max + 1 < t->min) + break; + l->max = (p->max && r->max) ? MAX(p->max, r->max) : 0; + l->next = t; + return; + } + } + if (p) + p->next = r; + else + list = r; +} + +static void +parselist(char *str) +{ + char *s; + size_t n = 1; + Range *r; + + for (s = str; *s; s++) { + if (*s == ' ') + *s = ','; + if (*s == ',') + n++; + } + r = emalloc(n * sizeof(Range)); + for (s = str; n; n--, s++) { + r->min = (*s == '-') ? 1 : strtoul(s, &s, 10); + r->max = (*s == '-') ? strtoul(s + 1, &s, 10) : r->min; + r->next = NULL; + if (!r->min || (r->max && r->max < r->min) || (*s && *s != ',')) + eprintf("cut: bad list value\n"); + insert(r++); + } +} + +static size_t +seek(const char *s, size_t pos, size_t *prev, size_t count) +{ + const char *t; + size_t n = pos - *prev; + + if (mode == 'b') { + if ((t = memchr(s, 0, n))) + return t - s; + if (nflag) + while (n && !UTF8_POINT(s[n])) + n--; + *prev += n; + return n; + } else if (mode == 'c') { + for (n++, t = s; *t; t++) + if (UTF8_POINT(*t) && !--n) + break; + } else { + for (t = (count < 2) ? s : s + 1; n && *t; t++) + if (*t == delim && !--n && count) + break; + } + *prev = pos; + return t - s; +} + +static void +cut(FILE *fp) +{ + static char *buf = NULL; + static size_t size = 0; + char *s; + size_t i, n, p; + ssize_t len; + Range *r; + + while ((len = getline(&buf, &size, fp)) != -1) { + if (len && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + if (mode == 'f' && !strchr(buf, delim)) { + if (!sflag) + puts(buf); + continue; + } + for (i = 0, p = 1, s = buf, r = list; r; r = r->next, s += n) { + s += seek(s, r->min, &p, i++); + if (!*s) + break; + if (!r->max) { + fputs(s, stdout); + break; + } + n = seek(s, r->max + 1, &p, i++); + if (fwrite(s, 1, n, stdout) != n) + eprintf("write error:"); + } + putchar('\n'); + } +} + +static void +usage(void) +{ + eprintf("usage: cut -b list [-n] [file ...]\n" + " cut -c list [file ...]\n" + " cut -f list [-d delim] [-s] [file ...]\n"); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + + ARGBEGIN { + case 'b': + case 'c': + case 'f': + mode = ARGC(); + parselist(ARGF()); + break; + case 'd': + delim = *ARGF(); + break; + case 'n': + nflag = 1; + break; + case 's': + sflag = 1; + break; + default: + usage(); + } ARGEND; + + if (!mode) + usage(); + + if (!argc) + cut(stdin); + else for (; argc--; argv++) { + if (!strcmp(*argv, "-")) + cut(stdin); + else { + if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + continue; + } + cut(fp); + fclose(fp); + } + } + return 0; +} diff --git a/sbase/date.1 b/sbase/date.1 @@ -0,0 +1,26 @@ +.TH DATE 1 sbase\-VERSION +.SH NAME +date \- print date and time +.SH SYNOPSIS +.B date +.RB [ \-d +.IR time ] +.RB [ \-u ] +.RI [+ format ] +.SH DESCRIPTION +.B date +prints the date and time. If a +.I format +is given it is used to format the date as per +.IR strftime (3). +.SH OPTIONS +.TP +.BI \-d " time" +prints +.I time +instead of the system time, given as the number of seconds since the Unix epoch. +.TP +.B \-u +prints UTC time instead of local time. +.SH SEE ALSO +.IR strftime (3) diff --git a/sbase/date.c b/sbase/date.c @@ -0,0 +1,46 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-u] [-d format] [+FORMAT]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char buf[BUFSIZ]; + char *fmt = "%c"; + struct tm *now = NULL; + struct tm *(*tztime)(const time_t *) = localtime; + const char *tz = "local"; + time_t t; + + t = time(NULL); + ARGBEGIN { + case 'd': + t = estrtol(EARGF(usage()), 0); + break; + case 'u': + tztime = gmtime; + tz = "gm"; + break; + default: + usage(); + } ARGEND; + if (argc > 0 && argv[0][0] == '+') + fmt = &argv[0][1]; + if (!(now = tztime(&t))) + eprintf("%stime failed\n", tz); + + strftime(buf, sizeof buf, fmt, now); + puts(buf); + + return 0; +} diff --git a/sbase/dirname.1 b/sbase/dirname.1 @@ -0,0 +1,14 @@ +.TH DIRNAME 1 sbase\-VERSION +.SH NAME +dirname \- strip final path component +.SH SYNOPSIS +.B dirname +.I pathname +.SH DESCRIPTION +.B dirname +prints the +.I pathname +with its final path component removed. +.SH SEE ALSO +.IR basename (1), +.IR dirname (3) diff --git a/sbase/dirname.c b/sbase/dirname.c @@ -0,0 +1,29 @@ +/* See LICENSE file for copyright and license details. */ +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s pathname\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + puts(dirname(argv[0])); + + return 0; +} diff --git a/sbase/du.1 b/sbase/du.1 @@ -0,0 +1,37 @@ +.TH DU 1 sbase\-VERSION +.SH NAME +du \- display disk usage statistics +.SH SYNOPSIS +.B du +.RB [ \-a +.RB | +.BR \-s ] +.RB [ \-d +.IR depth ] +.RB [ \-k ] +.RB [ \-h ] +.RI [ file ...] +.SH DESCRIPTION +.B du +displays the file system block usage for each +.I file +argument and for each directory in the file hierarchy rooted in directory argument. +If no file is specified, the block usage of the hierarchy rooted in the current +directory is displayed. +.SH OPTIONS +.TP +.BI \-a +Display an entry for each file in the file hierarchy. +.TP +.BI \-s +Display only the grand total for the specified files. +.TP +.BI "\-d depth" +Maximum directory depth to print files and directories. +.TP +.BI \-k +By default all sizes are reported in 512-byte block counts. +The -k option causes the numbers to be reported in kilobyte counts. +.TP +.BI \-h +Enable human readable output. diff --git a/sbase/du.c b/sbase/du.c @@ -0,0 +1,189 @@ +/* See LICENSE file for copyright and license details. */ +#include <dirent.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "util.h" + +static long blksize = 512; +static char file[PATH_MAX]; +static long depth = -1; +static long curdepth = 0; + +static int aflag = 0; +static int dflag = 0; +static int sflag = 0; +static int kflag = 0; +static int hflag = 0; + +static long du(const char *); +static void print(long n, char *path); + +static void +usage(void) +{ + eprintf("usage: %s [-a | -s] [-d depth] [-h] [-k] [file...]\n", argv0); +} + +static char * +xrealpath(const char *pathname, char *resolved) +{ + char *r; + + r = realpath(pathname, resolved); + if (!r) + eprintf("realpath: %s:", pathname); + return r; +} + +int +main(int argc, char *argv[]) +{ + char *bsize; + long n; + + ARGBEGIN { + case 'a': + aflag = 1; + break; + case 'd': + dflag = 1; + depth = estrtol(EARGF(usage()), 0); + break; + case 's': + sflag = 1; + break; + case 'k': + kflag = 1; + break; + case 'h': + hflag = 1; + break; + default: + usage(); + } ARGEND; + + if ((aflag && sflag) || (dflag && sflag)) + usage(); + + bsize = getenv("BLOCKSIZE"); + if (bsize) + blksize = estrtol(bsize, 0); + + if (kflag) + blksize = 1024; + + if (argc < 1) { + n = du("."); + if (sflag) + print(n, xrealpath(".", file)); + } else { + for (; argc > 0; argc--, argv++) { + curdepth = 0; + n = du(argv[0]); + if (sflag) + print(n, xrealpath(argv[0], file)); + } + } + return 0; +} + +static void +print(long n, char *path) +{ + if (hflag) + printf("%s\t%s\n", humansize(n * blksize), path); + else + printf("%lu\t%s\n", n, path); +} + +static char * +push(const char *path) +{ + char *cwd; + + cwd = agetcwd(); + if (chdir(path) < 0) + eprintf("chdir: %s:", path); + return cwd; +} + +static void +pop(char *path) +{ + if (chdir(path) < 0) + eprintf("chdir: %s:", path); + free(path); +} + +static long +nblks(struct stat *st) +{ + return (512 * st->st_blocks + blksize - 1) / blksize; +} + +static long +du(const char *path) +{ + DIR *dp; + char *cwd; + struct dirent *dent; + struct stat st; + long n = 0, m, t; + int r; + + if (lstat(path, &st) < 0) + eprintf("stat: %s:", path); + n = nblks(&st); + + if (!S_ISDIR(st.st_mode)) + goto done; + + dp = opendir(path); + if (!dp) { + weprintf("opendir %s:", path); + goto done; + } + + cwd = push(path); + while ((dent = readdir(dp))) { + if (strcmp(dent->d_name, ".") == 0 || + strcmp(dent->d_name, "..") == 0) + continue; + if (lstat(dent->d_name, &st) < 0) + eprintf("stat: %s:", dent->d_name); + if (S_ISDIR(st.st_mode)) { + t = curdepth; + curdepth++; + n += du(dent->d_name); + curdepth = t; + continue; + } + m = nblks(&st); + n += m; + if (aflag && !sflag) { + if (S_ISLNK(st.st_mode)) { + r = snprintf(file, sizeof(file), "%s/%s", + cwd, dent->d_name); + if (r >= sizeof(file) || r < 0) + eprintf("path too long\n"); + } else { + xrealpath(dent->d_name, file); + } + if (!dflag || (depth != -1 && curdepth < depth)) + print(m, file); + } + } + pop(cwd); + closedir(dp); + +done: + if (!sflag && (!dflag || (depth != -1 && curdepth <= depth))) + print(n, xrealpath(path, file)); + return n; +} diff --git a/sbase/echo.1 b/sbase/echo.1 @@ -0,0 +1,14 @@ +.TH ECHO 1 sbase\-VERSION +.SH NAME +echo \- print arguments +.SH SYNOPSIS +.B echo +.RB [ \-n ] +.RI [ string ...] +.SH DESCRIPTION +.B echo +prints its arguments to stdout, separated by spaces and terminated by a newline. +.SH OPTIONS +.TP +.B \-n +Do not print terminating newline. diff --git a/sbase/echo.c b/sbase/echo.c @@ -0,0 +1,32 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-n] text\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int nflag = 0; + + ARGBEGIN { + case 'n': + nflag = 1; + break; + default: + usage(); + } ARGEND; + + for (; argc > 0; argc--, argv++) + putword(argv[0]); + if (!nflag) + putchar('\n'); + + return 0; +} diff --git a/sbase/env.1 b/sbase/env.1 @@ -0,0 +1,41 @@ +.TH ENV 1 sbase\-VERSION +.SH NAME +env \- modify the environment, then print it or run a command. +.SH SYNOPSIS +.B env +.RB [ \-i ] +.RB [ \-u +.IR name ]... +.RI [ name=value ]... +.RI [ cmd +.RI [ arg ...]] + +.SH DESCRIPTION +.B env +removes part of the environment according to the flags, then adds or +sets each variable specified by +.IR name +to equal +.IR value . + +If +.IR cmd +is given, it is executed in this new environment; otherwise, the +modified environment is printed to standard out. + +.SH OPTIONS +.TP +.B \-i +Comptetely ignore the existing environment; start fresh. + +.TP +.B \-u name +Unsets +.IR name +from the environment. + +.SH SEE ALSO +.IR printenv (1) +.IR putenv (3) +.IR environ (7) + diff --git a/sbase/env.c b/sbase/env.c @@ -0,0 +1,45 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +extern char **environ; + +static void +usage(void) +{ + eprintf("usage: env [-i] [-u name]... [name=value]... [cmd [arg...]]\n"); +} + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + case 'i': + if(environ) + *environ = NULL; + break; + case 'u': + unsetenv(EARGF(usage())); + break; + default: + usage(); + } ARGEND; + + for (; *argv && strchr(*argv, '='); argv++) + putenv(*argv); + + if (*argv) { + execvp(*argv, argv); + enprintf(127 - (errno != EEXIST), "env: '%s':", *argv); + } + + while (environ && *environ) + printf("%s\n", *environ++); + + return 0; +} diff --git a/sbase/expand.1 b/sbase/expand.1 @@ -0,0 +1,25 @@ +.TH EXPAND 1 sbase\-VERSION +.SH NAME +expand \- expand tabs to spaces +.SH SYNOPSIS +.B expand +.RB [ \-t +.IR n ] +.RI [ file ...] +.SH DESCRIPTION +expand processes the named files or the standard input, writing the +standard output with tabs changed into spaces. Backspace characters +are preserved into the output and decrement the column count for tab +calculations. +.SH OPTIONS +.TP +.BI \-i +Only change tabs to spaces at the start of lines. +.TP +.BI \-t " n" +Expand tabs to +.I n +spaces. We currently support only a single numerical argument. +.SH SEE ALSO +.IR unexpand (1), +.IR fold (1) diff --git a/sbase/expand.c b/sbase/expand.c @@ -0,0 +1,98 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> + +#include "utf.h" +#include "util.h" + +static int expand(const char *, FILE *, int); + +static int iflag = 0; + +static void +usage(void) +{ + eprintf("usage: %s [-i] [-t n] [file...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + int tabstop = 8; + int ret = 0; + + ARGBEGIN { + case 'i': + iflag = 1; + break; + case 't': + tabstop = estrtol(EARGF(usage()), 0); + if (!tabstop) + eprintf("tab size cannot be zero\n"); + break; + default: + usage(); + } ARGEND; + + if (argc == 0) { + expand("<stdin>", stdin, tabstop); + } else { + for (; argc > 0; argc--, argv++) { + if (!(fp = fopen(argv[0], "r"))) { + weprintf("fopen %s:", argv[0]); + ret = 1; + continue; + } + expand(argv[0], fp, tabstop); + fclose(fp); + } + } + return ret; +} + +static int +expand(const char *file, FILE *fp, int tabstop) +{ + int col = 0; + Rune r; + int bol = 1; + + for (;;) { + if (!readrune(file, fp, &r)) + break; + + switch (r) { + case '\t': + if (bol || !iflag) { + do { + col++; + putchar(' '); + } while (col % tabstop); + } else { + putchar('\t'); + col += tabstop - col % tabstop; + } + break; + case '\b': + if (col) + col--; + bol = 0; + writerune("<stdout>", stdout, &r); + break; + case '\n': + col = 0; + bol = 1; + writerune("<stdout>", stdout, &r); + break; + default: + col++; + if (r != ' ') + bol = 0; + writerune("<stdout>", stdout, &r); + break; + } + } + + return 0; +} diff --git a/sbase/expr.1 b/sbase/expr.1 @@ -0,0 +1,156 @@ +.\" $OpenBSD: src/bin/expr/expr.1,v 1.22 2014/02/23 18:13:27 schwarze Exp $ +.\" $NetBSD: expr.1,v 1.9 1995/04/28 23:27:13 jtc Exp $ +.\" +.\" Written by J.T. Conklin <jtc@netbsd.org>. +.\" Public domain. +.\" +.Dd $Mdocdate: September 3 2010 $ +.Dt EXPR 1 +.Os +.Sh NAME +.Nm expr +.Nd evaluate expression +.Sh SYNOPSIS +.Nm expr +.Ar expression +.Sh DESCRIPTION +The +.Nm +utility evaluates +.Ar expression +and writes the result on standard output. +All operators are separate arguments to the +.Nm +utility. +Characters special to the command interpreter must be escaped. +.Pp +Operators are listed below in order of increasing precedence. +Operators with equal precedence are grouped within { } symbols. +.Bl -tag -width indent +.It Ar expr1 | expr2 +Returns the evaluation of +.Ar expr1 +if it is neither an empty string nor zero; +otherwise, returns the evaluation of +.Ar expr2 . +.It Ar expr1 Li & Ar expr2 +Returns the evaluation of +.Ar expr1 +if neither expression evaluates to an empty string or zero; +otherwise, returns zero. +.It Ar expr1 Li "{=, >, >=, <, <=, !=}" Ar expr2 +Returns the results of integer comparison if both arguments are integers; +otherwise, returns the results of string comparison using the locale-specific +collation sequence. +The result of each comparison is 1 if the specified relation is true, +or 0 if the relation is false. +.It Ar expr1 Li "{+, -}" Ar expr2 +Returns the results of addition or subtraction of integer-valued arguments. +.It Ar expr1 Li "{*, /, %}" Ar expr2 +Returns the results of multiplication, integer division, or remainder of +integer-valued arguments. +.It Ar expr1 Li : Ar expr2 +The +.Ql \&: +operator matches +.Ar expr1 +against +.Ar expr2 , +which must be a basic regular expression. +The regular expression is anchored +to the beginning of the string with an implicit +.Ql ^ . +.Pp +If the match succeeds and the pattern contains at least one regular +expression subexpression +.Dq "\e(...\e)" , +the string corresponding to +.Dq "\e1" +is returned; +otherwise, the matching operator returns the number of characters matched. +If the match fails and the pattern contains a regular expression subexpression +the null string is returned; +otherwise, returns 0. +.Pp +Note: the empty string cannot be matched using +.Bd -literal -offset indent +expr '' : '$' +.Ed +.Pp +This is because the returned number of matched characters +.Pq zero +is indistinguishable from a failed match, so +.Nm +returns failure +.Pq 0 . +To match the empty string, use a structure such as: +.Bd -literal -offset indent +expr X'' : 'X$' +.Ed +.El +.Pp +Parentheses are used for grouping in the usual manner. +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It 0 +The expression is neither an empty string nor 0. +.It 1 +The expression is an empty string or 0. +.It 2 +The expression is invalid. +.It \*(Gt2 +An error occurred (such as memory allocation failure). +.El +.Sh EXAMPLES +Add 1 to the variable +.Va a : +.Bd -literal -offset indent +$ a=`expr $a + 1` +.Ed +.Pp +Return the filename portion of a pathname stored +in variable +.Va a . +The +.Ql // +characters act to eliminate ambiguity with the division operator: +.Bd -literal -offset indent +$ expr "//$a" \&: '.*/\e(.*\e)' +.Ed +.Pp +Return the number of characters in variable +.Va a : +.Bd -literal -offset indent +$ expr $a \&: '.*' +.Ed +.Sh SEE ALSO +.Xr test 1 , +.Xr re_format 7 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. +.Sh HISTORY +The +.Nm +utility first appeared in the Programmer's Workbench (PWB/UNIX) +and has supported regular expressions since +.At v7 . +It was rewritten from scratch for +.Bx 386 0.1 +and again for +.Nx 1.1 . +.Sh AUTHORS +.An -nosplit +The first free version was written by +.An Pace Willisson +in 1992. +This version was written by +.An John T. Conklin +in 1994. diff --git a/sbase/expr.c b/sbase/expr.c @@ -0,0 +1,287 @@ +/* See LICENSE file for copyright and license details. */ +#include <inttypes.h> +#include <limits.h> +#include <regex.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +enum { + VAL = CHAR_MAX + 1, GE, LE, NE +}; + +typedef struct { + char *s; + intmax_t n; +} Val; + +static void enan(Val); +static void ezero(intmax_t); +static void doop(int*, int**, Val*, Val**); +static Val match(Val, Val); +static int valcmp(Val, Val); +static char *valstr(Val, char *, size_t); +static int lex(char *); +static int parse(char **, int); + +static size_t intlen; +static Val lastval; + +static void +enan(Val v) +{ + if (v.s) + enprintf(2, "syntax error: expected integer got `%s'\n", v.s); +} + +static void +ezero(intmax_t n) +{ + if (n == 0) + enprintf(2, "division by zero\n"); +} + +static void +doop(int *op, int **opp, Val *val, Val **valp) +{ + Val ret, a, b; + int o; + + /* For an operation, we need a valid operator + * and two values on the stack */ + if ((*opp)[-1] == '(') + enprintf(2, "syntax error: extra (\n"); + if (*valp - val < 2) + enprintf(2, "syntax error: missing expression or extra operator\n"); + + a = (*valp)[-2]; + b = (*valp)[-1]; + o = (*opp)[-1]; + + switch (o) { + case '|': + if (a.s && *a.s) { + ret = (Val){ a.s, 0 }; + } else if (!a.s && a.n) { + ret = (Val){ NULL, a.n }; + } else if (b.s && *b.s) { + ret = (Val){ b.s, 0 }; + } else { + ret = (Val){ NULL, b.n }; + } + break; + case '&': + if (((a.s && *a.s) || a.n) && + ((b.s && *b.s) || b.n)) { + ret = a; + } else { + ret = (Val){ NULL, 0 }; + } + break; + case '=': ret = (Val){ NULL, valcmp(a, b) == 0 }; break; + case '>': ret = (Val){ NULL, valcmp(a, b) > 0 }; break; + case GE : ret = (Val){ NULL, valcmp(a, b) >= 0 }; break; + case '<': ret = (Val){ NULL, valcmp(a, b) < 0 }; break; + case LE : ret = (Val){ NULL, valcmp(a, b) <= 0 }; break; + case NE : ret = (Val){ NULL, valcmp(a, b) != 0 }; break; + + case '+': enan(a); enan(b); ret = (Val){ NULL, a.n + b.n }; break; + case '-': enan(a); enan(b); ret = (Val){ NULL, a.n - b.n }; break; + case '*': enan(a); enan(b); ret = (Val){ NULL, a.n * b.n }; break; + case '/': enan(a); enan(b); ezero(b.n); ret = (Val){ NULL, a.n / b.n }; break; + case '%': enan(a); enan(b); ezero(b.n); ret = (Val){ NULL, a.n % b.n }; break; + + case ':': ret = match(a, b); break; + } + + (*valp)[-2] = ret; + (*opp)--; + (*valp)--; +} + +static Val +match(Val vstr, Val vregx) +{ + intmax_t d; + char *anchreg, *ret, *p; + char buf1[intlen], buf2[intlen], *str, *regx; + regoff_t len; + regex_t re; + regmatch_t matches[2]; + + str = valstr(vstr, buf1, sizeof(buf1)); + regx = valstr(vregx, buf2, sizeof(buf2)); + + anchreg = malloc(strlen(regx) + 2); + if (!anchreg) + enprintf(3, "malloc:"); + snprintf(anchreg, strlen(regx) + 2, "^%s", regx); + + enregcomp(3, &re, anchreg, 0); + free(anchreg); + + if (regexec(&re, str, 2, matches, 0)) { + regfree(&re); + return (Val){ (re.re_nsub ? "" : NULL), 0 }; + } + + if (re.re_nsub) { + regfree(&re); + len = matches[1].rm_eo - matches[1].rm_so + 1; + ret = malloc(len); + if (!ret) + enprintf(3, "malloc:"); + strlcpy(ret, str + matches[1].rm_so, len); + d = strtoimax(ret, &p, 10); + if (*ret && !*p) { + free(ret); + return (Val){ NULL, d }; + } + return (Val){ ret, 0 }; + } + regfree(&re); + return (Val){ NULL, matches[0].rm_eo - matches[0].rm_so }; +} + + + +static int +valcmp(Val a, Val b) +{ + char buf1[intlen], buf2[intlen], *astr, *bstr; + + astr = valstr(a, buf1, sizeof(buf1)); + bstr = valstr(b, buf2, sizeof(buf2)); + + return strcmp(astr, bstr); +} + +static char * +valstr(Val val, char *buf, size_t bufsiz) +{ + if (val.s) + return val.s; + snprintf(buf, bufsiz, "%"PRIdMAX, val.n); + return buf; +} + +static int +lex(char *p) +{ + intmax_t d; + char *q, *ops = "|&=><+-*/%():"; + + /* clean integer */ + d = strtoimax(p, &q, 10); + if (*p && !*q) { + lastval = (Val){ NULL, d }; + return VAL; + } + + /* one-char operand */ + if (*p && !*(p+1) && strchr(ops, *p)) + return *p; + + /* two-char operand */ + if (*p && *(p+1) == '=' && !*(p+2)) { + switch (*p) { + case '>': + return GE; + case '<': + return LE; + case '!': + return NE; + } + } + + /* nothing matched, treat as string */ + lastval = (Val){ p, 0 }; + return VAL; +} + +static int +parse(char **expr, int exprlen) +{ + Val val[exprlen], *valp = val; + int op[exprlen], *opp = op; + int i, type, lasttype = 0; + char prec[] = { + [ 0 ] = 0, [VAL] = 0, + ['|'] = 1, + ['&'] = 2, + ['='] = 3, ['>'] = 3, [GE] = 3, ['<'] = 3, [LE] = 3, [NE] = 3, + ['+'] = 4, ['-'] = 4, + ['*'] = 5, ['/'] = 5, ['%'] = 5, + [':'] = 6, + }; + + for (i = 0; i < exprlen; i++) { + type = lex(expr[i]); + + switch (type) { + case VAL: + *valp++ = lastval; + break; + case '(': + *opp++ = '('; + break; + case ')': + if (lasttype == '(') + enprintf(2, "syntax error: empty ( )\n"); + while (opp > op && opp[-1] != '(') + doop(op, &opp, val, &valp); + if (opp == op) + enprintf(2, "syntax error: extra )\n"); + opp--; + break; + default : + if (prec[lasttype]) + enprintf(2, "syntax error: extra operator\n"); + while (opp > op && prec[opp[-1]] >= prec[type]) + doop(op, &opp, val, &valp); + *opp++ = type; + break; + } + lasttype = type; + } + while (opp > op) + doop(op, &opp, val, &valp); + + if (valp == val) + enprintf(2, "syntax error: missing expression\n"); + if (valp - val > 1) + enprintf(2, "syntax error: extra expression\n"); + + valp--; + if (valp->s) + printf("%s\n", valp->s); + else + printf("%"PRIdMAX"\n", valp->n); + + return (valp->s && *valp->s) || valp->n; +} + +static void +usage(void) +{ + eprintf("usage: %s EXPRESSION\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + intmax_t n = INTMAX_MIN; + + /* Get the maximum number of digits (+ sign) */ + for (intlen = (n < 0); n; n /= 10, ++intlen); + + ARGBEGIN { + default: + usage(); + } ARGEND; + + return !parse(argv, argc); +} diff --git a/sbase/false.1 b/sbase/false.1 @@ -0,0 +1,16 @@ +.Dd January 16, 2015 +.Dt FALSE 1 sbase\-VERSION +.Sh NAME +.Nm false +.Nd return failure +.Sh SYNOPSIS +.Nm false +.Sh DESCRIPTION +.Nm +returns a status code indicating failure. +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. diff --git a/sbase/false.c b/sbase/false.c @@ -0,0 +1,6 @@ +/* See LICENSE file for copyright and license details. */ +int +main(void) +{ + return 1; +} diff --git a/sbase/fold.1 b/sbase/fold.1 @@ -0,0 +1,25 @@ +.TH FOLD 1 sbase\-VERSION +.SH NAME +fold \- wrap lines to width +.SH SYNOPSIS +.B fold +.RB [ \-bs ] +.RB [ \-w +.IR width ] +.RI [ file ...] +.SH DESCRIPTION +.B fold +reads each file in sequence and prints its lines, broken such that no line +exceeds 80 UTF-8 characters. If no file is given, fold reads from stdin. +.SH OPTIONS +.TP +.B \-b +counts bytes rather than characters. +.TP +.B \-s +breaks only at spaces. +.TP +.BI \-w " width" +breaks at +.I width +characters, instead of 80. diff --git a/sbase/fold.c b/sbase/fold.c @@ -0,0 +1,113 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "text.h" +#include "util.h" + +static void fold(FILE *, long); +static void foldline(const char *, long); + +static int bflag = 0; +static int sflag = 0; + +static void +usage(void) +{ + eprintf("usage: %s [-bs] [-w width] [FILE...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + long width = 80; + FILE *fp; + + ARGBEGIN { + case 'b': + bflag = 1; + break; + case 's': + sflag = 1; + break; + case 'w': + width = estrtol(EARGF(usage()), 0); + break; + ARGNUM: + width = ARGNUMF(0); + break; + default: + usage(); + } ARGEND; + + if (argc == 0) { + fold(stdin, width); + } else { + for (; argc > 0; argc--, argv++) { + if (!(fp = fopen(argv[0], "r"))) { + weprintf("fopen %s:", argv[0]); + continue; + } + fold(fp, width); + fclose(fp); + } + } + + return 0; +} + +static void +fold(FILE *fp, long width) +{ + char *buf = NULL; + size_t size = 0; + + while (getline(&buf, &size, fp) != -1) + foldline(buf, width); + free(buf); +} + +static void +foldline(const char *str, long width) +{ + int space; + long col, j; + size_t i = 0, n = 0; + int c; + + do { + space = 0; + for (j = i, col = 0; str[j] && col <= width; j++) { + c = str[j]; + if (!UTF8_POINT(c) && !bflag) + continue; + if (sflag && isspace(c)) { + space = 1; + n = j+1; + } + else if (!space) + n = j; + + if (!bflag && iscntrl(c)) + switch(c) { + case '\b': + col--; + break; + case '\r': + col = 0; + break; + case '\t': + col += (col+1) % 8; + break; + } + else + col++; + } + if (fwrite(&str[i], 1, n-i, stdout) != n-i) + eprintf("<stdout>: write error:"); + if (str[n]) + putchar('\n'); + } while (str[i = n] && str[i] != '\n'); +} diff --git a/sbase/fs.h b/sbase/fs.h @@ -0,0 +1,14 @@ +/* See LICENSE file for copyright and license details. */ +extern int cp_aflag; +extern int cp_fflag; +extern int cp_Pflag; +extern int cp_pflag; +extern int cp_rflag; +extern int cp_vflag; +extern int cp_status; + +extern int rm_fflag; +extern int rm_rflag; + +int cp(const char *, const char *); +void rm(const char *); diff --git a/sbase/grep.1 b/sbase/grep.1 @@ -0,0 +1,86 @@ +.Dd November 21, 2014 +.Dt GREP 1 sbase\-VERSION +.Sh NAME +.Nm grep +.Nd search files for patterns +.Sh SYNOPSIS +.Nm grep +.Op Fl EFHchilnqsvx +.Op Fl e Ar pattern +.Op Fl f Ar file +.Op Ar pattern +.Op Ar file ... +.Sh DESCRIPTION +.Nm +searches the input files for lines that match the +.Ar pattern , +a regular expression as defined in +.Xr regex 7 . +By default each matching line is printed to stdout. If no file is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl E +Match using extended regex. +.It Fl F +Match using fixed strings. Treat each pattern specified as a string instead of a regular +expression. +.It Fl H +Prefix each matching line with its filename in the output. This is the +default when there is more than one file specified. +.It Fl c +Print only a count of matching lines. +.It Fl e 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 -e options are used to +specify multiple patterns, or when a pattern begins with a dash +.It Fl f Ar file +Read one or more patterns from the file named by the pathname file. +Patterns in file shall be terminated by a <newline>. A null pattern can be +specified by an empty line in pattern_file. Unless the -E or -F option is +also specified, each pattern shall be treated as a BRE. +(`-'). +.It Fl h +Do not prefix each line with 'filename:' prefix. +.It Fl i +Match lines case insensitively. +.It Fl l +Print only the names of files with matching lines. +.It Fl n +Prefix each matching line with its line number in the input. +.It Fl q +Print nothing, only return status. +.It Fl s +Suppress the error messages ordinarily written for nonexistent or unreadable files. +.It Fl v +Select lines which do +.B not +Match the pattern. +.It Fl x +Consider only input lines that use all characters in the line excluding the terminating <newline> to +match an entire fixed string or regular expression to be matching lines. +.El +.Sh EXIT STATUS +.Bl -tag -width Ds +.It 0 +One or more lines were matched. +.It 1 +No lines were matched. +.It > 1 +An error occurred. +.El +.Sh SEE ALSO +.Xr regex 7 , +.Xr sed 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. +.Pp +The flags +.Op Fl Hh +are an extension to that specification. diff --git a/sbase/grep.c b/sbase/grep.c @@ -0,0 +1,255 @@ +/* See LICENSE file for copyright and license details. */ +#include <regex.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> + +#include "queue.h" +#include "text.h" +#include "util.h" + +enum { Match = 0, NoMatch = 1, Error = 2 }; + +static void addpattern(const char *); +static void addpatternfile(FILE *); +static int grep(FILE *, const char *); + +static int Fflag; +static int Hflag; +static int eflag; +static int fflag; +static int hflag; +static int iflag; +static int sflag; +static int vflag; +static int xflag; +static int many; +static int mode; + +struct pattern { + char *pattern; + regex_t preg; + SLIST_ENTRY(pattern) entry; +}; + +static SLIST_HEAD(phead, pattern) phead; + +static void +usage(void) +{ + enprintf(Error, "usage: %s [-EFHchilnqsvx] [-e pattern] [-f file] [pattern] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct pattern *pnode; + int i, m, flags = REG_NOSUB, match = NoMatch; + FILE *fp; + char *arg; + + SLIST_INIT(&phead); + + ARGBEGIN { + case 'E': + flags |= REG_EXTENDED; + break; + case 'F': + Fflag = 1; + break; + case 'H': + Hflag = 1; + hflag = 0; + break; + case 'e': + arg = EARGF(usage()); + fp = fmemopen(arg, strlen(arg) + 1, "r"); + addpatternfile(fp); + fclose(fp); + eflag = 1; + break; + case 'f': + arg = EARGF(usage()); + fp = fopen(arg, "r"); + if (!fp) + enprintf(Error, "fopen %s:", arg); + addpatternfile(fp); + fclose(fp); + fflag = 1; + break; + case 'h': + hflag = 1; + Hflag = 0; + break; + case 'c': + case 'l': + case 'n': + case 'q': + mode = ARGC(); + break; + case 'i': + flags |= REG_ICASE; + iflag = 1; + break; + case 's': + sflag = 1; + break; + case 'v': + vflag = 1; + break; + case 'x': + xflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc == 0 && !eflag && !fflag) + usage(); /* no pattern */ + + /* just add literal pattern to list */ + if (!eflag && !fflag) { + fp = fmemopen(argv[0], strlen(argv[0]) + 1, "r"); + addpatternfile(fp); + fclose(fp); + argc--; + argv++; + } + + if (!Fflag) + /* Compile regex for all search patterns */ + SLIST_FOREACH(pnode, &phead, entry) + enregcomp(Error, &pnode->preg, pnode->pattern, flags); + many = (argc > 1); + if (argc == 0) { + match = grep(stdin, "<stdin>"); + } else { + for (i = 0; i < argc; i++) { + if (!(fp = fopen(argv[i], "r"))) { + if (!sflag) + weprintf("fopen %s:", argv[i]); + match = Error; + continue; + } + m = grep(fp, argv[i]); + if (m == Error || (match != Error && m == Match)) + match = m; + fclose(fp); + } + } + return match; +} + +static void +addpattern(const char *pattern) +{ + struct pattern *pnode; + char *tmp; + + /* a null BRE/ERE matches every line */ + if (!Fflag) + if (pattern[0] == '\0') + pattern = "."; + + if (!Fflag && xflag) { + tmp = malloc(strlen(pattern) + 3); + if (!tmp) + enprintf(Error, "malloc:"); + snprintf(tmp, strlen(pattern) + 3, "%s%s%s", + pattern[0] == '^' ? "" : "^", + pattern, + pattern[strlen(pattern) - 1] == '$' ? "" : "$"); + } else { + tmp = strdup(pattern); + if (!tmp) + enprintf(Error, "strdup:"); + } + + pnode = malloc(sizeof(*pnode)); + if (!pnode) + enprintf(Error, "malloc:"); + pnode->pattern = tmp; + SLIST_INSERT_HEAD(&phead, pnode, entry); +} + +static void +addpatternfile(FILE *fp) +{ + static char *buf = NULL; + static size_t size = 0; + size_t len = 0; + + while ((len = getline(&buf, &size, fp)) != -1) { + if (len && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + addpattern(buf); + } + if (ferror(fp)) + enprintf(Error, "read error:"); +} + +static int +grep(FILE *fp, const char *str) +{ + static char *buf = NULL; + static size_t size = 0; + size_t len = 0; + long c = 0, n; + struct pattern *pnode; + int match = NoMatch; + + for (n = 1; (len = getline(&buf, &size, fp)) != -1; n++) { + /* Remove the trailing newline if one is present. */ + if (len && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + SLIST_FOREACH(pnode, &phead, entry) { + if (!Fflag) { + if (regexec(&pnode->preg, buf[0] == '\0' ? "\n" : buf, 0, NULL, 0) ^ vflag) + continue; + } else { + if (!xflag) { + if ((iflag ? strcasestr : strstr)(buf, pnode->pattern)) + match = Match; + else + match = NoMatch; + } else { + if (!(iflag ? strcasecmp : strcmp)(buf, pnode->pattern)) + match = Match; + else + match = NoMatch; + } + if (match ^ vflag) + continue; + } + switch (mode) { + case 'c': + c++; + break; + case 'l': + puts(str); + goto end; + case 'q': + exit(Match); + default: + if (!hflag && (many || Hflag)) + printf("%s:", str); + if (mode == 'n') + printf("%ld:", n); + puts(buf); + break; + } + match = Match; + break; + } + } + if (mode == 'c') + printf("%ld\n", c); +end: + if (ferror(fp)) { + weprintf("%s: read error:", str); + match = Error; + } + return match; +} diff --git a/sbase/head.1 b/sbase/head.1 @@ -0,0 +1,18 @@ +.TH HEAD 1 sbase\-VERSION +.SH NAME +head \- output first part of files +.SH SYNOPSIS +.B head +.RB [ \-n +.IR lines ] +.RI [ file ...] +.SH DESCRIPTION +.B head +writes the first 10 lines of each file to stdout. If no file is given, head +reads from stdin. +.SH OPTIONS +.TP +.BI \-n " lines" +outputs the given number of lines. +.SH SEE ALSO +.IR tail (1) diff --git a/sbase/head.c b/sbase/head.c @@ -0,0 +1,74 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "text.h" +#include "util.h" + +static void head(FILE *, const char *, long); + +static void +usage(void) +{ + eprintf("usage: %s [-n lines] [file...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + long n = 10; + FILE *fp; + int ret = 0; + int newline, many; + + ARGBEGIN { + case 'n': + n = estrtol(EARGF(usage()), 0); + break; + ARGNUM: + n = ARGNUMF(0); + break; + default: + usage(); + } ARGEND; + + if (argc == 0) { + head(stdin, "<stdin>", n); + } else { + many = argc > 1; + for (newline = 0; argc > 0; argc--, argv++) { + if (!(fp = fopen(argv[0], "r"))) { + weprintf("fopen %s:", argv[0]); + ret = 1; + continue; + } + if (many) + printf("%s==> %s <==\n", + newline ? "\n" : "", argv[0]); + newline = 1; + head(fp, argv[0], n); + fclose(fp); + } + } + return ret; +} + +static void +head(FILE *fp, const char *str, long n) +{ + char *buf = NULL; + size_t size = 0; + ssize_t len; + unsigned long i = 0; + + while (i < n && ((len = getline(&buf, &size, fp)) != -1)) { + fputs(buf, stdout); + if (buf[len - 1] == '\n') + i++; + } + free(buf); + if (ferror(fp)) + eprintf("%s: read error:", str); +} diff --git a/sbase/hostname.1 b/sbase/hostname.1 @@ -0,0 +1,12 @@ +.TH HOSTNAME 1 sbase\-VERSION +.SH NAME +hostname \- set or print name of current host system +.SH SYNOPSIS +.B hostname +.I [name] +.SH DESCRIPTION +.B hostname +sets or prints the name of the current host. If no argument is given, +the name of the current host is printed. +.SH SEE ALSO +.IR hostname (7) diff --git a/sbase/hostname.c b/sbase/hostname.c @@ -0,0 +1,34 @@ +/* See LICENSE file for copyright and license details. */ +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [name]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char host[HOST_NAME_MAX + 1]; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc < 1) { + if (gethostname(host, sizeof(host)) < 0) + eprintf("gethostname:"); + puts(host); + } else { + if (sethostname(argv[0], strlen(argv[0])) < 0) + eprintf("sethostname:"); + } + return 0; +} diff --git a/sbase/kill.1 b/sbase/kill.1 @@ -0,0 +1,59 @@ +.Dd November 23, 2014 +.Dt KILL 1 sbase\-VERSION +.Os +.Sh NAME +.Nm kill +.Nd signal processes +.Sh SYNOPSIS +.Nm kill +.Op Fl s Ar signal_name +.Ar pid ... +.Nm kill +.Fl l Op Ar exit_status +.Nm kill +.Fl Ar signal_name +.Ar pid ... +.Nm kill +.Fl Ar signal_number +.Ar pid ... +.Sh DESCRIPTION +.Nm +by default sends a +.I TERM +signal to the given processes. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl s Ar signal_name +A symbolic signal name specifying the signal to be sent instead +of the default SIGTERM. +Sends the named signal. +.It Fl l Op Ar exit_status +Lists available signals. If an +.Ar exit_status +is given, only the corresponding signal name will be printed. +.It Fl signal_name +A symbolic signal name specifying the signal to be sent instead +of the default SIGTERM. +.It Fl signal_number +A non-negative decimal integer specifying the signal to be sent +instead of the default SIGTERM. +.El +.Sh SEE ALSO +.Xr kill 2 , +.Xr signal 7 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. +.Pp +The +.Fl Ar signal_name +and +.Fl Ar signal_number +syntax is marked by +.St -p1003.1-2008 +as being an +X/OPEN System Interfaces +option. diff --git a/sbase/kill.c b/sbase/kill.c @@ -0,0 +1,138 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "util.h" + +struct { + const char *name; + int sig; +} sigs[] = { + { "0", 0 }, +#define SIG(n) { #n, SIG##n } + SIG(ABRT), SIG(ALRM), SIG(BUS), SIG(CHLD), SIG(CONT), SIG(FPE), SIG(HUP), + SIG(ILL), SIG(INT), SIG(KILL), SIG(PIPE), SIG(QUIT), SIG(SEGV), SIG(STOP), + SIG(TERM), SIG(TSTP), SIG(TTIN), SIG(TTOU), SIG(USR1), SIG(USR2), SIG(URG), +#undef SIG +}; + +const char *sig2name(int); +int name2sig(const char *); + +static void +usage(void) +{ + weprintf("usage: %s [-s signame | -signum | -signame] pid ...\n", argv0); + weprintf(" %s -l [exit_status]\n", argv0); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + char *end; + int ret = 0; + int sig = SIGTERM; + pid_t pid; + size_t i; + + argv0 = argv[0]; + if (argc < 2) + usage(); + + argc--, argv++; + if (strcmp(argv[0], "-l") == 0) { + argc--, argv++; + if (argc == 0) { + for (i = 0; i < LEN(sigs); i++) + puts(sigs[i].name); + exit(0); + } else if (argc > 1) + usage(); + errno = 0; + sig = strtol(argv[0], &end, 10); + if (*end != '\0' || errno != 0) + eprintf("%s: bad signal number\n", argv[0]); + if (sig > 128) + sig = WTERMSIG(sig); + puts(sig2name(sig)); + exit(0); + } + + if (strcmp(argv[0], "-s") == 0) { + argc--, argv++; + if (argc == 0) + usage(); + sig = name2sig(argv[0]); + argc--, argv++; + } else if (argv[0][0] == '-') { + if (isdigit(argv[0][1])) { + /* handle XSI extension -signum */ + errno = 0; + sig = strtol(&argv[0][1], &end, 10); + if (*end != '\0' || errno != 0) + eprintf("%s: bad signal number\n", &argv[0][1]); + sig2name(sig); + argc--, argv++; + } else if (argv[0][1] != '-') { + /* handle XSI extension -signame */ + sig = name2sig(&argv[0][1]); + argc--, argv++; + } + } + + if (argc > 0 && strcmp(argv[0], "--") == 0) + argc--, argv++; + + if (argc == 0) + usage(); + + for (; argc; argc--, argv++) { + errno = 0; + pid = strtol(argv[0], &end, 10); + if (*end == '\0' && errno == 0) { + if (kill(pid, sig) < 0) { + weprintf("kill %d:", pid); + ret = 1; + } + } else { + weprintf("%s: bad pid\n", argv[0]); + ret = 1; + } + } + + exit(ret); +} + +const char * +sig2name(int sig) +{ + size_t i; + + for (i = 0; i < LEN(sigs); i++) + if (sigs[i].sig == sig) + return sigs[i].name; + eprintf("%d: bad signal number\n", sig); + /* unreachable */ + return NULL; +} + +int +name2sig(const char *name) +{ + size_t i; + + for (i = 0; i < LEN(sigs); i++) + if (strcasecmp(sigs[i].name, name) == 0) + return sigs[i].sig; + eprintf("%s: bad signal name\n", name); + /* unreachable */ + return -1; +} diff --git a/sbase/libutf/chartorunearr.c b/sbase/libutf/chartorunearr.c @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdlib.h> +#include <string.h> + +#include "../util.h" +#include "../utf.h" + +int +chartorunearr(const char *str, Rune **r) +{ + size_t len = strlen(str), rlen, roff, ret = 1, i; + Rune s; + + for (rlen = 0, roff = 0; roff < len && ret; rlen++) { + ret = charntorune(&s, str + roff, MAX(UTFmax, len - roff)); + roff += ret; + } + + *r = emalloc(rlen * sizeof(Rune) + 1); + (*r)[rlen] = 0; + + for (i = 0, roff = 0; roff < len && i < rlen; i++) { + roff += charntorune(&(*r)[i], str + roff, MAX(UTFmax, len - roff)); + } + + return rlen; +} diff --git a/sbase/libutf/readrune.c b/sbase/libutf/readrune.c @@ -0,0 +1,46 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../utf.h" + +int +readrune(const char *file, FILE *fp, Rune *r) +{ + char buf[UTFmax]; + int c, i; + + if ((c = fgetc(fp)) == EOF) { + if (ferror(fp)) { + fprintf(stderr, "%s: read error: %s\n", + file, strerror(errno)); + exit(1); + } + return 0; + } + + if (c < Runeself) { + *r = (Rune)c; + return 1; + } + + buf[0] = c; + for (i = 1; ;) { + if ((c = fgetc(fp)) == EOF) { + if (ferror(fp)) { + fprintf(stderr, "%s: read error: %s\n", + file, strerror(errno)); + exit(1); + } + return 0; + } + buf[i++] = c; + if (fullrune(buf, i)) { + chartorune(r, buf); + break; + } + } + return 1; +} diff --git a/sbase/libutf/rune.c b/sbase/libutf/rune.c @@ -0,0 +1,148 @@ +/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "../utf.h" + +#define MIN(x,y) ((x) < (y) ? (x) : (y)) + +#define UTFSEQ(x) ((((x) & 0x80) == 0x00) ? 1 /* 0xxxxxxx */ \ + : (((x) & 0xC0) == 0x80) ? 0 /* 10xxxxxx */ \ + : (((x) & 0xE0) == 0xC0) ? 2 /* 110xxxxx */ \ + : (((x) & 0xF0) == 0xE0) ? 3 /* 1110xxxx */ \ + : (((x) & 0xF8) == 0xF0) ? 4 /* 11110xxx */ \ + : (((x) & 0xFC) == 0xF8) ? 5 /* 111110xx */ \ + : (((x) & 0xFE) == 0xFC) ? 6 /* 1111110x */ \ + : 0 ) + +#define BADRUNE(x) ((x) < 0 || (x) > Runemax \ + || ((x) & 0xFFFE) == 0xFFFE \ + || ((x) >= 0xD800 && (x) <= 0xDFFF) \ + || ((x) >= 0xFDD0 && (x) <= 0xFDEF)) + +int +runetochar(char *s, const Rune *p) +{ + Rune r = *p; + + switch(runelen(r)) { + case 1: /* 0aaaaaaa */ + s[0] = r; + return 1; + case 2: /* 00000aaa aabbbbbb */ + s[0] = 0xC0 | ((r & 0x0007C0) >> 6); /* 110aaaaa */ + s[1] = 0x80 | (r & 0x00003F); /* 10bbbbbb */ + return 2; + case 3: /* aaaabbbb bbcccccc */ + s[0] = 0xE0 | ((r & 0x00F000) >> 12); /* 1110aaaa */ + s[1] = 0x80 | ((r & 0x000FC0) >> 6); /* 10bbbbbb */ + s[2] = 0x80 | (r & 0x00003F); /* 10cccccc */ + return 3; + case 4: /* 000aaabb bbbbcccc ccdddddd */ + s[0] = 0xF0 | ((r & 0x1C0000) >> 18); /* 11110aaa */ + s[1] = 0x80 | ((r & 0x03F000) >> 12); /* 10bbbbbb */ + s[2] = 0x80 | ((r & 0x000FC0) >> 6); /* 10cccccc */ + s[3] = 0x80 | (r & 0x00003F); /* 10dddddd */ + return 4; + default: + return 0; /* error */ + } +} + +int +chartorune(Rune *p, const char *s) +{ + return charntorune(p, s, UTFmax); +} + +int +charntorune(Rune *p, const char *s, size_t len) +{ + unsigned int i, n; + Rune r; + + if(len == 0) /* can't even look at s[0] */ + return 0; + + switch((n = UTFSEQ(s[0]))) { + case 1: r = s[0]; break; /* 0xxxxxxx */ + case 2: r = s[0] & 0x1F; break; /* 110xxxxx */ + case 3: r = s[0] & 0x0F; break; /* 1110xxxx */ + case 4: r = s[0] & 0x07; break; /* 11110xxx */ + case 5: r = s[0] & 0x03; break; /* 111110xx */ + case 6: r = s[0] & 0x01; break; /* 1111110x */ + default: /* invalid sequence */ + *p = Runeerror; + return 1; + } + /* add values from continuation bytes */ + for(i = 1; i < MIN(n, len); i++) + if((s[i] & 0xC0) == 0x80) { + /* add bits from continuation byte to rune value + * cannot overflow: 6 byte sequences contain 31 bits */ + r = (r << 6) | (s[i] & 0x3F); /* 10xxxxxx */ + } + else { /* expected continuation */ + *p = Runeerror; + return i; + } + + if(i < n) /* must have reached len limit */ + return 0; + + /* reject invalid or overlong sequences */ + if(BADRUNE(r) || runelen(r) < (int)n) + r = Runeerror; + + *p = r; + return n; +} + +int +runelen(Rune r) +{ + if(BADRUNE(r)) + return 0; /* error */ + else if(r <= 0x7F) + return 1; + else if(r <= 0x07FF) + return 2; + else if(r <= 0xFFFF) + return 3; + else + return 4; +} + +size_t +runenlen(const Rune *p, size_t len) +{ + size_t i, n = 0; + + for(i = 0; i < len; i++) + n += runelen(p[i]); + return n; +} + +int +fullrune(const char *s, size_t len) +{ + Rune r; + + return charntorune(&r, s, len) > 0; +} diff --git a/sbase/libutf/runetype.c b/sbase/libutf/runetype.c @@ -0,0 +1,48 @@ +/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include <stdlib.h> +#include "../utf.h" + +#define nelem(x) (sizeof (x) / sizeof *(x)) + +static int rune1cmp(const void *, const void *); +static int rune2cmp(const void *, const void *); + +#include "../runetypebody.h" + +int +rune1cmp(const void *v1, const void *v2) +{ + Rune r1 = *(Rune *)v1, r2 = *(Rune *)v2; + + return r1 - r2; +} + +int +rune2cmp(const void *v1, const void *v2) +{ + Rune r = *(Rune *)v1, *p = (Rune *)v2; + + if(r >= p[0] && r <= p[1]) + return 0; + else + return r - p[0]; +} diff --git a/sbase/libutf/utf.c b/sbase/libutf/utf.c @@ -0,0 +1,129 @@ +/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include <string.h> +#include "../utf.h" + +char * +utfecpy(char *to, char *end, const char *from) +{ + Rune r = Runeerror; + size_t i, n; + + /* seek through to find final full rune */ + for(i = 0; r != '\0' && (n = charntorune(&r, &from[i], end - &to[i])); i += n) + ; + memcpy(to, from, i); /* copy over bytes up to this rune */ + + if(i > 0 && r != '\0') + to[i] = '\0'; /* terminate if unterminated */ + return &to[i]; +} + +size_t +utflen(const char *s) +{ + const char *p = s; + size_t i; + Rune r; + + for(i = 0; *p != '\0'; i++) + p += chartorune(&r, p); + return i; +} + +size_t +utfnlen(const char *s, size_t len) +{ + const char *p = s; + size_t i; + Rune r; + int n; + + for(i = 0; (n = charntorune(&r, p, len-(p-s))) && r != '\0'; i++) + p += n; + return i; +} + +char * +utfrune(const char *s, Rune r) +{ + if(r < Runeself) { + return strchr(s, r); + } + else if(r == Runeerror) { + Rune r0; + int n; + + for(; *s != '\0'; s += n) { + n = chartorune(&r0, s); + if(r == r0) + return (char *)s; + } + } + else { + char buf[UTFmax+1]; + int n; + + if(!(n = runetochar(buf, &r))) + return NULL; + buf[n] = '\0'; + return strstr(s, buf); + } + return NULL; +} + +char * +utfrrune(const char *s, Rune r) +{ + const char *p = NULL; + Rune r0; + int n; + + if(r < Runeself) + return strrchr(s, r); + + for(; *s != '\0'; s += n) { + n = chartorune(&r0, s); + if(r == r0) + p = s; + } + return (char *)p; +} + +char * +utfutf(const char *s, const char *t) +{ + const char *p, *q; + Rune r0, r1, r2; + int n, m; + + for(chartorune(&r0, t); (s = utfrune(s, r0)); s++) { + for(p = s, q = t; *q && *p; p += n, q += m) { + n = chartorune(&r1, p); + m = chartorune(&r2, q); + if(r1 != r2) + break; + } + if(!*q) + return (char *)s; + } + return NULL; +} diff --git a/sbase/libutf/writerune.c b/sbase/libutf/writerune.c @@ -0,0 +1,23 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "../utf.h" + +void +writerune(const char *file, FILE *fp, Rune *r) +{ + char buf[UTFmax]; + int n; + + if ((n = runetochar(buf, r)) > 0) { + fwrite(buf, n, 1, fp); + if (ferror(fp)) { + fprintf(stderr, "%s: write error: %s\n", + file, strerror(errno)); + exit(1); + } + } +} diff --git a/sbase/libutil/agetcwd.c b/sbase/libutil/agetcwd.c @@ -0,0 +1,18 @@ +/* See LICENSE file for copyright and license details. */ +#include <unistd.h> + +#include "../util.h" + +char * +agetcwd(void) +{ + char *buf; + long size; + + apathmax(&buf, &size); + if (!getcwd(buf, size)) + eprintf("getcwd:"); + + return buf; +} + diff --git a/sbase/libutil/apathmax.c b/sbase/libutil/apathmax.c @@ -0,0 +1,22 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "../util.h" + +void +apathmax(char **p, long *size) +{ + errno = 0; + + if ((*size = pathconf("/", _PC_PATH_MAX)) < 0) { + if (errno == 0) { + *size = BUFSIZ; + } else { + eprintf("pathconf:"); + } + } + *p = emalloc(*size); +} diff --git a/sbase/libutil/concat.c b/sbase/libutil/concat.c @@ -0,0 +1,20 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <unistd.h> + +#include "../text.h" +#include "../util.h" + +void +concat(FILE *fp1, const char *s1, FILE *fp2, const char *s2) +{ + char buf[BUFSIZ]; + ssize_t n; + + while ((n = read(fileno(fp1), buf, sizeof buf)) > 0) { + if (write(fileno(fp2), buf, n) != n) + eprintf("%s: write error:", s2); + } + if (n < 0) + eprintf("%s: read error:", s1); +} diff --git a/sbase/libutil/cp.c b/sbase/libutil/cp.c @@ -0,0 +1,153 @@ +/* See LICENSE file for copyright and license details. */ +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <utime.h> + +#include "../fs.h" +#include "../text.h" +#include "../util.h" + +int cp_aflag = 0; +int cp_fflag = 0; +int cp_Pflag = 0; +int cp_pflag = 0; +int cp_rflag = 0; +int cp_vflag = 0; +int cp_status = 0; + +int +cp(const char *s1, const char *s2) +{ + FILE *f1, *f2; + char *ns1, *ns2; + long size1, size2; + struct dirent *d; + struct stat st; + struct utimbuf ut; + char buf[PATH_MAX]; + DIR *dp; + int r; + + if (cp_vflag) + printf("'%s' -> '%s'\n", s1, s2); + + r = cp_Pflag ? lstat(s1, &st) : stat(s1, &st); + if (r < 0) { + weprintf("%s %s:", cp_Pflag ? "lstat" : "stat", s1); + cp_status = 1; + return 0; + } + + if (S_ISLNK(st.st_mode)) { + if (readlink(s1, buf, sizeof(buf) - 1) >= 0) { + if (cp_fflag) + unlink(s2); + if (symlink(buf, s2) != 0) { + weprintf("%s: can't create '%s'\n", argv0, s2); + cp_status = 1; + return 0; + } + } + goto preserve; + } + + if (S_ISDIR(st.st_mode)) { + if (!cp_rflag) + eprintf("%s: is a directory\n", s1); + + if (!(dp = opendir(s1))) + eprintf("opendir %s:", s1); + + if (mkdir(s2, st.st_mode) < 0 && errno != EEXIST) + eprintf("mkdir %s:", s2); + + apathmax(&ns1, &size1); + apathmax(&ns2, &size2); + while ((d = readdir(dp))) { + if (strcmp(d->d_name, ".") && strcmp(d->d_name, "..")) { + r = snprintf(ns1, size1, "%s/%s", s1, d->d_name); + if (r >= size1 || r < 0) { + eprintf("%s/%s: filename too long\n", + s1, d->d_name); + } + r = snprintf(ns2, size2, "%s/%s", s2, d->d_name); + if (r >= size2 || r < 0) { + eprintf("%s/%s: filename too long\n", + s2, d->d_name); + } + fnck(ns1, ns2, cp); + } + } + closedir(dp); + free(ns1); + free(ns2); + goto preserve; + } + + if (cp_aflag) { + if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode) || + S_ISSOCK(st.st_mode) || S_ISFIFO(st.st_mode)) { + unlink(s2); + if (mknod(s2, st.st_mode, st.st_rdev) < 0) { + weprintf("%s: can't create '%s':", argv0, s2); + cp_status = 1; + return 0; + } + goto preserve; + } + } + + if (!(f1 = fopen(s1, "r"))) { + weprintf("fopen %s:", s1); + cp_status = 1; + return 0; + } + + if (!(f2 = fopen(s2, "w"))) { + if (cp_fflag) { + unlink(s2); + if (!(f2 = fopen(s2, "w"))) { + weprintf("fopen %s:", s2); + cp_status = 1; + return 0; + } + } else { + weprintf("fopen %s:", s2); + cp_status = 1; + return 0; + } + } + concat(f1, s1, f2, s2); + /* preserve permissions by default */ + fchmod(fileno(f2), st.st_mode); + fclose(f2); + fclose(f1); + +preserve: + if (cp_aflag || cp_pflag) { + if (!(S_ISLNK(st.st_mode))) { + /* timestamp */ + ut.actime = st.st_atime; + ut.modtime = st.st_mtime; + utime(s2, &ut); + } + /* preserve owner ? */ + if (S_ISLNK(st.st_mode)) + r = lchown(s2, st.st_uid, st.st_gid); + else + r = chown(s2, st.st_uid, st.st_gid); + if (r < 0) { + weprintf("cp: can't preserve ownership of '%s':", s2); + cp_status = 1; + } + } + return 0; +} diff --git a/sbase/libutil/crypt.c b/sbase/libutil/crypt.c @@ -0,0 +1,156 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../crypt.h" +#include "../text.h" +#include "../util.h" + +static int +hexdec(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return -1; /* unknown character */ +} + +static int +mdcheckline(const char *s, uint8_t *md, size_t sz) +{ + size_t i; + int b1, b2; + + for (i = 0; i < sz; i++) { + if (!*s || (b1 = hexdec(*s++)) < 0) + return -1; /* invalid format */ + if (!*s || (b2 = hexdec(*s++)) < 0) + return -1; /* invalid format */ + if ((uint8_t)((b1 << 4) | b2) != md[i]) + return 0; /* value mismatch */ + } + return (i == sz) ? 1 : 0; +} + +int +cryptcheck(char *sumfile, int argc, char *argv[], + struct crypt_ops *ops, uint8_t *md, size_t sz) +{ + FILE *cfp, *fp; + char *line = NULL, *file, *p; + int r, nonmatch = 0, formatsucks = 0, noread = 0, ret = 0; + size_t bufsiz = 0; + + if (!sumfile) + cfp = stdin; + else if (!(cfp = fopen(sumfile, "r"))) + eprintf("fopen %s:", sumfile); + + while (getline(&line, &bufsiz, cfp) != -1) { + if (!(file = strstr(line, " "))) { + formatsucks++; + continue; + } + if ((file - line) / 2 != sz) { + formatsucks++; /* checksum length mismatch */ + continue; + } + *file = '\0'; + file += 2; + for (p = file; *p && *p != '\n' && *p != '\r'; p++); /* strip newline */ + *p = '\0'; + if (!(fp = fopen(file, "r"))) { + weprintf("fopen %s:", file); + noread++; + continue; + } + cryptsum(ops, fp, file, md); + r = mdcheckline(line, md, sz); + if (r == 1) { + printf("%s: OK\n", file); + } else if (r == 0) { + printf("%s: FAILED\n", file); + nonmatch++; + } else { + formatsucks++; + } + fclose(fp); + } + if (sumfile) + fclose(cfp); + free(line); + if (formatsucks > 0) { + weprintf("%d lines are improperly formatted\n", formatsucks); + ret = 1; + } + if (noread > 0) { + weprintf("%d listed file could not be read\n", noread); + ret = 1; + } + if (nonmatch > 0) { + weprintf("%d computed checksums did NOT match\n", nonmatch); + ret = 1; + } + return ret; +} + +int +cryptmain(int argc, char *argv[], + struct crypt_ops *ops, uint8_t *md, size_t sz) +{ + FILE *fp; + int ret = 0; + + if (argc == 0) { + cryptsum(ops, stdin, "<stdin>", md); + mdprint(md, "<stdin>", sz); + } else { + for (; argc > 0; argc--) { + if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + ret = 1; + continue; + } + if (cryptsum(ops, fp, *argv, md) == 1) + ret = 1; + else + mdprint(md, *argv, sz); + fclose(fp); + argv++; + } + } + return ret; +} + +int +cryptsum(struct crypt_ops *ops, FILE *fp, const char *f, + uint8_t *md) +{ + uint8_t buf[BUFSIZ]; + size_t n; + + ops->init(ops->s); + while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) + ops->update(ops->s, buf, n); + if (ferror(fp)) { + weprintf("%s: read error:", f); + return 1; + } + ops->sum(ops->s, md); + return 0; +} + +void +mdprint(const uint8_t *md, const char *f, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + printf("%02x", md[i]); + printf(" %s\n", f); +} diff --git a/sbase/libutil/ealloc.c b/sbase/libutil/ealloc.c @@ -0,0 +1,47 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdlib.h> +#include <string.h> + +#include "../util.h" + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + p = calloc(nmemb, size); + if (!p) + eprintf("calloc: out of memory\n"); + return p; +} + +void * +emalloc(size_t size) +{ + void *p; + + p = malloc(size); + if (!p) + eprintf("malloc: out of memory\n"); + return p; +} + +void * +erealloc(void *p, size_t size) +{ + p = realloc(p, size); + if (!p) + eprintf("realloc: out of memory\n"); + return p; +} + +char * +estrdup(const char *s) +{ + char *p; + + p = strdup(s); + if (!p) + eprintf("strdup: out of memory\n"); + return p; +} diff --git a/sbase/libutil/enmasse.c b/sbase/libutil/enmasse.c @@ -0,0 +1,41 @@ +/* See LICENSE file for copyright and license details. */ +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "../util.h" + +void +enmasse(int argc, char *argv[], int (*fn)(const char *, const char *)) +{ + char *buf, *dir; + int i, len; + long size; + struct stat st; + size_t dlen; + + if (argc == 2 && !(stat(argv[1], &st) == 0 && S_ISDIR(st.st_mode))) { + fnck(argv[0], argv[1], fn); + return; + } else { + dir = (argc == 1) ? "." : argv[--argc]; + } + + apathmax(&buf, &size); + for (i = 0; i < argc; i++) { + dlen = strlen(dir); + if (dlen > 0 && dir[dlen - 1] == '/') + len = snprintf(buf, size, "%s%s", dir, basename(argv[i])); + else + len = snprintf(buf, size, "%s/%s", dir, basename(argv[i])); + if (len < 0 || len >= size) { + eprintf("%s/%s: filename too long\n", dir, + basename(argv[i])); + } + fnck(argv[i], buf, fn); + } + free(buf); +} diff --git a/sbase/libutil/eprintf.c b/sbase/libutil/eprintf.c @@ -0,0 +1,67 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../util.h" + +char *argv0; + +static void venprintf(int, const char *, va_list); + +void +eprintf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + venprintf(1, fmt, ap); + va_end(ap); +} + +void +enprintf(int status, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + venprintf(status, fmt, ap); + va_end(ap); +} + +void +venprintf(int status, const char *fmt, va_list ap) +{ +#ifdef DEBUG + fprintf(stderr, "%s: ", argv0); +#endif + + vfprintf(stderr, fmt, ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } + + exit(status); +} + +void +weprintf(const char *fmt, ...) +{ + va_list ap; + +#ifdef DEBUG + fprintf(stderr, "%s: ", argv0); +#endif + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } +} diff --git a/sbase/libutil/eregcomp.c b/sbase/libutil/eregcomp.c @@ -0,0 +1,25 @@ +#include <regex.h> +#include <stdio.h> +#include <sys/types.h> + +#include "../util.h" + +int +enregcomp(int status, regex_t *preg, const char *regex, int cflags) +{ + char errbuf[BUFSIZ] = ""; + int r; + + if((r = regcomp(preg, regex, cflags)) == 0) + return r; + + regerror(r, preg, errbuf, sizeof(errbuf)); + enprintf(status, "invalid regex: %s\n", errbuf); + return r; +} + +int +eregcomp(regex_t *preg, const char *regex, int cflags) +{ + return enregcomp(1, preg, regex, cflags); +} diff --git a/sbase/libutil/estrtod.c b/sbase/libutil/estrtod.c @@ -0,0 +1,18 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../util.h" + +double +estrtod(const char *s) +{ + char *end; + double d; + + d = strtod(s, &end); + if (end == s || *end != '\0') + eprintf("%s: not a real number\n", s); + return d; +} diff --git a/sbase/libutil/estrtol.c b/sbase/libutil/estrtol.c @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../util.h" + +long +estrtol(const char *s, int base) +{ + char *end; + long n; + + errno = 0; + n = strtol(s, &end, base); + if (*end != '\0') { + if (base == 0) + eprintf("%s: not an integer\n", s); + else + eprintf("%s: not a base %d integer\n", s, base); + } + if (errno != 0) + eprintf("%s:", s); + + return n; +} + diff --git a/sbase/libutil/fnck.c b/sbase/libutil/fnck.c @@ -0,0 +1,20 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include "../util.h" + +void +fnck(const char *a, const char *b, int (*fn)(const char *, const char *)) +{ + struct stat sta, stb; + + if (!stat(a, &sta) + && !stat(b, &stb) + && sta.st_dev == stb.st_dev + && sta.st_ino == stb.st_ino) { + eprintf("%s -> %s: same file\n", a, b); + } + + if (fn(a, b) < 0) + eprintf("%s -> %s:", a, b); +} diff --git a/sbase/libutil/getlines.c b/sbase/libutil/getlines.c @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../text.h" +#include "../util.h" + +void +getlines(FILE *fp, struct linebuf *b) +{ + char *line = NULL, **nline; + size_t size = 0, linelen; + ssize_t len; + + while ((len = getline(&line, &size, fp)) != -1) { + if (++b->nlines > b->capacity) { + b->capacity += 512; + nline = erealloc(b->lines, b->capacity * sizeof(*b->lines)); + b->lines = nline; + } + linelen = len + 1; + b->lines[b->nlines-1] = emalloc(linelen); + memcpy(b->lines[b->nlines-1], line, linelen); + } + free(line); +} diff --git a/sbase/libutil/human.c b/sbase/libutil/human.c @@ -0,0 +1,22 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <string.h> + +#include "../util.h" + +char * +humansize(double n) +{ + static char buf[16]; + const char postfixes[] = "BKMGTPE"; + size_t i; + + for (i = 0; n >= 1024 && i < strlen(postfixes); i++) + n /= 1024; + + if (!i) + snprintf(buf, sizeof(buf), "%lu", (unsigned long)n); + else + snprintf(buf, sizeof(buf), "%.1f%c", n, postfixes[i]); + return buf; +} diff --git a/sbase/libutil/md5.c b/sbase/libutil/md5.c @@ -0,0 +1,148 @@ +/* public domain md5 implementation based on rfc1321 and libtomcrypt */ +#include <stdint.h> +#include <string.h> + +#include "../md5.h" + +static uint32_t rol(uint32_t n, int k) { return (n << k) | (n >> (32-k)); } +#define F(x,y,z) (z ^ (x & (y ^ z))) +#define G(x,y,z) (y ^ (z & (y ^ x))) +#define H(x,y,z) (x ^ y ^ z) +#define I(x,y,z) (y ^ (x | ~z)) +#define FF(a,b,c,d,w,s,t) a += F(b,c,d) + w + t; a = rol(a,s) + b +#define GG(a,b,c,d,w,s,t) a += G(b,c,d) + w + t; a = rol(a,s) + b +#define HH(a,b,c,d,w,s,t) a += H(b,c,d) + w + t; a = rol(a,s) + b +#define II(a,b,c,d,w,s,t) a += I(b,c,d) + w + t; a = rol(a,s) + b + +static const uint32_t tab[64] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 +}; + +static void +processblock(struct md5 *s, const uint8_t *buf) +{ + uint32_t i, W[16], a, b, c, d; + + for (i = 0; i < 16; i++) { + W[i] = buf[4*i]; + W[i] |= (uint32_t)buf[4*i+1]<<8; + W[i] |= (uint32_t)buf[4*i+2]<<16; + W[i] |= (uint32_t)buf[4*i+3]<<24; + } + + a = s->h[0]; + b = s->h[1]; + c = s->h[2]; + d = s->h[3]; + + i = 0; + while (i < 16) { + FF(a,b,c,d, W[i], 7, tab[i]); i++; + FF(d,a,b,c, W[i], 12, tab[i]); i++; + FF(c,d,a,b, W[i], 17, tab[i]); i++; + FF(b,c,d,a, W[i], 22, tab[i]); i++; + } + while (i < 32) { + GG(a,b,c,d, W[(5*i+1)%16], 5, tab[i]); i++; + GG(d,a,b,c, W[(5*i+1)%16], 9, tab[i]); i++; + GG(c,d,a,b, W[(5*i+1)%16], 14, tab[i]); i++; + GG(b,c,d,a, W[(5*i+1)%16], 20, tab[i]); i++; + } + while (i < 48) { + HH(a,b,c,d, W[(3*i+5)%16], 4, tab[i]); i++; + HH(d,a,b,c, W[(3*i+5)%16], 11, tab[i]); i++; + HH(c,d,a,b, W[(3*i+5)%16], 16, tab[i]); i++; + HH(b,c,d,a, W[(3*i+5)%16], 23, tab[i]); i++; + } + while (i < 64) { + II(a,b,c,d, W[7*i%16], 6, tab[i]); i++; + II(d,a,b,c, W[7*i%16], 10, tab[i]); i++; + II(c,d,a,b, W[7*i%16], 15, tab[i]); i++; + II(b,c,d,a, W[7*i%16], 21, tab[i]); i++; + } + + s->h[0] += a; + s->h[1] += b; + s->h[2] += c; + s->h[3] += d; +} + +static void +pad(struct md5 *s) +{ + unsigned r = s->len % 64; + + s->buf[r++] = 0x80; + if (r > 56) { + memset(s->buf + r, 0, 64 - r); + r = 0; + processblock(s, s->buf); + } + memset(s->buf + r, 0, 56 - r); + s->len *= 8; + s->buf[56] = s->len; + s->buf[57] = s->len >> 8; + s->buf[58] = s->len >> 16; + s->buf[59] = s->len >> 24; + s->buf[60] = s->len >> 32; + s->buf[61] = s->len >> 40; + s->buf[62] = s->len >> 48; + s->buf[63] = s->len >> 56; + processblock(s, s->buf); +} + +void +md5_init(void *ctx) +{ + struct md5 *s = ctx; + s->len = 0; + s->h[0] = 0x67452301; + s->h[1] = 0xefcdab89; + s->h[2] = 0x98badcfe; + s->h[3] = 0x10325476; +} + +void +md5_sum(void *ctx, uint8_t md[MD5_DIGEST_LENGTH]) +{ + struct md5 *s = ctx; + int i; + + pad(s); + for (i = 0; i < 4; i++) { + md[4*i] = s->h[i]; + md[4*i+1] = s->h[i] >> 8; + md[4*i+2] = s->h[i] >> 16; + md[4*i+3] = s->h[i] >> 24; + } +} + +void +md5_update(void *ctx, const void *m, unsigned long len) +{ + struct md5 *s = ctx; + const uint8_t *p = m; + unsigned r = s->len % 64; + + s->len += len; + if (r) { + if (len < 64 - r) { + memcpy(s->buf + r, p, len); + return; + } + memcpy(s->buf + r, p, 64 - r); + len -= 64 - r; + p += 64 - r; + processblock(s, s->buf); + } + for (; len >= 64; len -= 64, p += 64) + processblock(s, p); + memcpy(s->buf, p, len); +} diff --git a/sbase/libutil/mode.c b/sbase/libutil/mode.c @@ -0,0 +1,163 @@ +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "../util.h" + +mode_t +getumask(void) +{ + mode_t mask = umask(0); + umask(mask); + return mask; +} + +mode_t +parsemode(const char *str, mode_t mode, mode_t mask) +{ + char *end; + const char *p = str; + int octal, op; + mode_t who, perm, clear; + + octal = strtol(str, &end, 8); + if (*end == '\0') { + if (octal < 0 || octal > 07777) { + eprintf("%s: invalid mode\n", str); + return -1; + } + mode = 0; + if (octal & 04000) mode |= S_ISUID; + if (octal & 02000) mode |= S_ISGID; + if (octal & 01000) mode |= S_ISVTX; + if (octal & 00400) mode |= S_IRUSR; + if (octal & 00200) mode |= S_IWUSR; + if (octal & 00100) mode |= S_IXUSR; + if (octal & 00040) mode |= S_IRGRP; + if (octal & 00020) mode |= S_IWGRP; + if (octal & 00010) mode |= S_IXGRP; + if (octal & 00004) mode |= S_IROTH; + if (octal & 00002) mode |= S_IWOTH; + if (octal & 00001) mode |= S_IXOTH; + return mode; + } +next: + /* first, determine which bits we will be modifying */ + for (who = 0; *p; p++) { + switch (*p) { + /* masks */ + case 'u': + who |= S_IRWXU|S_ISUID; + continue; + case 'g': + who |= S_IRWXG|S_ISGID; + continue; + case 'o': + who |= S_IRWXO; + continue; + case 'a': + who |= S_IRWXU|S_ISUID|S_IRWXG|S_ISGID|S_IRWXO; + continue; + } + break; + } + if (who) { + clear = who; + } else { + clear = S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO; + who = ~mask; + } + while (*p) { + switch (*p) { + /* opers */ + case '=': + case '+': + case '-': + op = (int)*p; + break; + default: + eprintf("%s: invalid mode\n", str); + return -1; + } + + perm = 0; + switch (*++p) { + /* copy */ + case 'u': + if (mode & S_IRUSR) + perm |= S_IRUSR|S_IRGRP|S_IROTH; + if (mode & S_IWUSR) + perm |= S_IWUSR|S_IWGRP|S_IWOTH; + if (mode & S_IXUSR) + perm |= S_IXUSR|S_IXGRP|S_IXOTH; + if (mode & S_ISUID) + perm |= S_ISUID|S_ISGID; + p++; + break; + case 'g': + if (mode & S_IRGRP) + perm |= S_IRUSR|S_IRGRP|S_IROTH; + if (mode & S_IWGRP) + perm |= S_IWUSR|S_IWGRP|S_IWOTH; + if (mode & S_IXGRP) + perm |= S_IXUSR|S_IXGRP|S_IXOTH; + if (mode & S_ISGID) + perm |= S_ISUID|S_ISGID; + p++; + break; + case 'o': + if (mode & S_IROTH) + perm |= S_IRUSR|S_IRGRP|S_IROTH; + if (mode & S_IWOTH) + perm |= S_IWUSR|S_IWGRP|S_IWOTH; + if (mode & S_IXOTH) + perm |= S_IXUSR|S_IXGRP|S_IXOTH; + p++; + break; + default: + for (; *p; p++) { + switch (*p) { + /* modes */ + case 'r': + perm |= S_IRUSR|S_IRGRP|S_IROTH; + break; + case 'w': + perm |= S_IWUSR|S_IWGRP|S_IWOTH; + break; + case 'x': + perm |= S_IXUSR|S_IXGRP|S_IXOTH; + break; + case 's': + perm |= S_ISUID|S_ISGID; + break; + case 't': + perm |= S_ISVTX; + break; + default: + goto apply; + } + } + } + + apply: + /* apply */ + switch (op) { + case '=': + mode &= ~clear; + /* fallthrough */ + case '+': + mode |= perm & who; + break; + case '-': + mode &= ~(perm & who); + break; + } + /* if we hit a comma, move on to the next clause */ + if (*p == ',') { + p++; + goto next; + } + } + return mode; +} diff --git a/sbase/libutil/putword.c b/sbase/libutil/putword.c @@ -0,0 +1,16 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> + +#include "../util.h" + +void +putword(const char *s) +{ + static int first = 1; + + if (!first) + putchar(' '); + + fputs(s, stdout); + first = 0; +} diff --git a/sbase/libutil/recurse.c b/sbase/libutil/recurse.c @@ -0,0 +1,42 @@ +/* See LICENSE file for copyright and license details. */ +#include <dirent.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "../util.h" + +void +recurse(const char *path, void (*fn)(const char *)) +{ + char buf[PATH_MAX]; + struct dirent *d; + struct stat st; + DIR *dp; + + if (lstat(path, &st) < 0 || !S_ISDIR(st.st_mode)) + return; + + if (!(dp = opendir(path))) + eprintf("opendir %s:", path); + + while ((d = readdir(dp))) { + if (strcmp(d->d_name, ".") == 0 || + strcmp(d->d_name, "..") == 0) + continue; + if (strlcpy(buf, path, sizeof(buf)) >= sizeof(buf)) + eprintf("path too long\n"); + if (buf[strlen(buf) - 1] != '/') + if (strlcat(buf, "/", sizeof(buf)) >= sizeof(buf)) + eprintf("path too long\n"); + if (strlcat(buf, d->d_name, sizeof(buf)) >= sizeof(buf)) + eprintf("path too long\n"); + fn(buf); + } + + closedir(dp); +} diff --git a/sbase/libutil/rm.c b/sbase/libutil/rm.c @@ -0,0 +1,17 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> + +#include "../fs.h" +#include "../util.h" + +int rm_fflag = 0; +int rm_rflag = 0; + +void +rm(const char *path) +{ + if (rm_rflag) + recurse(path, rm); + if (remove(path) < 0 && !rm_fflag) + eprintf("remove %s:", path); +} diff --git a/sbase/libutil/sha1.c b/sbase/libutil/sha1.c @@ -0,0 +1,144 @@ +/* public domain sha1 implementation based on rfc3174 and libtomcrypt */ +#include <stdint.h> +#include <string.h> + +#include "../sha1.h" + +static uint32_t rol(uint32_t n, int k) { return (n << k) | (n >> (32-k)); } +#define F0(b,c,d) (d ^ (b & (c ^ d))) +#define F1(b,c,d) (b ^ c ^ d) +#define F2(b,c,d) ((b & c) | (d & (b | c))) +#define F3(b,c,d) (b ^ c ^ d) +#define G0(a,b,c,d,e,i) e += rol(a,5)+F0(b,c,d)+W[i]+0x5A827999; b = rol(b,30) +#define G1(a,b,c,d,e,i) e += rol(a,5)+F1(b,c,d)+W[i]+0x6ED9EBA1; b = rol(b,30) +#define G2(a,b,c,d,e,i) e += rol(a,5)+F2(b,c,d)+W[i]+0x8F1BBCDC; b = rol(b,30) +#define G3(a,b,c,d,e,i) e += rol(a,5)+F3(b,c,d)+W[i]+0xCA62C1D6; b = rol(b,30) + +static void +processblock(struct sha1 *s, const uint8_t *buf) +{ + uint32_t W[80], a, b, c, d, e; + int i; + + for (i = 0; i < 16; i++) { + W[i] = (uint32_t)buf[4*i]<<24; + W[i] |= (uint32_t)buf[4*i+1]<<16; + W[i] |= (uint32_t)buf[4*i+2]<<8; + W[i] |= buf[4*i+3]; + } + for (; i < 80; i++) + W[i] = rol(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16], 1); + a = s->h[0]; + b = s->h[1]; + c = s->h[2]; + d = s->h[3]; + e = s->h[4]; + for (i = 0; i < 20; ) { + G0(a,b,c,d,e,i++); + G0(e,a,b,c,d,i++); + G0(d,e,a,b,c,i++); + G0(c,d,e,a,b,i++); + G0(b,c,d,e,a,i++); + } + while (i < 40) { + G1(a,b,c,d,e,i++); + G1(e,a,b,c,d,i++); + G1(d,e,a,b,c,i++); + G1(c,d,e,a,b,i++); + G1(b,c,d,e,a,i++); + } + while (i < 60) { + G2(a,b,c,d,e,i++); + G2(e,a,b,c,d,i++); + G2(d,e,a,b,c,i++); + G2(c,d,e,a,b,i++); + G2(b,c,d,e,a,i++); + } + while (i < 80) { + G3(a,b,c,d,e,i++); + G3(e,a,b,c,d,i++); + G3(d,e,a,b,c,i++); + G3(c,d,e,a,b,i++); + G3(b,c,d,e,a,i++); + } + s->h[0] += a; + s->h[1] += b; + s->h[2] += c; + s->h[3] += d; + s->h[4] += e; +} + +static void +pad(struct sha1 *s) +{ + unsigned r = s->len % 64; + + s->buf[r++] = 0x80; + if (r > 56) { + memset(s->buf + r, 0, 64 - r); + r = 0; + processblock(s, s->buf); + } + memset(s->buf + r, 0, 56 - r); + s->len *= 8; + s->buf[56] = s->len >> 56; + s->buf[57] = s->len >> 48; + s->buf[58] = s->len >> 40; + s->buf[59] = s->len >> 32; + s->buf[60] = s->len >> 24; + s->buf[61] = s->len >> 16; + s->buf[62] = s->len >> 8; + s->buf[63] = s->len; + processblock(s, s->buf); +} + +void +sha1_init(void *ctx) +{ + struct sha1 *s = ctx; + + s->len = 0; + s->h[0] = 0x67452301; + s->h[1] = 0xEFCDAB89; + s->h[2] = 0x98BADCFE; + s->h[3] = 0x10325476; + s->h[4] = 0xC3D2E1F0; +} + +void +sha1_sum(void *ctx, uint8_t md[SHA1_DIGEST_LENGTH]) +{ + struct sha1 *s = ctx; + int i; + + pad(s); + for (i = 0; i < 5; i++) { + md[4*i] = s->h[i] >> 24; + md[4*i+1] = s->h[i] >> 16; + md[4*i+2] = s->h[i] >> 8; + md[4*i+3] = s->h[i]; + } +} + +void +sha1_update(void *ctx, const void *m, unsigned long len) +{ + struct sha1 *s = ctx; + const uint8_t *p = m; + unsigned r = s->len % 64; + + s->len += len; + if (r) { + if (len < 64 - r) { + memcpy(s->buf + r, p, len); + return; + } + memcpy(s->buf + r, p, 64 - r); + len -= 64 - r; + p += 64 - r; + processblock(s, s->buf); + } + for (; len >= 64; len -= 64, p += 64) + processblock(s, p); + memcpy(s->buf, p, len); +} diff --git a/sbase/libutil/sha256.c b/sbase/libutil/sha256.c @@ -0,0 +1,148 @@ +/* public domain sha256 implementation based on fips180-3 */ +#include <ctype.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../sha256.h" + +static uint32_t ror(uint32_t n, int k) { return (n >> k) | (n << (32-k)); } +#define Ch(x,y,z) (z ^ (x & (y ^ z))) +#define Maj(x,y,z) ((x & y) | (z & (x | y))) +#define S0(x) (ror(x,2) ^ ror(x,13) ^ ror(x,22)) +#define S1(x) (ror(x,6) ^ ror(x,11) ^ ror(x,25)) +#define R0(x) (ror(x,7) ^ ror(x,18) ^ (x>>3)) +#define R1(x) (ror(x,17) ^ ror(x,19) ^ (x>>10)) + +static const uint32_t K[64] = { +0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, +0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, +0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, +0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, +0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, +0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, +0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, +0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +static void +processblock(struct sha256 *s, const uint8_t *buf) +{ + uint32_t W[64], t1, t2, a, b, c, d, e, f, g, h; + int i; + + for (i = 0; i < 16; i++) { + W[i] = (uint32_t)buf[4*i]<<24; + W[i] |= (uint32_t)buf[4*i+1]<<16; + W[i] |= (uint32_t)buf[4*i+2]<<8; + W[i] |= buf[4*i+3]; + } + for (; i < 64; i++) + W[i] = R1(W[i-2]) + W[i-7] + R0(W[i-15]) + W[i-16]; + a = s->h[0]; + b = s->h[1]; + c = s->h[2]; + d = s->h[3]; + e = s->h[4]; + f = s->h[5]; + g = s->h[6]; + h = s->h[7]; + for (i = 0; i < 64; i++) { + t1 = h + S1(e) + Ch(e,f,g) + K[i] + W[i]; + t2 = S0(a) + Maj(a,b,c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + s->h[0] += a; + s->h[1] += b; + s->h[2] += c; + s->h[3] += d; + s->h[4] += e; + s->h[5] += f; + s->h[6] += g; + s->h[7] += h; +} + +static void +pad(struct sha256 *s) +{ + unsigned r = s->len % 64; + + s->buf[r++] = 0x80; + if (r > 56) { + memset(s->buf + r, 0, 64 - r); + r = 0; + processblock(s, s->buf); + } + memset(s->buf + r, 0, 56 - r); + s->len *= 8; + s->buf[56] = s->len >> 56; + s->buf[57] = s->len >> 48; + s->buf[58] = s->len >> 40; + s->buf[59] = s->len >> 32; + s->buf[60] = s->len >> 24; + s->buf[61] = s->len >> 16; + s->buf[62] = s->len >> 8; + s->buf[63] = s->len; + processblock(s, s->buf); +} + +void +sha256_init(void *ctx) +{ + struct sha256 *s = ctx; + s->len = 0; + s->h[0] = 0x6a09e667; + s->h[1] = 0xbb67ae85; + s->h[2] = 0x3c6ef372; + s->h[3] = 0xa54ff53a; + s->h[4] = 0x510e527f; + s->h[5] = 0x9b05688c; + s->h[6] = 0x1f83d9ab; + s->h[7] = 0x5be0cd19; +} + +void +sha256_sum(void *ctx, uint8_t md[SHA256_DIGEST_LENGTH]) +{ + struct sha256 *s = ctx; + int i; + + pad(s); + for (i = 0; i < 8; i++) { + md[4*i] = s->h[i] >> 24; + md[4*i+1] = s->h[i] >> 16; + md[4*i+2] = s->h[i] >> 8; + md[4*i+3] = s->h[i]; + } +} + +void +sha256_update(void *ctx, const void *m, unsigned long len) +{ + struct sha256 *s = ctx; + const uint8_t *p = m; + unsigned r = s->len % 64; + + s->len += len; + if (r) { + if (len < 64 - r) { + memcpy(s->buf + r, p, len); + return; + } + memcpy(s->buf + r, p, 64 - r); + len -= 64 - r; + p += 64 - r; + processblock(s, s->buf); + } + for (; len >= 64; len -= 64, p += 64) + processblock(s, p); + memcpy(s->buf, p, len); +} diff --git a/sbase/libutil/sha512.c b/sbase/libutil/sha512.c @@ -0,0 +1,169 @@ +/* public domain sha256 implementation based on fips180-3 */ + +#include <ctype.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../sha512.h" + +static uint64_t ror(uint64_t n, int k) { return (n >> k) | (n << (64-k)); } +#define Ch(x,y,z) (z ^ (x & (y ^ z))) +#define Maj(x,y,z) ((x & y) | (z & (x | y))) +#define S0(x) (ror(x,28) ^ ror(x,34) ^ ror(x,39)) +#define S1(x) (ror(x,14) ^ ror(x,18) ^ ror(x,41)) +#define R0(x) (ror(x,1) ^ ror(x,8) ^ (x>>7)) +#define R1(x) (ror(x,19) ^ ror(x,61) ^ (x>>6)) + +static const uint64_t K[80] = { +0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, +0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, +0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, +0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, +0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, +0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, +0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, +0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, +0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, +0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, +0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, +0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, +0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, +0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, +0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, +0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, +0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, +0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, +0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, +0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL +}; + +static void +processblock(struct sha512 *s, const uint8_t *buf) +{ + uint64_t W[80], t1, t2, a, b, c, d, e, f, g, h; + int i; + + for (i = 0; i < 16; i++) { + W[i] = (uint64_t)buf[8*i]<<56; + W[i] |= (uint64_t)buf[8*i+1]<<48; + W[i] |= (uint64_t)buf[8*i+2]<<40; + W[i] |= (uint64_t)buf[8*i+3]<<32; + W[i] |= (uint64_t)buf[8*i+4]<<24; + W[i] |= (uint64_t)buf[8*i+5]<<16; + W[i] |= (uint64_t)buf[8*i+6]<<8; + W[i] |= buf[8*i+7]; + } + for (; i < 80; i++) + W[i] = R1(W[i-2]) + W[i-7] + R0(W[i-15]) + W[i-16]; + a = s->h[0]; + b = s->h[1]; + c = s->h[2]; + d = s->h[3]; + e = s->h[4]; + f = s->h[5]; + g = s->h[6]; + h = s->h[7]; + for (i = 0; i < 80; i++) { + t1 = h + S1(e) + Ch(e,f,g) + K[i] + W[i]; + t2 = S0(a) + Maj(a,b,c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + s->h[0] += a; + s->h[1] += b; + s->h[2] += c; + s->h[3] += d; + s->h[4] += e; + s->h[5] += f; + s->h[6] += g; + s->h[7] += h; +} + +static void +pad(struct sha512 *s) +{ + unsigned r = s->len % 128; + + s->buf[r++] = 0x80; + if (r > 112) { + memset(s->buf + r, 0, 128 - r); + r = 0; + processblock(s, s->buf); + } + memset(s->buf + r, 0, 120 - r); + s->len *= 8; + s->buf[120] = s->len >> 56; + s->buf[121] = s->len >> 48; + s->buf[122] = s->len >> 40; + s->buf[123] = s->len >> 32; + s->buf[124] = s->len >> 24; + s->buf[125] = s->len >> 16; + s->buf[126] = s->len >> 8; + s->buf[127] = s->len; + processblock(s, s->buf); +} + +void +sha512_init(void *ctx) +{ + struct sha512 *s = ctx; + s->len = 0; + s->h[0] = 0x6a09e667f3bcc908ULL; + s->h[1] = 0xbb67ae8584caa73bULL; + s->h[2] = 0x3c6ef372fe94f82bULL; + s->h[3] = 0xa54ff53a5f1d36f1ULL; + s->h[4] = 0x510e527fade682d1ULL; + s->h[5] = 0x9b05688c2b3e6c1fULL; + s->h[6] = 0x1f83d9abfb41bd6bULL; + s->h[7] = 0x5be0cd19137e2179ULL; +} + +void +sha512_sum(void *ctx, uint8_t md[SHA512_DIGEST_LENGTH]) +{ + struct sha512 *s = ctx; + int i; + + pad(s); + for (i = 0; i < 8; i++) { + md[8*i] = s->h[i] >> 56; + md[8*i+1] = s->h[i] >> 48; + md[8*i+2] = s->h[i] >> 40; + md[8*i+3] = s->h[i] >> 32; + md[8*i+4] = s->h[i] >> 24; + md[8*i+5] = s->h[i] >> 16; + md[8*i+6] = s->h[i] >> 8; + md[8*i+7] = s->h[i]; + } +} + +void +sha512_update(void *ctx, const void *m, unsigned long len) +{ + struct sha512 *s = ctx; + const uint8_t *p = m; + unsigned r = s->len % 128; + + s->len += len; + if (r) { + if (len < 128 - r) { + memcpy(s->buf + r, p, len); + return; + } + memcpy(s->buf + r, p, 128 - r); + len -= 128 - r; + p += 128 - r; + processblock(s, s->buf); + } + for (; len >= 128; len -= 128, p += 128) + processblock(s, p); + memcpy(s->buf, p, len); +} diff --git a/sbase/libutil/strcasestr.c b/sbase/libutil/strcasestr.c @@ -0,0 +1,34 @@ +/* + * Copyright 2005-2014 Rich Felker, et al. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include <string.h> +#include <strings.h> + +#include "../util.h" + +char * +strcasestr(const char *h, const char *n) +{ + size_t l = strlen(n); + for (; *h; h++) if (!strncasecmp(h, n, l)) return (char *)h; + return 0; +} diff --git a/sbase/libutil/strlcat.c b/sbase/libutil/strlcat.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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 <string.h> +#include <sys/types.h> + +#include "../util.h" + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t +strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + return(dlen + (s - src)); /* count does not include NUL */ +} diff --git a/sbase/libutil/strlcpy.c b/sbase/libutil/strlcpy.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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 <string.h> +#include <sys/types.h> + +#include "../util.h" + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + return(s - src - 1); /* count does not include NUL */ +} diff --git a/sbase/link.1 b/sbase/link.1 @@ -0,0 +1,14 @@ +.TH LN 1 sbase\-VERSION +.SH NAME +link \- create a hard link by calling the link function +.SH SYNOPSIS +.B link +.I target +.I linkname +.P +.SH DESCRIPTION +.B link +creates a hard link to a given file, with the given name. +.SH SEE ALSO +.IR ln (1), +.IR link (2) diff --git a/sbase/link.c b/sbase/link.c @@ -0,0 +1,17 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <unistd.h> + +#include "util.h" + +int +main(int argc, char *argv[]) +{ + argv0 = argv[0]; + + if (argc != 3) + eprintf("usage: %s target linkname\n", argv0); + if (link(argv[1], argv[2]) < 0) + eprintf("link:"); + return 0; +} diff --git a/sbase/ln.1 b/sbase/ln.1 @@ -0,0 +1,36 @@ +.TH LN 1 sbase\-VERSION +.SH NAME +ln \- make links between files +.SH SYNOPSIS +.B ln +.RB [ \-LPfs ] +.I file +.RI [ name ] +.P +.B ln +.RB [ \-LPfs ] +.RI [ file ...] +.RI [ directory ] +.SH DESCRIPTION +.B ln +creates a hard link to a given file, with the given name. If no name is given +it is linked into the current directory. If multiple files are listed they will +be linked into the given directory. +.SH OPTIONS +.TP +.B \-L +create links to the files referenced by symbolic link source files (default +behavior). +.TP +.B \-P +create links to symbolic link source files themselves. +.TP +.B \-f +remove existing destinations. +.TP +.B \-s +create a symlink. +.SH SEE ALSO +.IR cp (1), +.IR link (2), +.IR symlink (2) diff --git a/sbase/ln.c b/sbase/ln.c @@ -0,0 +1,78 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %1$s [-LPfs] target [linkname]\n" + " %1$s [-LPfs] target... directory\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *fname, *to; + int sflag = 0; + int fflag = 0; + int hasto = 0; + int dirfd = AT_FDCWD; + int flags = AT_SYMLINK_FOLLOW; + struct stat st; + + ARGBEGIN { + case 'f': + fflag = 1; + break; + case 's': + sflag = 1; + break; + case 'L': + flags |= AT_SYMLINK_FOLLOW; + break; + case 'P': + flags &= ~AT_SYMLINK_FOLLOW; + break; + default: + usage(); + } ARGEND; + + if (argc == 0) + usage(); + + fname = sflag ? "symlink" : "link"; + + if (argc >= 2) { + if (stat(argv[argc - 1], &st) == 0 && S_ISDIR(st.st_mode)) { + if ((dirfd = open(argv[argc - 1], O_RDONLY)) < 0) + eprintf("open:"); + } else if (argc == 2) { + to = argv[1]; + hasto = 1; + } else { + eprintf("destination is not a directory\n"); + } + argc--; + } + + for (; argc > 0; argc--, argv++) { + if (!hasto) + to = basename(argv[0]); + if (fflag) + remove(to); + if ((!sflag ? linkat(AT_FDCWD, argv[0], dirfd, to, flags) + : symlinkat(argv[0], dirfd, to)) < 0) { + eprintf("%s %s <- %s:", fname, argv[0], to); + } + } + + return 0; +} diff --git a/sbase/logger.1 b/sbase/logger.1 @@ -0,0 +1,50 @@ +.Dd December 4, 2014 +.Dt LOGGER 1 sbase\-VERSION +.Os +.Sh NAME +.Nm logger +.Nd make entries in the system log +.Sh SYNOPSIS +.Nm logger +.Op Fl is +.Op Fl p Ar priority +.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 +.Sh OPTIONS +.Bl -tag -width xxxxxxxxxxxx +.It Fl i +Log the process ID of the logger process with each line. +.It Fl p Ar priority +Enter the message with the specified priority. They priority +has to be specified symbolically as +.Dq facility.level +pair. The default is +.Dq user.notice . +.It Fl s +Log the message to standard error, as well as the system log. +.It Fl t Ar tag +Mark every line in the log with the specified +.Ar tag . +.It Ar message +Write the message to the log; if not specified, standard input +is logged. +.El +.Sh SEE ALSO +.Xr syslog 3 , +.Xr syslogd 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. +.Pp +The flags +.Op Fl ipst +are extensions to that specification. diff --git a/sbase/logger.c b/sbase/logger.c @@ -0,0 +1,91 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#define SYSLOG_NAMES +#include <syslog.h> +#include <unistd.h> + +#include "util.h" + +static int +decodetable(CODE *table, char *name) +{ + CODE *c; + + for (c = table; c->c_name; c++) + if (!strcasecmp(name, c->c_name)) + return c->c_val; + eprintf("invalid priority name: %s\n", name); + /* NOTREACHED */ + return -1; +} + +static int +decodepri(char *pri) +{ + char *lev, *fac = pri; + + if (!(lev = strchr(pri, '.'))) + eprintf("invalid priority name: %s\n", pri); + *lev++ = '\0'; + if (!*lev) + eprintf("invalid priority name: %s\n", pri); + return (decodetable(facilitynames, fac) & LOG_FACMASK) | + (decodetable(prioritynames, lev) & LOG_PRIMASK); +} + +static void +usage(void) +{ + eprintf("usage: %s [-is] [-p priority] [-t tag] [message ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *buf = NULL, *tag = NULL; + size_t sz = 0; + int logflags = 0, priority = LOG_NOTICE; + int i; + + ARGBEGIN { + case 'i': + logflags |= LOG_PID; + break; + case 'p': + priority = decodepri(EARGF(usage())); + break; + case 's': + logflags |= LOG_PERROR; + break; + case 't': + tag = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + + openlog(tag ? tag : getlogin(), logflags, 0); + + if (argc == 0) { + while(getline(&buf, &sz, stdin) != -1) + syslog(priority, "%s", buf); + if (ferror(stdin)) + eprintf("%s: read error:", "<stdin>"); + } else { + for (i = 0; i < argc; i++) + sz += strlen(argv[i]); + sz += argc; + buf = ecalloc(1, sz); + for (i = 0; i < argc; i++) { + strlcat(buf, argv[i], sz); + if (i + 1 < argc) + strlcat(buf, " ", sz); + } + syslog(priority, "%s", buf); + } + closelog(); + return 0; +} diff --git a/sbase/logname.1 b/sbase/logname.1 @@ -0,0 +1,8 @@ +.TH LOGNAME 1 sbase\-VERSION +.SH NAME +logname \- return the user's login name +.SH SYNOPSIS +.B logname +.SH DESCRIPTION +.B logname +prints the login name of the current user. diff --git a/sbase/logname.c b/sbase/logname.c @@ -0,0 +1,20 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <unistd.h> + +#include "util.h" + +int +main(int argc, char *argv[]) +{ + char *login; + + argv0 = argv[0]; + if (argc != 1) + eprintf("usage: %s\n", argv0); + if ((login = getlogin())) + puts(login); + else + eprintf("%s: no login name\n", argv0); + return 0; +} diff --git a/sbase/ls.1 b/sbase/ls.1 @@ -0,0 +1,46 @@ +.Dd January 20, 2015 +.Dt LS 1 sbase\-VERSION +.Sh NAME +.Nm ls +.Nd list directory contents +.Sh SYNOPSIS +.Nm ls +.Op Fl 1acdFHhiLlrtU +.Op Ar file ... +.Sh DESCRIPTION +.Nm +lists each given file, and the contents of each given directory. If no files +are given the current directory is listed. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl a +Show hidden files (those beginning with '.'). +.It Fl c +Use time file's status was last changed instead of last +modification time for sorting or printing. +.It Fl d +List directories themselves, not their contents. +.It Fl F +Append a file type indicator to files. +.It Fl H +List information about the targets of symbolic links specified on the command +line instead of the links themselves. +.It Fl h +Show filesizes in human\-readable format. +.It Fl i +Print the index number of each file. +.It Fl L +List information about the targets of symbolic links instead of the links +themselves. +.It Fl l +List detailed information about each file, including their type, permissions, +links, owner, group, size, and last file status/modification time. +.It Fl r +Reverse the sort order. +.It Fl t +Sort files by last file status/modification time instead of by name. +.It Fl U +Keep the list unsorted. +.El +.Sh SEE ALSO +.Xr stat 2 diff --git a/sbase/ls.c b/sbase/ls.c @@ -0,0 +1,310 @@ +/* See LICENSE file for copyright and license details. */ +#include <dirent.h> +#include <grp.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> + +#include "util.h" + +typedef struct { + char *name; + mode_t mode, tmode; + nlink_t nlink; + uid_t uid; + gid_t gid; + off_t size; + time_t t; + ino_t ino; +} Entry; + +static int entcmp(const void *, const void *); +static void ls(Entry *); +static void lsdir(const char *); +static void mkent(Entry *, char *, int, int); +static void output(Entry *); + +static int aflag = 0; +static int cflag = 0; +static int dflag = 0; +static int Fflag = 0; +static int Hflag = 0; +static int hflag = 0; +static int iflag = 0; +static int Lflag = 0; +static int lflag = 0; +static int rflag = 0; +static int tflag = 0; +static int Uflag = 0; +static int first = 1; +static int many; + +static void +usage(void) +{ + eprintf("usage: %s [-1acdFHhiLlrtU] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int i; + Entry *ents; + + ARGBEGIN { + case '1': + /* ignore */ + break; + case 'a': + aflag = 1; + break; + case 'c': + cflag = 1; + break; + case 'd': + dflag = 1; + break; + case 'F': + Fflag = 1; + break; + case 'H': + Hflag = 1; + break; + case 'h': + hflag = 1; + break; + case 'i': + iflag = 1; + break; + case 'L': + Lflag = 1; + break; + case 'l': + lflag = 1; + break; + case 'r': + rflag = 1; + break; + case 't': + tflag = 1; + break; + case 'U': + Uflag = 1; + break; + default: + usage(); + } ARGEND; + + many = (argc > 1); + if (argc == 0) + *--argv = ".", argc++; + + ents = emalloc(argc * sizeof(*ents)); + + for (i = 0; i < argc; i++) + mkent(&ents[i], argv[i], 1, Hflag || Lflag); + qsort(ents, argc, sizeof *ents, entcmp); + for (i = 0; i < argc; i++) + ls(&ents[rflag ? argc-i-1 : i]); + + return 0; +} + +static int +entcmp(const void *va, const void *vb) +{ + const Entry *a = va, *b = vb; + + if (tflag) + return b->t - a->t; + else + return strcmp(a->name, b->name); +} + +static void +ls(Entry *ent) +{ + if ((S_ISDIR(ent->mode) || (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode) && !Fflag && !lflag)) && !dflag) { + lsdir(ent->name); + } else { + output(ent); + } +} + +static void +lsdir(const char *path) +{ + char *cwd, *p; + long i, n = 0; + struct dirent *d; + DIR *dp; + Entry ent, *ents = NULL; + size_t sz; + + cwd = agetcwd(); + if (!(dp = opendir(path))) + eprintf("opendir %s:", path); + if (chdir(path) < 0) + eprintf("chdir %s:", path); + + if (many) { + if (!first) + putchar('\n'); + printf("%s:\n", path); + first = 0; + } + + while ((d = readdir(dp))) { + if (d->d_name[0] == '.' && !aflag) + continue; + if (Uflag){ + mkent(&ent, d->d_name, Fflag || lflag || iflag, Lflag); + output(&ent); + } else { + ents = erealloc(ents, ++n * sizeof *ents); + p = emalloc((sz = strlen(d->d_name)+1)); + memcpy(p, d->d_name, sz); + mkent(&ents[n-1], p, tflag || Fflag || lflag || iflag, Lflag); + } + } + closedir(dp); + if (!Uflag){ + qsort(ents, n, sizeof *ents, entcmp); + for (i = 0; i < n; i++) { + output(&ents[rflag ? n-i-1 : i]); + free(ents[rflag ? n-i-1 : i].name); + } + } + if (chdir(cwd) < 0) + eprintf("chdir %s:", cwd); + free(ents); + free(cwd); +} + +static void +mkent(Entry *ent, char *path, int dostat, int follow) +{ + struct stat st; + + ent->name = path; + if (!dostat) + return; + if ((follow ? stat : lstat)(path, &st) < 0) + eprintf("%s %s:", follow ? "stat" : "lstat", path); + ent->mode = st.st_mode; + ent->nlink = st.st_nlink; + ent->uid = st.st_uid; + ent->gid = st.st_gid; + ent->size = st.st_size; + ent->t = cflag ? st.st_ctime : st.st_mtime; + ent->ino = st.st_ino; + if (S_ISLNK(ent->mode)) + ent->tmode = stat(path, &st) == 0 ? st.st_mode : 0; +} + +static char * +indicator(mode_t mode) +{ + if (!Fflag) + return ""; + + if (S_ISLNK(mode)) + return "@"; + else if (S_ISDIR(mode)) + return "/"; + else if (S_ISFIFO(mode)) + return "|"; + else if (S_ISSOCK(mode)) + return "="; + else if (mode & S_IXUSR || + mode & S_IXGRP || + mode & S_IXOTH) + return "*"; + else + return ""; +} + +static void +output(Entry *ent) +{ + char buf[BUFSIZ], *fmt; + char mode[] = "----------"; + ssize_t len; + struct group *gr; + struct passwd *pw; + char pwname[_SC_LOGIN_NAME_MAX]; + char grname[_SC_LOGIN_NAME_MAX]; + + if (iflag) + printf("%lu ", (unsigned long)ent->ino); + if (!lflag) { + printf("%s%s\n", ent->name, indicator(ent->mode)); + return; + } + if (S_ISREG(ent->mode)) + mode[0] = '-'; + else if (S_ISBLK(ent->mode)) + mode[0] = 'b'; + else if (S_ISCHR(ent->mode)) + mode[0] = 'c'; + else if (S_ISDIR(ent->mode)) + mode[0] = 'd'; + else if (S_ISFIFO(ent->mode)) + mode[0] = 'p'; + else if (S_ISLNK(ent->mode)) + mode[0] = 'l'; + else if (S_ISSOCK(ent->mode)) + mode[0] = 's'; + else + mode[0] = '?'; + + if (ent->mode & S_IRUSR) mode[1] = 'r'; + if (ent->mode & S_IWUSR) mode[2] = 'w'; + if (ent->mode & S_IXUSR) mode[3] = 'x'; + if (ent->mode & S_IRGRP) mode[4] = 'r'; + if (ent->mode & S_IWGRP) mode[5] = 'w'; + if (ent->mode & S_IXGRP) mode[6] = 'x'; + if (ent->mode & S_IROTH) mode[7] = 'r'; + if (ent->mode & S_IWOTH) mode[8] = 'w'; + if (ent->mode & S_IXOTH) mode[9] = 'x'; + + if (ent->mode & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S'; + if (ent->mode & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S'; + if (ent->mode & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T'; + + pw = getpwuid(ent->uid); + if (pw) + snprintf(pwname, sizeof(pwname), "%s", pw->pw_name); + else + snprintf(pwname, sizeof(pwname), "%d", ent->uid); + + gr = getgrgid(ent->gid); + if (gr) + snprintf(grname, sizeof(grname), "%s", gr->gr_name); + else + snprintf(grname, sizeof(grname), "%d", ent->gid); + + if (time(NULL) > ent->t + (180*24*60*60)) /* 6 months ago? */ + fmt = "%b %d %Y"; + else + fmt = "%b %d %H:%M"; + + strftime(buf, sizeof buf, fmt, localtime(&ent->t)); + printf("%s %4ld %-8.8s %-8.8s ", mode, (long)ent->nlink, pwname, grname); + if (hflag) + printf("%10s ", humansize((unsigned long)ent->size)); + else + printf("%10lu ", (unsigned long)ent->size); + printf("%s %s%s", buf, ent->name, indicator(ent->mode)); + if (S_ISLNK(ent->mode)) { + if ((len = readlink(ent->name, buf, sizeof buf - 1)) < 0) + eprintf("readlink %s:", ent->name); + buf[len] = '\0'; + printf(" -> %s%s", buf, indicator(ent->tmode)); + } + putchar('\n'); +} diff --git a/sbase/md5.h b/sbase/md5.h @@ -0,0 +1,18 @@ +/* public domain md5 implementation based on rfc1321 and libtomcrypt */ + +struct md5 { + uint64_t len; /* processed message length */ + uint32_t h[4]; /* hash state */ + uint8_t buf[64]; /* message block buffer */ +}; + +enum { MD5_DIGEST_LENGTH = 16 }; + +/* reset state */ +void md5_init(void *ctx); +/* process message */ +void md5_update(void *ctx, const void *m, unsigned long len); +/* get message digest */ +/* state is ruined after sum, keep a copy if multiple sum is needed */ +/* part of the message might be left in s, zero it if secrecy is needed */ +void md5_sum(void *ctx, uint8_t md[MD5_DIGEST_LENGTH]); diff --git a/sbase/md5sum.1 b/sbase/md5sum.1 @@ -0,0 +1,12 @@ +.TH MD5SUM 1 sbase\-VERSION +.SH NAME +md5sum \- compute MD5 message digest +.SH SYNOPSIS +.B md5sum +.RB [\-c] +.RI [ file ...] +.TP +.B \-c +read list of MD5 checksums from file and check them +.SH DESCRIPTION +Print MD5 (128-bit) checksums. With no file, read standard input. diff --git a/sbase/md5sum.c b/sbase/md5sum.c @@ -0,0 +1,43 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> + +#include "crypt.h" +#include "md5.h" +#include "util.h" + +static struct md5 s; +struct crypt_ops md5_ops = { + md5_init, + md5_update, + md5_sum, + &s, +}; + +static void +usage(void) +{ + eprintf("usage: %s [-c] [file...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + uint8_t md[MD5_DIGEST_LENGTH]; + char *checkfile = NULL; + int cflag = 0; + + ARGBEGIN { + case 'c': + cflag = 1; + checkfile = ARGF(); + break; + default: + usage(); + } ARGEND; + + if (cflag) + return cryptcheck(checkfile, argc, argv, &md5_ops, md, sizeof(md)); + return cryptmain(argc, argv, &md5_ops, md, sizeof(md)); +} diff --git a/sbase/mkdir.1 b/sbase/mkdir.1 @@ -0,0 +1,19 @@ +.TH MKDIR 1 sbase\-VERSION +.SH NAME +mkdir \- make directory +.SH SYNOPSIS +.B mkdir +.RB [ \-pm ] +.RI [ name ...] +.SH DESCRIPTION +.B mkdir +creates the specified directories. +.SH OPTIONS +.TP +.B \-p +creates any necessary parent directories, and does not fail if the target +already exists. +.B \-m +set the file permission bits of the newly created directory. +.SH SEE ALSO +.IR mkdir (2) diff --git a/sbase/mkdir.c b/sbase/mkdir.c @@ -0,0 +1,68 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "util.h" + +static void mkdirp(char *); + +static void +usage(void) +{ + eprintf("usage: %s [-pm] directory...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int pflag = 0; + int mflag = 0; + int mode; + + ARGBEGIN { + case 'p': + pflag = 1; + break; + case 'm': + mflag = 1; + mode = estrtol(EARGF(usage()), 8); + break; + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + for (; argc > 0; argc--, argv++) { + if (pflag) { + mkdirp(argv[0]); + } else if (mkdir(argv[0], S_IRWXU|S_IRWXG|S_IRWXO) < 0) { + eprintf("mkdir %s:", argv[0]); + } + if (mflag) + if (chmod(argv[0], mode) < 0) + eprintf("chmod %s:", argv[0]); + } + + return 0; +} + +static void +mkdirp(char *path) +{ + char *p = path; + + do { + if (*p && (p = strchr(&p[1], '/'))) + *p = '\0'; + if (mkdir(path, S_IRWXU|S_IRWXG|S_IRWXO) < 0 && errno != EEXIST) + eprintf("mkdir %s:", path); + if (p) + *p = '/'; + } while (p); +} diff --git a/sbase/mkfifo.1 b/sbase/mkfifo.1 @@ -0,0 +1,19 @@ +.TH MKFIFO 1 sbase\-VERSION +.SH NAME +mkfifo \- make named pipe +.SH SYNOPSIS +.B mkfifo +.RB [ \-m +.IR mode ] +.I name ... +.SH DESCRIPTION +.B mkfifo +creates named pipes (FIFOs) with the given names. +.SH OPTIONS +.TP +.B \-m +Set the file permission bits of newly created FIFOs to mode. The mode +is specified in octal as we do not currently support all the formats that +the chmod(1) utility supports. +.SH SEE ALSO +.IR mkfifo (3) diff --git a/sbase/mkfifo.c b/sbase/mkfifo.c @@ -0,0 +1,40 @@ +/* See LICENSE file for copyright and license details. */ +#include <fcntl.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-m mode] name...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | + S_IWGRP | S_IROTH | S_IWOTH; + int ret = 0; + + ARGBEGIN { + case 'm': + mode = estrtol(EARGF(usage()), 8); + break; + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + for (; argc > 0; argc--, argv++) { + if (mkfifo(argv[0], mode) < 0) { + weprintf("mkfifo %s:", argv[0]); + ret = 1; + } + } + return ret; +} diff --git a/sbase/mktemp.1 b/sbase/mktemp.1 @@ -0,0 +1,25 @@ +.TH MKTEMP 1 sbase\-VERSION +.SH NAME +mktemp \- make temporary filename +.SH SYNOPSIS +.B mktemp +.RB [ \-dq ] +.RB [ template ] +.SH DESCRIPTION +.B mktemp +takes the given filename template and overwrites a portion of it +to create a unique filename. The template may be any filename with at least +six `Xs' appended to it. If no template is specified a default of +`tmp.XXXXXXXXXX' is used and the tmpdir is set to `/tmp' unless the +TMPDIR envrionment variable has been set. +.SH OPTIONS +.TP +.B \-d +Make a directory instead of a file +.TP +.B \-q +Fail silently if an error occurs. This is useful if a script +does not want error output to go to standard error. +.SH SEE ALSO +.IR mkdtemp (3), +.IR mkstemp (3) diff --git a/sbase/mktemp.c b/sbase/mktemp.c @@ -0,0 +1,78 @@ +/* See LICENSE file for copyright and license details. */ +#include <libgen.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-dq] [template]\n", argv0); +} + +static int dflag = 0; +static int qflag = 0; + +int +main(int argc, char *argv[]) +{ + char *template = "tmp.XXXXXXXXXX"; + char *tmpdir = "/tmp", *p; + char path[PATH_MAX], tmp[PATH_MAX]; + int fd; + + ARGBEGIN { + case 'd': + dflag = 1; + break; + case 'q': + qflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc > 1) + usage(); + else if (argc == 1) + template = argv[0]; + + if ((p = getenv("TMPDIR"))) + tmpdir = p; + + if (strlcpy(tmp, template, sizeof(tmp)) >= sizeof(tmp)) + eprintf("path too long\n"); + p = dirname(tmp); + if (p[0] != '.') { + if (strlcpy(path, template, sizeof(path)) >= sizeof(path)) + eprintf("path too long\n"); + } else { + if (strlcpy(path, tmpdir, sizeof(path)) >= sizeof(path)) + eprintf("path too long\n"); + if (strlcat(path, "/", sizeof(path)) >= sizeof(path)) + eprintf("path too long\n"); + if (strlcat(path, template, sizeof(path)) >= sizeof(path)) + eprintf("path too long\n"); + } + + if (dflag) { + if (!mkdtemp(path)) { + if (!qflag) + eprintf("mkdtemp %s:", path); + exit(1); + } + } else { + if ((fd = mkstemp(path)) < 0) { + if (!qflag) + eprintf("mkstemp %s:", path); + exit(1); + } + close(fd); + } + puts(path); + return 0; +} diff --git a/sbase/mv.1 b/sbase/mv.1 @@ -0,0 +1,22 @@ +.TH MV 1 sbase\-VERSION +.SH NAME +mv \- move files and directories +.SH SYNOPSIS +.B mv +.RB [ \-f ] +.I file +.RI [ name ] +.P +.B mv +.RB [ \-f ] +.RI [ file ...] +.RI [ directory ] +.SH DESCRIPTION +.B mv +moves or renames a given file or directory, naming it the given name. If +multiple files and directories are listed they will be moved into the given +directory. +.SH OPTIONS +.TP +.B \-f +do not prompt for confirmation before overwriting the destination path. diff --git a/sbase/mv.c b/sbase/mv.c @@ -0,0 +1,54 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "fs.h" +#include "util.h" + +static int mv(const char *, const char *); + +static void +usage(void) +{ + eprintf("usage: %s [-f] source... dest\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct stat st; + + ARGBEGIN { + case 'f': + break; + default: + usage(); + } ARGEND; + + if (argc < 2) + usage(); + + if (argc > 3 && !(stat(argv[argc-1], &st) == 0 && S_ISDIR(st.st_mode))) + eprintf("%s: not a directory\n", argv[argc-1]); + enmasse(argc, &argv[0], mv); + + return 0; +} + +static int +mv(const char *s1, const char *s2) +{ + if (rename(s1, s2) == 0) + return 0; + if (errno == EXDEV) { + cp_rflag = 1; + rm_rflag = 1; + cp(s1, s2); + rm(s1); + return 0; + } + return -1; +} diff --git a/sbase/nice.1 b/sbase/nice.1 @@ -0,0 +1,18 @@ +.TH NICE 1 sbase\-VERSION +.SH NAME +nice \- invoke a utility with an altered nice value +.SH SYNOPSIS +.B nice +.RB [ \-n inc ] +.I command +.RI [ options ...] +.SH DESCRIPTION +.B nice +invokes +.IR command +with a nice value equal to the current nice value plus +.IR inc, +which defaults to 10 if unspecified. + +.SH SEE ALSO +.IR renice (1) nice (2) diff --git a/sbase/nice.c b/sbase/nice.c @@ -0,0 +1,49 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: nice [-n inc] command [options ...]\n"); +} + +int +main(int argc, char *argv[]) +{ + long val = 10; + int savederrno; + + ARGBEGIN { + case 'n': + val = estrtol(EARGF(usage()), 10); + break; + default: + usage(); + break; + } ARGEND; + + if (argc == 0) + usage(); + + errno = 0; + val += getpriority(PRIO_PROCESS, 0); + if (errno != 0) + weprintf("getpriority:"); + val = MAX(PRIO_MIN, MIN(val, PRIO_MAX)); + if (setpriority(PRIO_PROCESS, 0, val) != 0) + weprintf("setpriority:"); + + /* POSIX specifies the nice failure still invokes the command */ + execvp(argv[0], argv); + savederrno = errno; + weprintf("execvp %s:", argv[0]); + return (savederrno == ENOENT)? 127 : 126; +} diff --git a/sbase/nl.1 b/sbase/nl.1 @@ -0,0 +1,46 @@ +.Dd December 4, 2014 +.Dt NL 1 sbase\-VERSION +.Os +.Sh NAME +.Nm nl +.Nd line numbering filter +.Sh SYNOPSIS +.Nm nl +.Op Fl b Ar type +.Op Fl i Ar incr +.Op Fl s Ar sep +.Op Ar file +.Sh DESCRIPTION +.Nm +reads lines from the named +.Ar file +and writes them to stdout with non-empty lines +numbered. If no file is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl b Ar type +Defines which lines will be numbered: +.Bl -tag -width pstringXX +.It a +All lines. +.It n +No lines. +.It t +Only non-empty lines (default). +.It p Ns Ar expr +Only lines which match +.Ar expr , +a regular expression as defined in +.IR regex (7). +.TP +.El +.It Fl i Ar incr +Defines the increment between numbered lines. +.It Fl s Ar sep +Defines the string used to separate line numbers and lines. By default this is +a tab. +.El +.Sh SEE ALSO +.Xr pr 1 diff --git a/sbase/nl.c b/sbase/nl.c @@ -0,0 +1,81 @@ +/* See LICENSE file for copyright and license details. */ +#include <regex.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "text.h" +#include "util.h" + +static void nl(const char *, FILE *); + +static char mode = 't'; +static const char *sep = "\t"; +static long incr = 1; +static regex_t preg; + +static void +usage(void) +{ + eprintf("usage: %s [-b type] [-i incr] [-s sep] [file]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + char *r; + + ARGBEGIN { + case 'b': + r = EARGF(usage()); + mode = r[0]; + if (r[0] == 'p') + eregcomp(&preg, &r[1], REG_NOSUB); + else if (!strchr("ant", mode)) + usage(); + break; + case 'i': + incr = estrtol(EARGF(usage()), 0); + break; + case 's': + sep = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + + if (argc > 1) + usage(); + + if (argc == 0) { + nl("<stdin>", stdin); + } else { + if (!(fp = fopen(argv[0], "r"))) + eprintf("fopen %s:", argv[0]); + nl(argv[0], fp); + fclose(fp); + } + return 0; +} + +void +nl(const char *name, FILE *fp) +{ + char *buf = NULL; + long n = 0; + size_t size = 0; + + while (getline(&buf, &size, fp) != -1) { + if ((mode == 'a') + || (mode == 'p' && !regexec(&preg, buf, 0, NULL, 0)) + || (mode == 't' && buf[0] != '\n')) + printf("%6ld%s%s", n += incr, sep, buf); + else + printf(" %s", buf); + } + free(buf); + if (ferror(fp)) + eprintf("%s: read error:", name); +} diff --git a/sbase/nohup.1 b/sbase/nohup.1 @@ -0,0 +1,16 @@ +.TH NOHUP 1 sbase\-VERSION +.SH NAME +nohup \- run a command immune to hangups +.SH SYNOPSIS +.B nohup +.IR command ... +.SH DESCRIPTION +.B nohup +runs the given command with the +.I HUP +signal set to be ignored. If stdout is a terminal, it is appended to +.I nohup.out +in the current working directory; if stderr is a terminal, it is redirected to +stdout. +.SH SEE ALSO +.IR signal (7) diff --git a/sbase/nohup.c b/sbase/nohup.c @@ -0,0 +1,53 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "util.h" + +enum { Error = 127, Found = 126 }; + +static void +usage(void) +{ + eprintf("usage: %s command [argument...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int fd; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc == 0) + usage(); + + if (signal(SIGHUP, SIG_IGN) == SIG_ERR) + enprintf(Error, "signal HUP:"); + + if (isatty(STDOUT_FILENO)) { + if ((fd = open("nohup.out", O_APPEND|O_CREAT, + S_IRUSR|S_IWUSR)) < 0) { + enprintf(Error, "open nohup.out:"); + } + if (dup2(fd, STDOUT_FILENO) < 0) + enprintf(Error, "dup2:"); + close(fd); + } + if (isatty(STDERR_FILENO)) + if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0) + enprintf(Error, "dup2:"); + + execvp(argv[0], &argv[0]); + enprintf(errno == ENOENT ? Error : Found, "exec %s:", argv[0]); + _exit(Error); + /* unreachable */ + return 0; +} diff --git a/sbase/paste.1 b/sbase/paste.1 @@ -0,0 +1,122 @@ +.TH PASTE 1 paste-VERSION "Apr 2013" +.SH NAME +paste \- merge corresponding or subsequent lines of files +.SH "SYNOPSIS" +.PP +.B paste +[ +.B \-s +] +[ +.B \-d +.I list +] +.I file... +.SH DESCRIPTION +The +.B paste +utility concatenates the corresponding lines of the given input files, +and writes the resulting lines to standard output. The default operation +of +.B paste +concatenates the corresponding lines of the input files. +The newline of every line except the line from the last input file is +replaced with a tab. +If an end-of-file condition is detected on one or more input files, +but not all input files, +.B paste +behaves as though empty lines were read from the files on which +end-of-file was detected, unless the +.B \-s +option is specified. +.SH OPTIONS +.TP +.B \-d list +unless a backslash character appears in +.I list +each character is an element specifying a delimiter. +If a backslash character appears, that and one or more characters +following it are an element specifying a delimiter. +These elements specify one or more characters to use, +instead of the default tab, to replace the newline of the input +lines. The elements in +.I list +are used circularly; that is, when the +.I list +is exhausted the first element from the list is reused. +When the +.B \-s +option is specified, the last newline in a file is not be modified. +The delimiter is reset to the first element of list after each file +operand is processed. +If a backslash character appears in list, it and the character following +it represents the following delimiters: +.RS +.TP +.I \en +newline character +.TP +.I \et +tab character +.TP +.I \e\e +backslash character +.TP +.I \e0 +empty string (not a null character) +.TP +If Any other characters follow the backslash, results are unspecified. +.RE +.TP +.B \-s +concatenate all of the lines of each separate input file in command line +order. The newline of every line except the last line in each input file +are replaced with the tab, unless otherwise specified by the +.B \-d +option. +.PP +If '\-' is specified for one or more input files, the standard input is +used; standard input is read one line at a time, circularly for each +instance of '\-'. +.SH EXIT VALUES +The +.B paste +utility exits 0 on successful completion, and >0 if an error +occurs. +.SH ENVIRONMENT VARIABLES +The following environment variables affect the execution: +.TP +.B LANG +provide a default value for the internationalization variables +that are unset or null. +.TP +.B LC_ALL +if set to a non-empty string value, override the values of all the +other internationalization variables. +.TP +.B LC_CTYPE +determine the locale for the interpretation of sequences of bytes +of text data as characters (for example, single-byte as opposed to +multi-byte characters in arguments and input files). +.TP +.B LC_MESSAGES +determine the locale that should be used to affect the format and +contents of diagnostic messages written to standard error. +.SH CONFORMING TO +The +.B paste +utility is IEEE Std 1003.2 (POSIX.2) compatible. +.SH EXAMPLES +.TP +.I "ls | paste - - - -" +.PP +Write out a directory in four columns. +.TP +.I "paste -s -d '\et\en' file" +.PP +Combine pairs of lines from a file into single lines. +.SH AUTHOR +Written by Lorenzo Cogotti. +.SH SEE ALSO +.BR cut(1) +.BR lam(1) diff --git a/sbase/paste.c b/sbase/paste.c @@ -0,0 +1,225 @@ +/* See LICENSE file for copyright and license details. */ +#include <locale.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <wchar.h> + +#include "util.h" + +typedef struct { + FILE *fp; + const char *name; +} Fdescr; + +static size_t unescape(wchar_t *); +static wint_t in(Fdescr *); +static void out(wchar_t); +static void sequential(Fdescr *, int, const wchar_t *, size_t); +static void parallel(Fdescr *, int, const wchar_t *, size_t); + +static void +usage(void) +{ + eprintf("usage: %s [-s] [-d list] file...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + const char *adelim = NULL; + int seq = 0; + wchar_t *delim = NULL; + size_t len; + Fdescr *dsc = NULL; + int i; + + setlocale(LC_CTYPE, ""); + + ARGBEGIN { + case 's': + seq = 1; + break; + case 'd': + adelim = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + + if (argc == 0) + usage(); + + /* populate delimeters */ + if (!adelim) + adelim = "\t"; + + len = mbstowcs(NULL, adelim, 0); + if (len == (size_t) - 1) + eprintf("invalid delimiter\n"); + + delim = emalloc((len + 1) * sizeof(*delim)); + + mbstowcs(delim, adelim, len); + len = unescape(delim); + if (len == 0) + eprintf("no delimiters specified\n"); + + /* populate file list */ + dsc = emalloc(argc * sizeof(*dsc)); + + for (i = 0; i < argc; i++) { + if (strcmp(argv[i], "-") == 0) + dsc[i].fp = stdin; + else + dsc[i].fp = fopen(argv[i], "r"); + + if (!dsc[i].fp) + eprintf("can't open '%s':", argv[i]); + + dsc[i].name = argv[i]; + } + + if (seq) + sequential(dsc, argc, delim, len); + else + parallel(dsc, argc, delim, len); + + for (i = 0; i < argc; i++) { + if (dsc[i].fp != stdin) + (void)fclose(dsc[i].fp); + } + + free(delim); + free(dsc); + + return 0; +} + +static size_t +unescape(wchar_t *delim) +{ + wchar_t c; + size_t i; + size_t len; + + for (i = 0, len = 0; (c = delim[i++]) != '\0'; len++) { + if (c == '\\') { + switch (delim[i++]) { + case 'n': + delim[len] = '\n'; + break; + case 't': + delim[len] = '\t'; + break; + case '0': + delim[len] = '\0'; + break; + case '\\': + delim[len] = '\\'; + break; + case '\0': + default: + /* POSIX: unspecified results */ + return len; + } + } else + delim[len] = c; + } + + return len; +} + +static wint_t +in(Fdescr *f) +{ + wint_t c = fgetwc(f->fp); + + if (c == WEOF && ferror(f->fp)) + eprintf("'%s' read error:", f->name); + + return c; +} + +static void +out(wchar_t c) +{ + putwchar(c); + if (ferror(stdout)) + eprintf("write error:"); +} + +static void +sequential(Fdescr *dsc, int len, const wchar_t *delim, size_t cnt) +{ + int i; + size_t d; + wint_t c, last; + + for (i = 0; i < len; i++) { + d = 0; + last = WEOF; + + while ((c = in(&dsc[i])) != WEOF) { + if (last == '\n') { + if (delim[d] != '\0') + out(delim[d]); + + d++; + d %= cnt; + } + + if (c != '\n') + out((wchar_t)c); + + last = c; + } + + if (last == '\n') + out((wchar_t)last); + } +} + +static void +parallel(Fdescr *dsc, int len, const wchar_t *delim, size_t cnt) +{ + int last, i; + wint_t c, o; + wchar_t d; + + do { + last = 0; + for (i = 0; i < len; i++) { + d = delim[i % cnt]; + + do { + o = in(&dsc[i]); + c = o; + switch (c) { + case WEOF: + if (last == 0) + break; + + o = '\n'; + /* fallthrough */ + case '\n': + if (i != len - 1) + o = d; + break; + default: + break; + } + + if (o != WEOF) { + /* pad with delimiters up to this point */ + while (++last < i) { + if (d != '\0') + out(d); + } + out((wchar_t)o); + } + } while (c != '\n' && c != WEOF); + } + } while (last > 0); +} diff --git a/sbase/printenv.1 b/sbase/printenv.1 @@ -0,0 +1,19 @@ +.TH PRINTENV 1 sbase\-VERSION +.SH NAME +printenv \- print out the environment or the values of specific variables. +.SH SYNOPSIS +.B printenv +.RB [ var... ] +.SH DESCRIPTION +.B printenv +prints the entire environment as key=values pairs when +no +.IR var +is specified. Otherwise, in the order specified, +.B printenv +prints the value only of each +.IR var, +one per line. + +.SH SEE ALSO +.IR env (1) diff --git a/sbase/printenv.c b/sbase/printenv.c @@ -0,0 +1,38 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> + +#include "util.h" + +extern char **environ; + +static void +usage(void) +{ + eprintf("usage: %s [variable...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *var; + int ret = 0; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc == 0) { + while (*environ) + printf("%s\n", *environ++); + } else { + while (*argv) { + if ((var = getenv(*argv++))) + printf("%s\n", var); + else + ret = 1; + } + } + return ret; +} diff --git a/sbase/printf.1 b/sbase/printf.1 @@ -0,0 +1,386 @@ +.\" $OpenBSD: src/usr.bin/printf/printf.1,v 1.27 2014/05/25 07:36:36 jmc Exp $ +.\" +.\" Copyright (c) 1989, 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: @(#)printf.1 5.11 (Berkeley) 7/24/91 +.\" +.Dd $Mdocdate: May 13 2014 $ +.Dt PRINTF 1 +.Os +.Sh NAME +.Nm printf +.Nd formatted output +.Sh SYNOPSIS +.Nm printf +.Ar format +.Op Ar argument ... +.Sh DESCRIPTION +.Nm printf +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 arguments after the first are treated as strings +if the corresponding format is +.Cm b , +.Cm c +or +.Cm s ; +otherwise it is evaluated as a C constant, with the following extensions: +.Bl -bullet -offset indent +.It +A leading plus or minus sign is allowed. +.It +If the leading character is a single or double quote, the value is the +.Tn ASCII +code of the next character. +.El +.Pp +The format string is reused as often as necessary to satisfy the 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: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It Cm \ee +Write an <escape> character. +.It Cm \ea +Write a <bell> character. +.It Cm \eb +Write a <backspace> character. +.It Cm \ef +Write a <form-feed> character. +.It Cm \en +Write a <new-line> character. +.It Cm \er +Write a <carriage return> character. +.It Cm \et +Write a <tab> character. +.It Cm \ev +Write a <vertical tab> character. +.It Cm \e\' +Write a <single quote> character. +.It Cm \e\e +Write a backslash character. +.It Cm \e Ns Ar num +Write an 8-bit character whose +.Tn ASCII +value is the 1-, 2-, or 3-digit +octal number +.Ar num . +.El +.Pp +Each format specification is introduced by the percent +.Pq Sq \&% +character. +The remainder of the format specifiers include, +in the following order: +.Bl -tag -width Ds +.It "Zero or more of the following flags:" +.Bl -tag -width Ds +.It Cm # +Specifies that the value should be printed in an +.Dq alternate form . +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 a , +.Cm A , +.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. +For all other formats, behaviour is undefined. +.It Cm \&\- +Specifies the +.Em left adjustment +of the output in the indicated field. +.It Cm \&+ +Specifies that there should always be +a sign placed before the number when using signed formats. +.It Sq \&\ \& +A space specifies that a blank should be left before a positive number +for a signed format. +A +.Ql + +overrides a space if both are used. +.It Cm \&0 +A zero character specifies that zero-padding should be used +rather than blank-padding. +This flag is ignored if used with a precision +specifier and any of the +.Cm d , i , o , u , +or +.Cm x +.Pq Cm X +formats. +A +.Ql \&- +overrides a +.Ql \&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 +.Pq Sq \&. , +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; 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 diouxXfFeEgGaAbcs ) . +.El +.Pp +A field width or precision may be +.Ql \&* +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 +is printed as a signed decimal +.Pq Cm d No or Cm i , +unsigned octal, unsigned decimal, +or unsigned hexadecimal +.Pq Cm x No or Cm X , +respectively. +.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. +.Pp +If the argument is infinity, it will be converted to [-]inf +.Pq Cm f +or [-]INF +.Pq Cm F , +respectively. +If the argument is not-a-number (NaN), it will be converted to +[-]nan +.Pq Cm f +or [-]NAN +.Pq Cm F , +respectively. +.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 +.Sq E +is used for an +.Cm E +format. +.Pp +If the argument is infinity, it will be converted to [-]inf +.Pq Cm e +or [-]INF +.Pq Cm E , +respectively. +If the argument is not-a-number (NaN), it will be converted to +[-]nan +.Pq Cm e +or [-]NAN +.Pq Cm E , +respectively. +.It Cm gG +The +.Ar argument +is printed in style +.Cm f +or in style +.Cm e +.Pq Cm E +whichever gives full precision in minimum space. +.Pp +If the argument is infinity, it will be converted to [-]inf +.Pq Cm g +or [-]INF +.Pq Cm G , +respectively. +If the argument is not-a-number (NaN), it will be converted to +[-]nan +.Pq Cm g +or [-]NAN +.Pq Cm G , +respectively. +.It Cm aA +The +.Ar argument +is printed in style +.Sm off +.Pf [\-]0xh Cm \&. No hhh Cm p No [\*(Pm]d +.Sm on +where there is one digit before the hexadecimal point and the number +after is equal to the precision specification for the argument. +When the precision is missing, enough digits are produced to convey +the argument's exact double-precision floating-point representation. +.Pp +If the argument is infinity, it will be converted to [-]inf +.Pq Cm a +or [-]INF +.Pq Cm A , +respectively. +If the argument is not-a-number (NaN), it will be converted to +[-]nan +.Pq Cm a +or [-]NAN +.Pq Cm A , +respectively. +.It Cm b +Characters from the string +.Ar argument +are printed with backslash-escape sequences expanded. +.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; however if the +precision is 0 or missing, all characters in the string are printed. +.It Cm \&% +Print a +.Ql \&% ; +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. +.Sh EXIT STATUS +.Ex -std printf +.Sh EXAMPLES +Convert a hexadecimal value to decimal and print it out: +.Pp +.D1 Ic $ printf \&"%d\en\&" 0x20 +.Pp +Print the decimal representation of the character 'a' (see +.Xr ascii 7 ) : +.Pp +.D1 Ic $ printf \&"%d\en\&" \e'a +.Sh SEE ALSO +.Xr echo 1 , +.Xr printf 3 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. +.Pp +The escape sequences \ee and \e' are extensions to that specification. +.Sh HISTORY +The +.Nm +command appeared in +.Bx 4.3 Reno . +.Sh CAVEATS +It is important never to pass a string with user-supplied data as a +format without using +.Ql %s . +An attacker can put format specifiers in the string to mangle your stack, +leading to a possible security hole. +.Pp +Always be sure to use the proper secure idiom: +.Bd -literal -offset indent +printf "%s" "$STRING" +.Ed +.Sh BUGS +Since arguments are translated from +.Tn ASCII +to floating-point, and +then back again, floating-point precision may be lost. diff --git a/sbase/printf.c b/sbase/printf.c @@ -0,0 +1,497 @@ +/* $OpenBSD: printf.c,v 1.22 2014/05/25 07:36:36 jmc 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 <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <locale.h> +#include <errno.h> +#include <err.h> + +static int print_escape_str(const char *); +static int print_escape(const char *); + +static int getchr(void); +static double getdouble(void); +static int getint(void); +static long getlong(void); +static unsigned long getulong(void); +static char *getstr(void); +static char *mklong(const char *, int); +static void check_conversion(const char *, const char *); +static void usage(void); + +static int rval; +static char **gargv; + +#define isodigit(c) ((c) >= '0' && (c) <= '7') +#define octtobin(c) ((c) - '0') +#define hextobin(c) ((c) >= 'A' && (c) <= 'F' ? c - 'A' + 10 : (c) >= 'a' && (c) <= 'f' ? c - 'a' + 10 : c - '0') + +#define PF(f, func) { \ + if (havefieldwidth) \ + if (haveprecision) \ + (void)printf(f, fieldwidth, precision, func); \ + else \ + (void)printf(f, fieldwidth, func); \ + else if (haveprecision) \ + (void)printf(f, precision, func); \ + else \ + (void)printf(f, func); \ +} + +int +main(int argc, char *argv[]) +{ + char *fmt, *start; + int havefieldwidth, haveprecision; + int fieldwidth, precision; + char convch, nextch; + char *format; + + setlocale (LC_ALL, ""); + + /* Need to accept/ignore "--" option. */ + if (argc > 1 && strcmp(argv[1], "--") == 0) { + argc--; + argv++; + } + + if (argc < 2) { + 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; *fmt; fmt++) { + switch (*fmt) { + case '%': + start = fmt++; + + if (*fmt == '%') { + putchar ('%'); + break; + } else if (*fmt == 'b') { + char *p = getstr(); + if (print_escape_str(p)) { + return (rval); + } + break; + } + + /* skip to field width */ + for (; strchr(SKIP1, *fmt); ++fmt) + ; + if (*fmt == '*') { + ++fmt; + havefieldwidth = 1; + fieldwidth = getint(); + } else + havefieldwidth = 0; + + /* skip to field precision */ + for (; strchr(SKIP2, *fmt); ++fmt) + ; + haveprecision = 0; + if (*fmt == '.') { + ++fmt; + if (*fmt == '*') { + ++fmt; + haveprecision = 1; + precision = getint(); + } + for (; strchr(SKIP2, *fmt); ++fmt) + ; + } + + if (!*fmt) { + warnx ("missing format character"); + return(1); + } + + convch = *fmt; + nextch = *(fmt + 1); + *(fmt + 1) = '\0'; + switch(convch) { + case 'c': { + char p = getchr(); + PF(start, p); + break; + } + case 's': { + char *p = getstr(); + PF(start, p); + break; + } + case 'd': + case 'i': { + long p; + char *f = mklong(start, convch); + if (!f) { + warnx("out of memory"); + return (1); + } + p = getlong(); + PF(f, p); + break; + } + case 'o': + case 'u': + case 'x': + case 'X': { + unsigned long p; + char *f = mklong(start, convch); + if (!f) { + warnx("out of memory"); + return (1); + } + p = getulong(); + PF(f, p); + break; + } + case 'a': + case 'A': + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': { + double p = getdouble(); + PF(start, p); + break; + } + default: + warnx ("%s: invalid directive", start); + return(1); + } + *(fmt + 1) = nextch; + break; + + case '\\': + fmt += print_escape(fmt); + break; + + default: + putchar (*fmt); + break; + } + } + } while (gargv > argv && *gargv); + + return (rval); +} + + +/* + * Print SysV echo(1) style escape string + * Halts processing string and returns 1 if a \c escape is encountered. + */ +static int +print_escape_str(const char *str) +{ + int value; + int c; + + while (*str) { + if (*str == '\\') { + str++; + /* + * %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 (*str == '0') { + str++; + for (c = 3, value = 0; c-- && isodigit(*str); str++) { + value <<= 3; + value += octtobin(*str); + } + putchar (value); + str--; + } else if (*str == 'c') { + return 1; + } else { + str--; + str += print_escape(str); + } + } else { + putchar (*str); + } + str++; + } + + return 0; +} + +/* + * Print "standard" escape characters + */ +static int +print_escape(const char *str) +{ + const char *start = str; + int value; + int c; + + str++; + + switch (*str) { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + for (c = 3, value = 0; c-- && isodigit(*str); str++) { + value <<= 3; + value += octtobin(*str); + } + putchar(value); + return str - start - 1; + /* NOTREACHED */ + + case 'x': + str++; + for (value = 0; isxdigit((unsigned char)*str); str++) { + value <<= 4; + value += hextobin(*str); + } + if (value > UCHAR_MAX) { + warnx ("escape sequence out of range for character"); + rval = 1; + } + putchar (value); + return str - start - 1; + /* NOTREACHED */ + + case '\\': /* backslash */ + putchar('\\'); + break; + + case '\'': /* single quote */ + putchar('\''); + break; + + case '"': /* double quote */ + putchar('"'); + break; + + case 'a': /* alert */ + putchar('\a'); + break; + + case 'b': /* backspace */ + putchar('\b'); + break; + + case 'e': /* escape */ + putchar(033); + break; + + case 'f': /* form-feed */ + putchar('\f'); + break; + + case 'n': /* newline */ + putchar('\n'); + break; + + case 'r': /* carriage-return */ + putchar('\r'); + break; + + case 't': /* tab */ + putchar('\t'); + break; + + case 'v': /* vertical-tab */ + putchar('\v'); + break; + + case '\0': + warnx("null escape sequence"); + rval = 1; + return 0; + + default: + putchar(*str); + warnx("unknown escape sequence `\\%c'", *str); + rval = 1; + } + + return 1; +} + +static char * +mklong(const char *str, int ch) +{ + static char *copy; + static int copysize; + int len; + + len = strlen(str) + 2; + if (copysize < len) { + char *newcopy; + copysize = len + 256; + + newcopy = realloc(copy, copysize); + if (newcopy == NULL) { + copysize = 0; + free(copy); + copy = NULL; + return (NULL); + } + copy = newcopy; + } + (void) memmove(copy, str, len - 3); + copy[len - 3] = 'l'; + copy[len - 2] = ch; + copy[len - 1] = '\0'; + return (copy); +} + +static int +getchr(void) +{ + if (!*gargv) + return((int)'\0'); + return((int)**gargv++); +} + +static char * +getstr(void) +{ + if (!*gargv) + return(""); + return(*gargv++); +} + +static char *number = "+-.0123456789"; +static int +getint(void) +{ + if (!*gargv) + return(0); + + if (strchr(number, **gargv)) + return(atoi(*gargv++)); + + return 0; +} + +static long +getlong(void) +{ + long val; + char *ep; + + if (!*gargv) + return(0L); + + if (**gargv == '\"' || **gargv == '\'') + return (long) *((*gargv++)+1); + + errno = 0; + val = strtol (*gargv, &ep, 0); + check_conversion(*gargv++, ep); + return val; +} + +static unsigned long +getulong(void) +{ + unsigned long val; + char *ep; + + if (!*gargv) + return(0UL); + + if (**gargv == '\"' || **gargv == '\'') + return (unsigned long) *((*gargv++)+1); + + errno = 0; + val = strtoul (*gargv, &ep, 0); + check_conversion(*gargv++, 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) { + (void)fprintf(stderr, "%s: %s\n", s, strerror(ERANGE)); + rval = 1; + } +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: printf format [argument ...]\n"); +} diff --git a/sbase/pwd.1 b/sbase/pwd.1 @@ -0,0 +1,32 @@ +.Dd November 23, 2014 +.Dt PWD 1 sbase\-VERSION +.Os +.Sh NAME +.Nm pwd +.Nd print working directory +.Sh SYNOPSIS +.Nm pwd +.Op Fl LP +.Sh DESCRIPTION +.Nm +prints the path of the current working directory. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl L +Logical path, uses $PWD (default). +.It Fl P +Physical path, avoids all symlinks. +.El +.Sh ENVIRONMENT +.Bl -tag -width PWD +.It Ev PWD +The logical path to the current working directory. +.El +.Sh SEE ALSO +.Xr getcwd 3 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. diff --git a/sbase/pwd.c b/sbase/pwd.c @@ -0,0 +1,52 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "util.h" + +static const char *getpwd(const char *cwd); + +static void +usage(void) +{ + eprintf("usage: %s [-LP]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *cwd; + char mode = 'L'; + + ARGBEGIN { + case 'L': + case 'P': + mode = ARGC(); + break; + default: + usage(); + } ARGEND; + + cwd = agetcwd(); + puts((mode == 'L') ? getpwd(cwd) : cwd); + + return 0; +} + +static const char * +getpwd(const char *cwd) +{ + const char *pwd; + struct stat cst, pst; + + if (!(pwd = getenv("PWD")) || pwd[0] != '/' || stat(pwd, &pst) < 0) + return cwd; + if (stat(cwd, &cst) < 0) + eprintf("stat %s:", cwd); + if (pst.st_dev == cst.st_dev && pst.st_ino == cst.st_ino) + return pwd; + else + return cwd; +} diff --git a/sbase/queue.h b/sbase/queue.h @@ -0,0 +1,648 @@ +/* $OpenBSD: queue.h,v 1.38 2013/07/03 15:05:21 fgsch Exp $ */ +/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft 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. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues, and circular queues. + * + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC)) +#define _Q_INVALIDATE(a) (a) = ((void *)-1) +#else +#define _Q_INVALIDATE(a) +#endif + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List access methods. + */ +#define SLIST_FIRST(head) ((head)->slh_first) +#define SLIST_END(head) NULL +#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_FOREACH(var, head, field) \ + for((var) = SLIST_FIRST(head); \ + (var) != SLIST_END(head); \ + (var) = SLIST_NEXT(var, field)) + +#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SLIST_FIRST(head); \ + (var) && ((tvar) = SLIST_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * Singly-linked List functions. + */ +#define SLIST_INIT(head) { \ + SLIST_FIRST(head) = SLIST_END(head); \ +} + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (0) + +#define SLIST_REMOVE_AFTER(elm, field) do { \ + (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->slh_first; \ + \ + while (curelm->field.sle_next != (elm)) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + _Q_INVALIDATE((elm)->field.sle_next); \ + } \ +} while (0) + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List access methods + */ +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_END(head) NULL +#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_FOREACH(var, head, field) \ + for((var) = LIST_FIRST(head); \ + (var)!= LIST_END(head); \ + (var) = LIST_NEXT(var, field)) + +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST(head); \ + (var) && ((tvar) = LIST_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * List functions. + */ +#define LIST_INIT(head) do { \ + LIST_FIRST(head) = LIST_END(head); \ +} while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (0) + +#define LIST_REMOVE(elm, field) do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +#define LIST_REPLACE(elm, elm2, field) do { \ + if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ + (elm2)->field.le_next->field.le_prev = \ + &(elm2)->field.le_next; \ + (elm2)->field.le_prev = (elm)->field.le_prev; \ + *(elm2)->field.le_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue access methods. + */ +#define SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define SIMPLEQ_END(head) NULL +#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) +#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + +#define SIMPLEQ_FOREACH(var, head, field) \ + for((var) = SIMPLEQ_FIRST(head); \ + (var) != SIMPLEQ_END(head); \ + (var) = SIMPLEQ_NEXT(var, field)) + +#define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SIMPLEQ_FIRST(head); \ + (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * Simple queue functions. + */ +#define SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (0) + +#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ + if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \ + == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +/* + * XOR Simple queue definitions. + */ +#define XSIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqx_first; /* first element */ \ + struct type **sqx_last; /* addr of last next element */ \ + unsigned long sqx_cookie; \ +} + +#define XSIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqx_next; /* next element */ \ +} + +/* + * XOR Simple queue access methods. + */ +#define XSIMPLEQ_XOR(head, ptr) ((__typeof(ptr))((head)->sqx_cookie ^ \ + (unsigned long)(ptr))) +#define XSIMPLEQ_FIRST(head) XSIMPLEQ_XOR(head, ((head)->sqx_first)) +#define XSIMPLEQ_END(head) NULL +#define XSIMPLEQ_EMPTY(head) (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head)) +#define XSIMPLEQ_NEXT(head, elm, field) XSIMPLEQ_XOR(head, ((elm)->field.sqx_next)) + + +#define XSIMPLEQ_FOREACH(var, head, field) \ + for ((var) = XSIMPLEQ_FIRST(head); \ + (var) != XSIMPLEQ_END(head); \ + (var) = XSIMPLEQ_NEXT(head, var, field)) + +#define XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = XSIMPLEQ_FIRST(head); \ + (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1); \ + (var) = (tvar)) + +/* + * XOR Simple queue functions. + */ +#define XSIMPLEQ_INIT(head) do { \ + arc4random_buf(&(head)->sqx_cookie, sizeof((head)->sqx_cookie)); \ + (head)->sqx_first = XSIMPLEQ_XOR(head, NULL); \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ +} while (0) + +#define XSIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqx_next = (head)->sqx_first) == \ + XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ + (head)->sqx_first = XSIMPLEQ_XOR(head, (elm)); \ +} while (0) + +#define XSIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL); \ + *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = XSIMPLEQ_XOR(head, (elm)); \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ +} while (0) + +#define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqx_next = (listelm)->field.sqx_next) == \ + XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ + (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm)); \ +} while (0) + +#define XSIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqx_first = XSIMPLEQ_XOR(head, \ + (head)->sqx_first)->field.sqx_next) == XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ +} while (0) + +#define XSIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ + if (((elm)->field.sqx_next = XSIMPLEQ_XOR(head, \ + (elm)->field.sqx_next)->field.sqx_next) \ + == XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = \ + XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ +} while (0) + + +/* + * Tail queue definitions. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ +} + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first } + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/* + * tail queue access methods + */ +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_END(head) NULL +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +/* XXX */ +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define TAILQ_EMPTY(head) \ + (TAILQ_FIRST(head) == TAILQ_END(head)) + +#define TAILQ_FOREACH(var, head, field) \ + for((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_NEXT(var, field)) + +#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head) && \ + ((tvar) = TAILQ_NEXT(var, field), 1); \ + (var) = (tvar)) + + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_PREV(var, headname, field)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head) && \ + ((tvar) = TAILQ_PREV(var, headname, field), 1); \ + (var) = (tvar)) + +/* + * Tail queue functions. + */ +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +#define TAILQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +/* + * Circular queue definitions. + */ +#define CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define CIRCLEQ_HEAD_INITIALIZER(head) \ + { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } + +#define CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue access methods + */ +#define CIRCLEQ_FIRST(head) ((head)->cqh_first) +#define CIRCLEQ_LAST(head) ((head)->cqh_last) +#define CIRCLEQ_END(head) ((void *)(head)) +#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) +#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) +#define CIRCLEQ_EMPTY(head) \ + (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head)) + +#define CIRCLEQ_FOREACH(var, head, field) \ + for((var) = CIRCLEQ_FIRST(head); \ + (var) != CIRCLEQ_END(head); \ + (var) = CIRCLEQ_NEXT(var, field)) + +#define CIRCLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = CIRCLEQ_FIRST(head); \ + (var) != CIRCLEQ_END(head) && \ + ((tvar) = CIRCLEQ_NEXT(var, field), 1); \ + (var) = (tvar)) + +#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ + for((var) = CIRCLEQ_LAST(head); \ + (var) != CIRCLEQ_END(head); \ + (var) = CIRCLEQ_PREV(var, field)) + +#define CIRCLEQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = CIRCLEQ_LAST(head, headname); \ + (var) != CIRCLEQ_END(head) && \ + ((tvar) = CIRCLEQ_PREV(var, headname, field), 1); \ + (var) = (tvar)) + +/* + * Circular queue functions. + */ +#define CIRCLEQ_INIT(head) do { \ + (head)->cqh_first = CIRCLEQ_END(head); \ + (head)->cqh_last = CIRCLEQ_END(head); \ +} while (0) + +#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = CIRCLEQ_END(head); \ + if ((head)->cqh_last == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.cqe_next = CIRCLEQ_END(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} while (0) + +#define CIRCLEQ_REMOVE(head, elm, field) do { \ + if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ + _Q_INVALIDATE((elm)->field.cqe_prev); \ + _Q_INVALIDATE((elm)->field.cqe_next); \ +} while (0) + +#define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \ + CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm2); \ + else \ + (elm2)->field.cqe_next->field.cqe_prev = (elm2); \ + if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \ + CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm2); \ + else \ + (elm2)->field.cqe_prev->field.cqe_next = (elm2); \ + _Q_INVALIDATE((elm)->field.cqe_prev); \ + _Q_INVALIDATE((elm)->field.cqe_next); \ +} while (0) + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/sbase/readlink.1 b/sbase/readlink.1 @@ -0,0 +1,25 @@ +.TH READLINK 1 sbase\-VERSION +.SH NAME +readlink \- print value of a symbolic link or canonical file name +.SH SYNOPSIS +.B readlink +.RB [ \-fn ] +.IR file +.SH DESCRIPTION +The readlink utility when invoked with the pathname of a symbolic link as +its argument dereferences the symbolic link and prints the name of target +on standard output. If the -f option is not specified and readlink is +invoked with an argument other than the pathname of a symbolic link, it +exits with a nonzero exit code without printing anything. +.SH OPTIONS +.TP +.B \-f +Canonicalize by following every symlink in every component of the +given path recursively. The argument does not need to be a symbolic +link. +.TP +.B \-n +Do not output the trailing newline. +.SH SEE ALSO +.IR readlink (2), +.IR realpath (3) diff --git a/sbase/readlink.c b/sbase/readlink.c @@ -0,0 +1,59 @@ +/* See LICENSE file for copyright and license details. */ +#include <unistd.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-efmn] file\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char buf[PATH_MAX]; + int nflag = 0; + int fflag = 0; + ssize_t n; + + ARGBEGIN { + case 'e': + case 'm': + eprintf("not implemented\n"); + case 'f': + fflag = 1; + break; + case 'n': + nflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc != 1) + usage(); + + if (strlen(argv[0]) > PATH_MAX - 1) + eprintf("path too long\n"); + + if (fflag) { + if (!realpath(argv[0], buf)) + exit(1); + } else { + if ((n = readlink(argv[0], buf, sizeof(buf) - 1)) < 0) + exit(1); + buf[n] = '\0'; + } + + printf("%s", buf); + if (!nflag) + putchar('\n'); + + return 0; +} diff --git a/sbase/renice.1 b/sbase/renice.1 @@ -0,0 +1,97 @@ +.TH RENICE 1 renice-VERSION "Jun 2013" +.SH NAME +renice \- set nice values of running processes +.SH "SYNOPSIS" +.PP +.B renice +.B \-n +.I increment +[ +.B \-g +| +.B \-p +| +.B \-u +] +.I ID... +.SH DESCRIPTION +The +.B renice +utility requests that the nice values of one or more +running processes be changed. By default, the applicable processes +are specified by their process IDs. When a process group is specified +(see +.B -g +), the request applies to all processes in the process group. If the +requested increment would raise or lower the nice value of the +executed utility beyond its limits, then the limit whose value was +exceeded is used. When a user is reniced, the request applies to all +processes whose saved set-user-ID matches the user ID corresponding to +the user. Regardless of which options are supplied or any other factor, +renice does not alter the nice values of any process unless the user +requesting such a change has appropriate privileges to do so for the +specified process. If the user lacks appropriate privileges to perform +the requested action, the utility returns an error status. +The saved set-user-ID of the user's process is checked instead of its +effective user ID when renice attempts to determine the user ID of the +process in order to determine whether the user has appropriate privileges. +.SH OPTIONS +.TP +.B \-g +interpret all operands as unsigned decimal integer process group IDs. +.TP +.B \-n +.I increment +specify how the nice value of the specified process or processes +is to be adjusted. The increment option-argument is a positive or +negative decimal integer used to modify the nice value of the +specified process or processes. positive increment values cause a +lower nice value. Negative increment values may require appropriate +privileges and cause a higher nice value. +.TP +.B \-p +interpret all operands as unsigned decimal integer process IDs. +The +.B \-p +option is the default if no options are specified. +.TP +.B \-u +interpret all operands as users. If a user exists with a user name +equal to the operand, then the user ID of that user is used in further +processing. Otherwise, if the operand represents an unsigned decimal +integer, used as the numeric user ID of the user. +.SH EXIT VALUES +On successful completion 0 is returned, a value which is >0 is +returned on error. +.SH FILES +.TP +.I /etc/passwd +used to map user names to user ID's. +.SH CONFORMING TO +The +.B renice +utility is IEEE Std 1003.1-2001 (POSIX.1) compatible. +.SH EXAMPLES +.TP +.I "renice -n 5 -p 987 32" +.PP +Adjust the nice value so that process IDs 987 and 32 would have a +lower nice value. +.TP +.I "renice -n -4 -g 324 76" +.PP +Adjust the nice value so that group IDs 324 and 76 would have a +higher nice value, if the user has the appropriate privileges to do so. +.TP +.I "renice -n 4 -u 8 sas" +.PP +Adjust the nice value so that numeric user ID 8 and user sas would +have a lower nice value. +Useful nice value increments on historical systems include +19 or 20 (the affected processes run only when nothing else in the +system attempts to run) and any negative number +(to make processes run faster). +.SH AUTHOR +Written by Lorenzo Cogotti. +.SH SEE ALSO +.BR nice(1) diff --git a/sbase/renice.c b/sbase/renice.c @@ -0,0 +1,114 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <limits.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/resource.h> + +#include "util.h" + +static int strtop(const char *); +static int renice(int, int, long); + +static void +usage(void) +{ + eprintf("renice -n inc [-g | -p | -u] ID ...\n"); +} + +int +main(int argc, char *argv[]) +{ + const char *adj = NULL; + long val; + int i, which = PRIO_PROCESS, status = 0; + struct passwd *pw; + int who; + + ARGBEGIN { + case 'n': + adj = EARGF(usage()); + break; + case 'g': + which = PRIO_PGRP; + break; + case 'p': + which = PRIO_PROCESS; + break; + case 'u': + which = PRIO_USER; + break; + default: + usage(); + break; + } ARGEND; + + if (argc == 0 || !adj) + usage(); + + val = estrtol(adj, 10); + for (i = 0; i < argc; i++) { + who = -1; + if (which == PRIO_USER) { + errno = 0; + pw = getpwnam(argv[i]); + if (!pw) { + if (errno != 0) + weprintf("getpwnam %s:", argv[i]); + else + weprintf("getpwnam %s: no user found\n", argv[i]); + status = 1; + continue; + } + who = pw->pw_uid; + } + if (who < 0) + who = strtop(argv[i]); + + if (who < 0 || !renice(which, who, val)) + status = 1; + } + + return status; +} + +static int +strtop(const char *s) +{ + char *end; + long n; + + errno = 0; + n = strtol(s, &end, 10); + if (*end != '\0') { + weprintf("%s: not an integer\n", s); + return -1; + } + if (errno != 0 || n <= 0 || n > INT_MAX) { + weprintf("%s: invalid value\n", s); + return -1; + } + + return (int)n; +} + +static int +renice(int which, int who, long adj) +{ + errno = 0; + adj += getpriority(which, who); + if (errno != 0) { + weprintf("getpriority %d:", who); + return 0; + } + + adj = MAX(PRIO_MIN, MIN(adj, PRIO_MAX)); + if (setpriority(which, who, (int)adj) < 0) { + weprintf("setpriority %d:", who); + return 0; + } + + return 1; +} diff --git a/sbase/rm.1 b/sbase/rm.1 @@ -0,0 +1,22 @@ +.TH RM 1 sbase\-VERSION +.SH NAME +rm \- remove files and directories +.SH SYNOPSIS +.B rm +.RB [ \-fRr ] +.RI [ file ...] +.SH DESCRIPTION +.B rm +removes the given files and directories. +.SH OPTIONS +.TP +.B \-f +ignore files that cannot be removed. +.TP +.B \-R +equivalent to -r. +.TP +.B \-r +remove directories recursively. +.SH SEE ALSO +.IR remove (3) diff --git a/sbase/rm.c b/sbase/rm.c @@ -0,0 +1,42 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "fs.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-fRr] FILE...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + case 'f': + rm_fflag = 1; + break; + case 'R': + case 'r': + rm_rflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc < 1) { + if (!rm_fflag) + usage(); + else + return 0; + } + + for (; argc > 0; argc--, argv++) + rm(argv[0]); + + return 0; +} diff --git a/sbase/rmdir.1 b/sbase/rmdir.1 @@ -0,0 +1,25 @@ +.TH RMDIR 1 sbase\-VERSION +.SH NAME +rmdir \- remove a directory +.SH SYNOPSIS +.B rmdir +.I directory... +.SH DESCRIPTION +.B rmdir +attempts to remove all non-full directories specified +by +.IR directory. +.SH BUGS +Subdirectories are removed in the order specified, so +.nf + rmdir foo/bar foo +.fi +will be successful, but +.nf + rmdir foo foo/bar +.fi +will only succeed in removing +.BR foo/bar. + +.SH SEE ALSO +.IR rm (1) rmdir (2) unlink (1) unlink (2) diff --git a/sbase/rmdir.c b/sbase/rmdir.c @@ -0,0 +1,29 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: rmdir dir...\n"); +} + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + for (; argc > 0; argc--, argv++) + if (rmdir(argv[0]) < 0) + weprintf("rmdir %s:", argv[0]); + return 0; +} diff --git a/sbase/runetypebody.h b/sbase/runetypebody.h @@ -0,0 +1,1886 @@ +/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* Automatically generated from UnicodeData-6.1.0.txt by mkrunetype.awk */ + +static Rune alpha2[][2] = { + { 0x0041, 0x005A }, + { 0x0061, 0x007A }, + { 0x00C0, 0x00D6 }, + { 0x00D8, 0x00F6 }, + { 0x00F8, 0x02C1 }, + { 0x02C6, 0x02D1 }, + { 0x02E0, 0x02E4 }, + { 0x0370, 0x0374 }, + { 0x0376, 0x0377 }, + { 0x037A, 0x037D }, + { 0x0388, 0x038A }, + { 0x038E, 0x03A1 }, + { 0x03A3, 0x03F5 }, + { 0x03F7, 0x0481 }, + { 0x048A, 0x0527 }, + { 0x0531, 0x0556 }, + { 0x0561, 0x0587 }, + { 0x05D0, 0x05EA }, + { 0x05F0, 0x05F2 }, + { 0x0620, 0x064A }, + { 0x066E, 0x066F }, + { 0x0671, 0x06D3 }, + { 0x06E5, 0x06E6 }, + { 0x06EE, 0x06EF }, + { 0x06FA, 0x06FC }, + { 0x0712, 0x072F }, + { 0x074D, 0x07A5 }, + { 0x07CA, 0x07EA }, + { 0x07F4, 0x07F5 }, + { 0x0800, 0x0815 }, + { 0x0840, 0x0858 }, + { 0x08A2, 0x08AC }, + { 0x0904, 0x0939 }, + { 0x0958, 0x0961 }, + { 0x0971, 0x0977 }, + { 0x0979, 0x097F }, + { 0x0985, 0x098C }, + { 0x098F, 0x0990 }, + { 0x0993, 0x09A8 }, + { 0x09AA, 0x09B0 }, + { 0x09B6, 0x09B9 }, + { 0x09DC, 0x09DD }, + { 0x09DF, 0x09E1 }, + { 0x09F0, 0x09F1 }, + { 0x0A05, 0x0A0A }, + { 0x0A0F, 0x0A10 }, + { 0x0A13, 0x0A28 }, + { 0x0A2A, 0x0A30 }, + { 0x0A32, 0x0A33 }, + { 0x0A35, 0x0A36 }, + { 0x0A38, 0x0A39 }, + { 0x0A59, 0x0A5C }, + { 0x0A72, 0x0A74 }, + { 0x0A85, 0x0A8D }, + { 0x0A8F, 0x0A91 }, + { 0x0A93, 0x0AA8 }, + { 0x0AAA, 0x0AB0 }, + { 0x0AB2, 0x0AB3 }, + { 0x0AB5, 0x0AB9 }, + { 0x0AE0, 0x0AE1 }, + { 0x0B05, 0x0B0C }, + { 0x0B0F, 0x0B10 }, + { 0x0B13, 0x0B28 }, + { 0x0B2A, 0x0B30 }, + { 0x0B32, 0x0B33 }, + { 0x0B35, 0x0B39 }, + { 0x0B5C, 0x0B5D }, + { 0x0B5F, 0x0B61 }, + { 0x0B85, 0x0B8A }, + { 0x0B8E, 0x0B90 }, + { 0x0B92, 0x0B95 }, + { 0x0B99, 0x0B9A }, + { 0x0B9E, 0x0B9F }, + { 0x0BA3, 0x0BA4 }, + { 0x0BA8, 0x0BAA }, + { 0x0BAE, 0x0BB9 }, + { 0x0C05, 0x0C0C }, + { 0x0C0E, 0x0C10 }, + { 0x0C12, 0x0C28 }, + { 0x0C2A, 0x0C33 }, + { 0x0C35, 0x0C39 }, + { 0x0C58, 0x0C59 }, + { 0x0C60, 0x0C61 }, + { 0x0C85, 0x0C8C }, + { 0x0C8E, 0x0C90 }, + { 0x0C92, 0x0CA8 }, + { 0x0CAA, 0x0CB3 }, + { 0x0CB5, 0x0CB9 }, + { 0x0CE0, 0x0CE1 }, + { 0x0CF1, 0x0CF2 }, + { 0x0D05, 0x0D0C }, + { 0x0D0E, 0x0D10 }, + { 0x0D12, 0x0D3A }, + { 0x0D60, 0x0D61 }, + { 0x0D7A, 0x0D7F }, + { 0x0D85, 0x0D96 }, + { 0x0D9A, 0x0DB1 }, + { 0x0DB3, 0x0DBB }, + { 0x0DC0, 0x0DC6 }, + { 0x0E01, 0x0E30 }, + { 0x0E32, 0x0E33 }, + { 0x0E40, 0x0E46 }, + { 0x0E81, 0x0E82 }, + { 0x0E87, 0x0E88 }, + { 0x0E94, 0x0E97 }, + { 0x0E99, 0x0E9F }, + { 0x0EA1, 0x0EA3 }, + { 0x0EAA, 0x0EAB }, + { 0x0EAD, 0x0EB0 }, + { 0x0EB2, 0x0EB3 }, + { 0x0EC0, 0x0EC4 }, + { 0x0EDC, 0x0EDF }, + { 0x0F40, 0x0F47 }, + { 0x0F49, 0x0F6C }, + { 0x0F88, 0x0F8C }, + { 0x1000, 0x102A }, + { 0x1050, 0x1055 }, + { 0x105A, 0x105D }, + { 0x1065, 0x1066 }, + { 0x106E, 0x1070 }, + { 0x1075, 0x1081 }, + { 0x10A0, 0x10C5 }, + { 0x10D0, 0x10FA }, + { 0x10FC, 0x1248 }, + { 0x124A, 0x124D }, + { 0x1250, 0x1256 }, + { 0x125A, 0x125D }, + { 0x1260, 0x1288 }, + { 0x128A, 0x128D }, + { 0x1290, 0x12B0 }, + { 0x12B2, 0x12B5 }, + { 0x12B8, 0x12BE }, + { 0x12C2, 0x12C5 }, + { 0x12C8, 0x12D6 }, + { 0x12D8, 0x1310 }, + { 0x1312, 0x1315 }, + { 0x1318, 0x135A }, + { 0x1380, 0x138F }, + { 0x13A0, 0x13F4 }, + { 0x1401, 0x166C }, + { 0x166F, 0x167F }, + { 0x1681, 0x169A }, + { 0x16A0, 0x16EA }, + { 0x1700, 0x170C }, + { 0x170E, 0x1711 }, + { 0x1720, 0x1731 }, + { 0x1740, 0x1751 }, + { 0x1760, 0x176C }, + { 0x176E, 0x1770 }, + { 0x1780, 0x17B3 }, + { 0x1820, 0x1877 }, + { 0x1880, 0x18A8 }, + { 0x18B0, 0x18F5 }, + { 0x1900, 0x191C }, + { 0x1950, 0x196D }, + { 0x1970, 0x1974 }, + { 0x1980, 0x19AB }, + { 0x19C1, 0x19C7 }, + { 0x1A00, 0x1A16 }, + { 0x1A20, 0x1A54 }, + { 0x1B05, 0x1B33 }, + { 0x1B45, 0x1B4B }, + { 0x1B83, 0x1BA0 }, + { 0x1BAE, 0x1BAF }, + { 0x1BBA, 0x1BE5 }, + { 0x1C00, 0x1C23 }, + { 0x1C4D, 0x1C4F }, + { 0x1C5A, 0x1C7D }, + { 0x1CE9, 0x1CEC }, + { 0x1CEE, 0x1CF1 }, + { 0x1CF5, 0x1CF6 }, + { 0x1D00, 0x1DBF }, + { 0x1E00, 0x1F15 }, + { 0x1F18, 0x1F1D }, + { 0x1F20, 0x1F45 }, + { 0x1F48, 0x1F4D }, + { 0x1F50, 0x1F57 }, + { 0x1F5F, 0x1F7D }, + { 0x1F80, 0x1FB4 }, + { 0x1FB6, 0x1FBC }, + { 0x1FC2, 0x1FC4 }, + { 0x1FC6, 0x1FCC }, + { 0x1FD0, 0x1FD3 }, + { 0x1FD6, 0x1FDB }, + { 0x1FE0, 0x1FEC }, + { 0x1FF2, 0x1FF4 }, + { 0x1FF6, 0x1FFC }, + { 0x2090, 0x209C }, + { 0x210A, 0x2113 }, + { 0x2119, 0x211D }, + { 0x212A, 0x212D }, + { 0x212F, 0x2139 }, + { 0x213C, 0x213F }, + { 0x2145, 0x2149 }, + { 0x2183, 0x2184 }, + { 0x2C00, 0x2C2E }, + { 0x2C30, 0x2C5E }, + { 0x2C60, 0x2CE4 }, + { 0x2CEB, 0x2CEE }, + { 0x2CF2, 0x2CF3 }, + { 0x2D00, 0x2D25 }, + { 0x2D30, 0x2D67 }, + { 0x2D80, 0x2D96 }, + { 0x2DA0, 0x2DA6 }, + { 0x2DA8, 0x2DAE }, + { 0x2DB0, 0x2DB6 }, + { 0x2DB8, 0x2DBE }, + { 0x2DC0, 0x2DC6 }, + { 0x2DC8, 0x2DCE }, + { 0x2DD0, 0x2DD6 }, + { 0x2DD8, 0x2DDE }, + { 0x3005, 0x3006 }, + { 0x3031, 0x3035 }, + { 0x303B, 0x303C }, + { 0x3041, 0x3096 }, + { 0x309D, 0x309F }, + { 0x30A1, 0x30FA }, + { 0x30FC, 0x30FF }, + { 0x3105, 0x312D }, + { 0x3131, 0x318E }, + { 0x31A0, 0x31BA }, + { 0x31F0, 0x31FF }, + { 0xA000, 0xA48C }, + { 0xA4D0, 0xA4FD }, + { 0xA500, 0xA60C }, + { 0xA610, 0xA61F }, + { 0xA62A, 0xA62B }, + { 0xA640, 0xA66E }, + { 0xA67F, 0xA697 }, + { 0xA6A0, 0xA6E5 }, + { 0xA717, 0xA71F }, + { 0xA722, 0xA788 }, + { 0xA78B, 0xA78E }, + { 0xA790, 0xA793 }, + { 0xA7A0, 0xA7AA }, + { 0xA7F8, 0xA801 }, + { 0xA803, 0xA805 }, + { 0xA807, 0xA80A }, + { 0xA80C, 0xA822 }, + { 0xA840, 0xA873 }, + { 0xA882, 0xA8B3 }, + { 0xA8F2, 0xA8F7 }, + { 0xA90A, 0xA925 }, + { 0xA930, 0xA946 }, + { 0xA960, 0xA97C }, + { 0xA984, 0xA9B2 }, + { 0xAA00, 0xAA28 }, + { 0xAA40, 0xAA42 }, + { 0xAA44, 0xAA4B }, + { 0xAA60, 0xAA76 }, + { 0xAA80, 0xAAAF }, + { 0xAAB5, 0xAAB6 }, + { 0xAAB9, 0xAABD }, + { 0xAADB, 0xAADD }, + { 0xAAE0, 0xAAEA }, + { 0xAAF2, 0xAAF4 }, + { 0xAB01, 0xAB06 }, + { 0xAB09, 0xAB0E }, + { 0xAB11, 0xAB16 }, + { 0xAB20, 0xAB26 }, + { 0xAB28, 0xAB2E }, + { 0xABC0, 0xABE2 }, + { 0xD7B0, 0xD7C6 }, + { 0xD7CB, 0xD7FB }, + { 0xF900, 0xFA6D }, + { 0xFA70, 0xFAD9 }, + { 0xFB00, 0xFB06 }, + { 0xFB13, 0xFB17 }, + { 0xFB1F, 0xFB28 }, + { 0xFB2A, 0xFB36 }, + { 0xFB38, 0xFB3C }, + { 0xFB40, 0xFB41 }, + { 0xFB43, 0xFB44 }, + { 0xFB46, 0xFBB1 }, + { 0xFBD3, 0xFD3D }, + { 0xFD50, 0xFD8F }, + { 0xFD92, 0xFDC7 }, + { 0xFDF0, 0xFDFB }, + { 0xFE70, 0xFE74 }, + { 0xFE76, 0xFEFC }, + { 0xFF21, 0xFF3A }, + { 0xFF41, 0xFF5A }, + { 0xFF66, 0xFFBE }, + { 0xFFC2, 0xFFC7 }, + { 0xFFCA, 0xFFCF }, + { 0xFFD2, 0xFFD7 }, + { 0xFFDA, 0xFFDC }, + { 0x10000, 0x1000B }, + { 0x1000D, 0x10026 }, + { 0x10028, 0x1003A }, + { 0x1003C, 0x1003D }, + { 0x1003F, 0x1004D }, + { 0x10050, 0x1005D }, + { 0x10080, 0x100FA }, + { 0x10280, 0x1029C }, + { 0x102A0, 0x102D0 }, + { 0x10300, 0x1031E }, + { 0x10330, 0x10340 }, + { 0x10342, 0x10349 }, + { 0x10380, 0x1039D }, + { 0x103A0, 0x103C3 }, + { 0x103C8, 0x103CF }, + { 0x10400, 0x1049D }, + { 0x10800, 0x10805 }, + { 0x1080A, 0x10835 }, + { 0x10837, 0x10838 }, + { 0x1083F, 0x10855 }, + { 0x10900, 0x10915 }, + { 0x10920, 0x10939 }, + { 0x10980, 0x109B7 }, + { 0x109BE, 0x109BF }, + { 0x10A10, 0x10A13 }, + { 0x10A15, 0x10A17 }, + { 0x10A19, 0x10A33 }, + { 0x10A60, 0x10A7C }, + { 0x10B00, 0x10B35 }, + { 0x10B40, 0x10B55 }, + { 0x10B60, 0x10B72 }, + { 0x10C00, 0x10C48 }, + { 0x11003, 0x11037 }, + { 0x11083, 0x110AF }, + { 0x110D0, 0x110E8 }, + { 0x11103, 0x11126 }, + { 0x11183, 0x111B2 }, + { 0x111C1, 0x111C4 }, + { 0x11680, 0x116AA }, + { 0x12000, 0x1236E }, + { 0x13000, 0x1342E }, + { 0x16800, 0x16A38 }, + { 0x16F00, 0x16F44 }, + { 0x16F93, 0x16F9F }, + { 0x1B000, 0x1B001 }, + { 0x1D400, 0x1D454 }, + { 0x1D456, 0x1D49C }, + { 0x1D49E, 0x1D49F }, + { 0x1D4A5, 0x1D4A6 }, + { 0x1D4A9, 0x1D4AC }, + { 0x1D4AE, 0x1D4B9 }, + { 0x1D4BD, 0x1D4C3 }, + { 0x1D4C5, 0x1D505 }, + { 0x1D507, 0x1D50A }, + { 0x1D50D, 0x1D514 }, + { 0x1D516, 0x1D51C }, + { 0x1D51E, 0x1D539 }, + { 0x1D53B, 0x1D53E }, + { 0x1D540, 0x1D544 }, + { 0x1D54A, 0x1D550 }, + { 0x1D552, 0x1D6A5 }, + { 0x1D6A8, 0x1D6C0 }, + { 0x1D6C2, 0x1D6DA }, + { 0x1D6DC, 0x1D6FA }, + { 0x1D6FC, 0x1D714 }, + { 0x1D716, 0x1D734 }, + { 0x1D736, 0x1D74E }, + { 0x1D750, 0x1D76E }, + { 0x1D770, 0x1D788 }, + { 0x1D78A, 0x1D7A8 }, + { 0x1D7AA, 0x1D7C2 }, + { 0x1D7C4, 0x1D7CB }, + { 0x1EE00, 0x1EE03 }, + { 0x1EE05, 0x1EE1F }, + { 0x1EE21, 0x1EE22 }, + { 0x1EE29, 0x1EE32 }, + { 0x1EE34, 0x1EE37 }, + { 0x1EE4D, 0x1EE4F }, + { 0x1EE51, 0x1EE52 }, + { 0x1EE61, 0x1EE62 }, + { 0x1EE67, 0x1EE6A }, + { 0x1EE6C, 0x1EE72 }, + { 0x1EE74, 0x1EE77 }, + { 0x1EE79, 0x1EE7C }, + { 0x1EE80, 0x1EE89 }, + { 0x1EE8B, 0x1EE9B }, + { 0x1EEA1, 0x1EEA3 }, + { 0x1EEA5, 0x1EEA9 }, + { 0x1EEAB, 0x1EEBB }, + { 0x2F800, 0x2FA1D }, +}; + +static Rune alpha1[] = { + 0x00AA, + 0x00B5, + 0x00BA, + 0x02EC, + 0x02EE, + 0x0386, + 0x038C, + 0x0559, + 0x06D5, + 0x06FF, + 0x0710, + 0x07B1, + 0x07FA, + 0x081A, + 0x0824, + 0x0828, + 0x08A0, + 0x093D, + 0x0950, + 0x09B2, + 0x09BD, + 0x09CE, + 0x0A5E, + 0x0ABD, + 0x0AD0, + 0x0B3D, + 0x0B71, + 0x0B83, + 0x0B9C, + 0x0BD0, + 0x0C3D, + 0x0CBD, + 0x0CDE, + 0x0D3D, + 0x0D4E, + 0x0DBD, + 0x0E84, + 0x0E8A, + 0x0E8D, + 0x0EA5, + 0x0EA7, + 0x0EBD, + 0x0EC6, + 0x0F00, + 0x103F, + 0x1061, + 0x108E, + 0x10C7, + 0x10CD, + 0x1258, + 0x12C0, + 0x17D7, + 0x17DC, + 0x18AA, + 0x1AA7, + 0x1F59, + 0x1F5B, + 0x1F5D, + 0x1FBE, + 0x2071, + 0x207F, + 0x2102, + 0x2107, + 0x2115, + 0x2124, + 0x2126, + 0x2128, + 0x214E, + 0x2D27, + 0x2D2D, + 0x2D6F, + 0x2E2F, + 0x3400, + 0x4DB5, + 0x4E00, + 0x9FCC, + 0xA8FB, + 0xA9CF, + 0xAA7A, + 0xAAB1, + 0xAAC0, + 0xAAC2, + 0xAC00, + 0xD7A3, + 0xFB1D, + 0xFB3E, + 0x10808, + 0x1083C, + 0x10A00, + 0x16F50, + 0x1D4A2, + 0x1D4BB, + 0x1D546, + 0x1EE24, + 0x1EE27, + 0x1EE39, + 0x1EE3B, + 0x1EE42, + 0x1EE47, + 0x1EE49, + 0x1EE4B, + 0x1EE54, + 0x1EE57, + 0x1EE59, + 0x1EE5B, + 0x1EE5D, + 0x1EE5F, + 0x1EE64, + 0x1EE7E, + 0x20000, + 0x2A6D6, + 0x2A700, + 0x2B734, + 0x2B740, + 0x2B81D, +}; + +int +isalpharune(Rune r) +{ + if(bsearch(&r, alpha2, nelem(alpha2), sizeof *alpha2, &rune2cmp)) + return 1; + if(bsearch(&r, alpha1, nelem(alpha1), sizeof *alpha1, &rune1cmp)) + return 1; + return 0; +} + +static Rune space2[][2] = { + { 0x2000, 0x200A }, + { 0x2028, 0x2029 }, +}; + +static Rune space1[] = { + 0x0020, + 0x00A0, + 0x1680, + 0x180E, + 0x202F, + 0x205F, + 0x3000, +}; + +int +isspacerune(Rune r) +{ + if(bsearch(&r, space2, nelem(space2), sizeof *space2, &rune2cmp)) + return 1; + if(bsearch(&r, space1, nelem(space1), sizeof *space1, &rune1cmp)) + return 1; + return 0; +} + +static Rune upper2[][2] = { + { 0x0041, 0x005A }, + { 0x00C0, 0x00D6 }, + { 0x00D8, 0x00DE }, + { 0x0178, 0x0179 }, + { 0x0181, 0x0182 }, + { 0x0186, 0x0187 }, + { 0x0189, 0x018B }, + { 0x018E, 0x0191 }, + { 0x0193, 0x0194 }, + { 0x0196, 0x0198 }, + { 0x019C, 0x019D }, + { 0x019F, 0x01A0 }, + { 0x01A6, 0x01A7 }, + { 0x01AE, 0x01AF }, + { 0x01B1, 0x01B3 }, + { 0x01B7, 0x01B8 }, + { 0x01F6, 0x01F8 }, + { 0x023A, 0x023B }, + { 0x023D, 0x023E }, + { 0x0243, 0x0246 }, + { 0x0388, 0x038A }, + { 0x038E, 0x038F }, + { 0x0391, 0x03A1 }, + { 0x03A3, 0x03AB }, + { 0x03D2, 0x03D4 }, + { 0x03F9, 0x03FA }, + { 0x03FD, 0x042F }, + { 0x04C0, 0x04C1 }, + { 0x0531, 0x0556 }, + { 0x10A0, 0x10C5 }, + { 0x1F08, 0x1F0F }, + { 0x1F18, 0x1F1D }, + { 0x1F28, 0x1F2F }, + { 0x1F38, 0x1F3F }, + { 0x1F48, 0x1F4D }, + { 0x1F68, 0x1F6F }, + { 0x1FB8, 0x1FBB }, + { 0x1FC8, 0x1FCB }, + { 0x1FD8, 0x1FDB }, + { 0x1FE8, 0x1FEC }, + { 0x1FF8, 0x1FFB }, + { 0x210B, 0x210D }, + { 0x2110, 0x2112 }, + { 0x2119, 0x211D }, + { 0x212A, 0x212D }, + { 0x2130, 0x2133 }, + { 0x213E, 0x213F }, + { 0x2C00, 0x2C2E }, + { 0x2C62, 0x2C64 }, + { 0x2C6D, 0x2C70 }, + { 0x2C7E, 0x2C80 }, + { 0xA77D, 0xA77E }, + { 0xFF21, 0xFF3A }, + { 0x10400, 0x10427 }, + { 0x1D400, 0x1D419 }, + { 0x1D434, 0x1D44D }, + { 0x1D468, 0x1D481 }, + { 0x1D49E, 0x1D49F }, + { 0x1D4A5, 0x1D4A6 }, + { 0x1D4A9, 0x1D4AC }, + { 0x1D4AE, 0x1D4B5 }, + { 0x1D4D0, 0x1D4E9 }, + { 0x1D504, 0x1D505 }, + { 0x1D507, 0x1D50A }, + { 0x1D50D, 0x1D514 }, + { 0x1D516, 0x1D51C }, + { 0x1D538, 0x1D539 }, + { 0x1D53B, 0x1D53E }, + { 0x1D540, 0x1D544 }, + { 0x1D54A, 0x1D550 }, + { 0x1D56C, 0x1D585 }, + { 0x1D5A0, 0x1D5B9 }, + { 0x1D5D4, 0x1D5ED }, + { 0x1D608, 0x1D621 }, + { 0x1D63C, 0x1D655 }, + { 0x1D670, 0x1D689 }, + { 0x1D6A8, 0x1D6C0 }, + { 0x1D6E2, 0x1D6FA }, + { 0x1D71C, 0x1D734 }, + { 0x1D756, 0x1D76E }, + { 0x1D790, 0x1D7A8 }, +}; + +static Rune upper1[] = { + 0x0100, + 0x0102, + 0x0104, + 0x0106, + 0x0108, + 0x010A, + 0x010C, + 0x010E, + 0x0110, + 0x0112, + 0x0114, + 0x0116, + 0x0118, + 0x011A, + 0x011C, + 0x011E, + 0x0120, + 0x0122, + 0x0124, + 0x0126, + 0x0128, + 0x012A, + 0x012C, + 0x012E, + 0x0130, + 0x0132, + 0x0134, + 0x0136, + 0x0139, + 0x013B, + 0x013D, + 0x013F, + 0x0141, + 0x0143, + 0x0145, + 0x0147, + 0x014A, + 0x014C, + 0x014E, + 0x0150, + 0x0152, + 0x0154, + 0x0156, + 0x0158, + 0x015A, + 0x015C, + 0x015E, + 0x0160, + 0x0162, + 0x0164, + 0x0166, + 0x0168, + 0x016A, + 0x016C, + 0x016E, + 0x0170, + 0x0172, + 0x0174, + 0x0176, + 0x017B, + 0x017D, + 0x0184, + 0x01A2, + 0x01A4, + 0x01A9, + 0x01AC, + 0x01B5, + 0x01BC, + 0x01C4, + 0x01C7, + 0x01CA, + 0x01CD, + 0x01CF, + 0x01D1, + 0x01D3, + 0x01D5, + 0x01D7, + 0x01D9, + 0x01DB, + 0x01DE, + 0x01E0, + 0x01E2, + 0x01E4, + 0x01E6, + 0x01E8, + 0x01EA, + 0x01EC, + 0x01EE, + 0x01F1, + 0x01F4, + 0x01FA, + 0x01FC, + 0x01FE, + 0x0200, + 0x0202, + 0x0204, + 0x0206, + 0x0208, + 0x020A, + 0x020C, + 0x020E, + 0x0210, + 0x0212, + 0x0214, + 0x0216, + 0x0218, + 0x021A, + 0x021C, + 0x021E, + 0x0220, + 0x0222, + 0x0224, + 0x0226, + 0x0228, + 0x022A, + 0x022C, + 0x022E, + 0x0230, + 0x0232, + 0x0241, + 0x0248, + 0x024A, + 0x024C, + 0x024E, + 0x0370, + 0x0372, + 0x0376, + 0x0386, + 0x038C, + 0x03CF, + 0x03D8, + 0x03DA, + 0x03DC, + 0x03DE, + 0x03E0, + 0x03E2, + 0x03E4, + 0x03E6, + 0x03E8, + 0x03EA, + 0x03EC, + 0x03EE, + 0x03F4, + 0x03F7, + 0x0460, + 0x0462, + 0x0464, + 0x0466, + 0x0468, + 0x046A, + 0x046C, + 0x046E, + 0x0470, + 0x0472, + 0x0474, + 0x0476, + 0x0478, + 0x047A, + 0x047C, + 0x047E, + 0x0480, + 0x048A, + 0x048C, + 0x048E, + 0x0490, + 0x0492, + 0x0494, + 0x0496, + 0x0498, + 0x049A, + 0x049C, + 0x049E, + 0x04A0, + 0x04A2, + 0x04A4, + 0x04A6, + 0x04A8, + 0x04AA, + 0x04AC, + 0x04AE, + 0x04B0, + 0x04B2, + 0x04B4, + 0x04B6, + 0x04B8, + 0x04BA, + 0x04BC, + 0x04BE, + 0x04C3, + 0x04C5, + 0x04C7, + 0x04C9, + 0x04CB, + 0x04CD, + 0x04D0, + 0x04D2, + 0x04D4, + 0x04D6, + 0x04D8, + 0x04DA, + 0x04DC, + 0x04DE, + 0x04E0, + 0x04E2, + 0x04E4, + 0x04E6, + 0x04E8, + 0x04EA, + 0x04EC, + 0x04EE, + 0x04F0, + 0x04F2, + 0x04F4, + 0x04F6, + 0x04F8, + 0x04FA, + 0x04FC, + 0x04FE, + 0x0500, + 0x0502, + 0x0504, + 0x0506, + 0x0508, + 0x050A, + 0x050C, + 0x050E, + 0x0510, + 0x0512, + 0x0514, + 0x0516, + 0x0518, + 0x051A, + 0x051C, + 0x051E, + 0x0520, + 0x0522, + 0x0524, + 0x0526, + 0x10C7, + 0x10CD, + 0x1E00, + 0x1E02, + 0x1E04, + 0x1E06, + 0x1E08, + 0x1E0A, + 0x1E0C, + 0x1E0E, + 0x1E10, + 0x1E12, + 0x1E14, + 0x1E16, + 0x1E18, + 0x1E1A, + 0x1E1C, + 0x1E1E, + 0x1E20, + 0x1E22, + 0x1E24, + 0x1E26, + 0x1E28, + 0x1E2A, + 0x1E2C, + 0x1E2E, + 0x1E30, + 0x1E32, + 0x1E34, + 0x1E36, + 0x1E38, + 0x1E3A, + 0x1E3C, + 0x1E3E, + 0x1E40, + 0x1E42, + 0x1E44, + 0x1E46, + 0x1E48, + 0x1E4A, + 0x1E4C, + 0x1E4E, + 0x1E50, + 0x1E52, + 0x1E54, + 0x1E56, + 0x1E58, + 0x1E5A, + 0x1E5C, + 0x1E5E, + 0x1E60, + 0x1E62, + 0x1E64, + 0x1E66, + 0x1E68, + 0x1E6A, + 0x1E6C, + 0x1E6E, + 0x1E70, + 0x1E72, + 0x1E74, + 0x1E76, + 0x1E78, + 0x1E7A, + 0x1E7C, + 0x1E7E, + 0x1E80, + 0x1E82, + 0x1E84, + 0x1E86, + 0x1E88, + 0x1E8A, + 0x1E8C, + 0x1E8E, + 0x1E90, + 0x1E92, + 0x1E94, + 0x1E9E, + 0x1EA0, + 0x1EA2, + 0x1EA4, + 0x1EA6, + 0x1EA8, + 0x1EAA, + 0x1EAC, + 0x1EAE, + 0x1EB0, + 0x1EB2, + 0x1EB4, + 0x1EB6, + 0x1EB8, + 0x1EBA, + 0x1EBC, + 0x1EBE, + 0x1EC0, + 0x1EC2, + 0x1EC4, + 0x1EC6, + 0x1EC8, + 0x1ECA, + 0x1ECC, + 0x1ECE, + 0x1ED0, + 0x1ED2, + 0x1ED4, + 0x1ED6, + 0x1ED8, + 0x1EDA, + 0x1EDC, + 0x1EDE, + 0x1EE0, + 0x1EE2, + 0x1EE4, + 0x1EE6, + 0x1EE8, + 0x1EEA, + 0x1EEC, + 0x1EEE, + 0x1EF0, + 0x1EF2, + 0x1EF4, + 0x1EF6, + 0x1EF8, + 0x1EFA, + 0x1EFC, + 0x1EFE, + 0x1F59, + 0x1F5B, + 0x1F5D, + 0x1F5F, + 0x2102, + 0x2107, + 0x2115, + 0x2124, + 0x2126, + 0x2128, + 0x2145, + 0x2183, + 0x2C60, + 0x2C67, + 0x2C69, + 0x2C6B, + 0x2C72, + 0x2C75, + 0x2C82, + 0x2C84, + 0x2C86, + 0x2C88, + 0x2C8A, + 0x2C8C, + 0x2C8E, + 0x2C90, + 0x2C92, + 0x2C94, + 0x2C96, + 0x2C98, + 0x2C9A, + 0x2C9C, + 0x2C9E, + 0x2CA0, + 0x2CA2, + 0x2CA4, + 0x2CA6, + 0x2CA8, + 0x2CAA, + 0x2CAC, + 0x2CAE, + 0x2CB0, + 0x2CB2, + 0x2CB4, + 0x2CB6, + 0x2CB8, + 0x2CBA, + 0x2CBC, + 0x2CBE, + 0x2CC0, + 0x2CC2, + 0x2CC4, + 0x2CC6, + 0x2CC8, + 0x2CCA, + 0x2CCC, + 0x2CCE, + 0x2CD0, + 0x2CD2, + 0x2CD4, + 0x2CD6, + 0x2CD8, + 0x2CDA, + 0x2CDC, + 0x2CDE, + 0x2CE0, + 0x2CE2, + 0x2CEB, + 0x2CED, + 0x2CF2, + 0xA640, + 0xA642, + 0xA644, + 0xA646, + 0xA648, + 0xA64A, + 0xA64C, + 0xA64E, + 0xA650, + 0xA652, + 0xA654, + 0xA656, + 0xA658, + 0xA65A, + 0xA65C, + 0xA65E, + 0xA660, + 0xA662, + 0xA664, + 0xA666, + 0xA668, + 0xA66A, + 0xA66C, + 0xA680, + 0xA682, + 0xA684, + 0xA686, + 0xA688, + 0xA68A, + 0xA68C, + 0xA68E, + 0xA690, + 0xA692, + 0xA694, + 0xA696, + 0xA722, + 0xA724, + 0xA726, + 0xA728, + 0xA72A, + 0xA72C, + 0xA72E, + 0xA732, + 0xA734, + 0xA736, + 0xA738, + 0xA73A, + 0xA73C, + 0xA73E, + 0xA740, + 0xA742, + 0xA744, + 0xA746, + 0xA748, + 0xA74A, + 0xA74C, + 0xA74E, + 0xA750, + 0xA752, + 0xA754, + 0xA756, + 0xA758, + 0xA75A, + 0xA75C, + 0xA75E, + 0xA760, + 0xA762, + 0xA764, + 0xA766, + 0xA768, + 0xA76A, + 0xA76C, + 0xA76E, + 0xA779, + 0xA77B, + 0xA780, + 0xA782, + 0xA784, + 0xA786, + 0xA78B, + 0xA78D, + 0xA790, + 0xA792, + 0xA7A0, + 0xA7A2, + 0xA7A4, + 0xA7A6, + 0xA7A8, + 0xA7AA, + 0x1D49C, + 0x1D4A2, + 0x1D546, + 0x1D7CA, +}; + +int +isupperrune(Rune r) +{ + if(bsearch(&r, upper2, nelem(upper2), sizeof *upper2, &rune2cmp)) + return 1; + if(bsearch(&r, upper1, nelem(upper1), sizeof *upper1, &rune1cmp)) + return 1; + return 0; +} + +static Rune lower2[][2] = { + { 0x0061, 0x007A }, + { 0x00DF, 0x00F6 }, + { 0x00F8, 0x00FF }, + { 0x0137, 0x0138 }, + { 0x0148, 0x0149 }, + { 0x017E, 0x0180 }, + { 0x018C, 0x018D }, + { 0x0199, 0x019B }, + { 0x01AA, 0x01AB }, + { 0x01B9, 0x01BA }, + { 0x01BD, 0x01BF }, + { 0x01DC, 0x01DD }, + { 0x01EF, 0x01F0 }, + { 0x0233, 0x0239 }, + { 0x023F, 0x0240 }, + { 0x024F, 0x0293 }, + { 0x0295, 0x02AF }, + { 0x037B, 0x037D }, + { 0x03AC, 0x03CE }, + { 0x03D0, 0x03D1 }, + { 0x03D5, 0x03D7 }, + { 0x03EF, 0x03F3 }, + { 0x03FB, 0x03FC }, + { 0x0430, 0x045F }, + { 0x04CE, 0x04CF }, + { 0x0561, 0x0587 }, + { 0x1D00, 0x1D2B }, + { 0x1D6B, 0x1D77 }, + { 0x1D79, 0x1D9A }, + { 0x1E95, 0x1E9D }, + { 0x1EFF, 0x1F07 }, + { 0x1F10, 0x1F15 }, + { 0x1F20, 0x1F27 }, + { 0x1F30, 0x1F37 }, + { 0x1F40, 0x1F45 }, + { 0x1F50, 0x1F57 }, + { 0x1F60, 0x1F67 }, + { 0x1F70, 0x1F7D }, + { 0x1F80, 0x1F87 }, + { 0x1F90, 0x1F97 }, + { 0x1FA0, 0x1FA7 }, + { 0x1FB0, 0x1FB4 }, + { 0x1FB6, 0x1FB7 }, + { 0x1FC2, 0x1FC4 }, + { 0x1FC6, 0x1FC7 }, + { 0x1FD0, 0x1FD3 }, + { 0x1FD6, 0x1FD7 }, + { 0x1FE0, 0x1FE7 }, + { 0x1FF2, 0x1FF4 }, + { 0x1FF6, 0x1FF7 }, + { 0x210E, 0x210F }, + { 0x213C, 0x213D }, + { 0x2146, 0x2149 }, + { 0x2C30, 0x2C5E }, + { 0x2C65, 0x2C66 }, + { 0x2C73, 0x2C74 }, + { 0x2C76, 0x2C7B }, + { 0x2CE3, 0x2CE4 }, + { 0x2D00, 0x2D25 }, + { 0xA72F, 0xA731 }, + { 0xA771, 0xA778 }, + { 0xFB00, 0xFB06 }, + { 0xFB13, 0xFB17 }, + { 0xFF41, 0xFF5A }, + { 0x10428, 0x1044F }, + { 0x1D41A, 0x1D433 }, + { 0x1D44E, 0x1D454 }, + { 0x1D456, 0x1D467 }, + { 0x1D482, 0x1D49B }, + { 0x1D4B6, 0x1D4B9 }, + { 0x1D4BD, 0x1D4C3 }, + { 0x1D4C5, 0x1D4CF }, + { 0x1D4EA, 0x1D503 }, + { 0x1D51E, 0x1D537 }, + { 0x1D552, 0x1D56B }, + { 0x1D586, 0x1D59F }, + { 0x1D5BA, 0x1D5D3 }, + { 0x1D5EE, 0x1D607 }, + { 0x1D622, 0x1D63B }, + { 0x1D656, 0x1D66F }, + { 0x1D68A, 0x1D6A5 }, + { 0x1D6C2, 0x1D6DA }, + { 0x1D6DC, 0x1D6E1 }, + { 0x1D6FC, 0x1D714 }, + { 0x1D716, 0x1D71B }, + { 0x1D736, 0x1D74E }, + { 0x1D750, 0x1D755 }, + { 0x1D770, 0x1D788 }, + { 0x1D78A, 0x1D78F }, + { 0x1D7AA, 0x1D7C2 }, + { 0x1D7C4, 0x1D7C9 }, +}; + +static Rune lower1[] = { + 0x00B5, + 0x0101, + 0x0103, + 0x0105, + 0x0107, + 0x0109, + 0x010B, + 0x010D, + 0x010F, + 0x0111, + 0x0113, + 0x0115, + 0x0117, + 0x0119, + 0x011B, + 0x011D, + 0x011F, + 0x0121, + 0x0123, + 0x0125, + 0x0127, + 0x0129, + 0x012B, + 0x012D, + 0x012F, + 0x0131, + 0x0133, + 0x0135, + 0x013A, + 0x013C, + 0x013E, + 0x0140, + 0x0142, + 0x0144, + 0x0146, + 0x014B, + 0x014D, + 0x014F, + 0x0151, + 0x0153, + 0x0155, + 0x0157, + 0x0159, + 0x015B, + 0x015D, + 0x015F, + 0x0161, + 0x0163, + 0x0165, + 0x0167, + 0x0169, + 0x016B, + 0x016D, + 0x016F, + 0x0171, + 0x0173, + 0x0175, + 0x0177, + 0x017A, + 0x017C, + 0x0183, + 0x0185, + 0x0188, + 0x0192, + 0x0195, + 0x019E, + 0x01A1, + 0x01A3, + 0x01A5, + 0x01A8, + 0x01AD, + 0x01B0, + 0x01B4, + 0x01B6, + 0x01C6, + 0x01C9, + 0x01CC, + 0x01CE, + 0x01D0, + 0x01D2, + 0x01D4, + 0x01D6, + 0x01D8, + 0x01DA, + 0x01DF, + 0x01E1, + 0x01E3, + 0x01E5, + 0x01E7, + 0x01E9, + 0x01EB, + 0x01ED, + 0x01F3, + 0x01F5, + 0x01F9, + 0x01FB, + 0x01FD, + 0x01FF, + 0x0201, + 0x0203, + 0x0205, + 0x0207, + 0x0209, + 0x020B, + 0x020D, + 0x020F, + 0x0211, + 0x0213, + 0x0215, + 0x0217, + 0x0219, + 0x021B, + 0x021D, + 0x021F, + 0x0221, + 0x0223, + 0x0225, + 0x0227, + 0x0229, + 0x022B, + 0x022D, + 0x022F, + 0x0231, + 0x023C, + 0x0242, + 0x0247, + 0x0249, + 0x024B, + 0x024D, + 0x0371, + 0x0373, + 0x0377, + 0x0390, + 0x03D9, + 0x03DB, + 0x03DD, + 0x03DF, + 0x03E1, + 0x03E3, + 0x03E5, + 0x03E7, + 0x03E9, + 0x03EB, + 0x03ED, + 0x03F5, + 0x03F8, + 0x0461, + 0x0463, + 0x0465, + 0x0467, + 0x0469, + 0x046B, + 0x046D, + 0x046F, + 0x0471, + 0x0473, + 0x0475, + 0x0477, + 0x0479, + 0x047B, + 0x047D, + 0x047F, + 0x0481, + 0x048B, + 0x048D, + 0x048F, + 0x0491, + 0x0493, + 0x0495, + 0x0497, + 0x0499, + 0x049B, + 0x049D, + 0x049F, + 0x04A1, + 0x04A3, + 0x04A5, + 0x04A7, + 0x04A9, + 0x04AB, + 0x04AD, + 0x04AF, + 0x04B1, + 0x04B3, + 0x04B5, + 0x04B7, + 0x04B9, + 0x04BB, + 0x04BD, + 0x04BF, + 0x04C2, + 0x04C4, + 0x04C6, + 0x04C8, + 0x04CA, + 0x04CC, + 0x04D1, + 0x04D3, + 0x04D5, + 0x04D7, + 0x04D9, + 0x04DB, + 0x04DD, + 0x04DF, + 0x04E1, + 0x04E3, + 0x04E5, + 0x04E7, + 0x04E9, + 0x04EB, + 0x04ED, + 0x04EF, + 0x04F1, + 0x04F3, + 0x04F5, + 0x04F7, + 0x04F9, + 0x04FB, + 0x04FD, + 0x04FF, + 0x0501, + 0x0503, + 0x0505, + 0x0507, + 0x0509, + 0x050B, + 0x050D, + 0x050F, + 0x0511, + 0x0513, + 0x0515, + 0x0517, + 0x0519, + 0x051B, + 0x051D, + 0x051F, + 0x0521, + 0x0523, + 0x0525, + 0x0527, + 0x1E01, + 0x1E03, + 0x1E05, + 0x1E07, + 0x1E09, + 0x1E0B, + 0x1E0D, + 0x1E0F, + 0x1E11, + 0x1E13, + 0x1E15, + 0x1E17, + 0x1E19, + 0x1E1B, + 0x1E1D, + 0x1E1F, + 0x1E21, + 0x1E23, + 0x1E25, + 0x1E27, + 0x1E29, + 0x1E2B, + 0x1E2D, + 0x1E2F, + 0x1E31, + 0x1E33, + 0x1E35, + 0x1E37, + 0x1E39, + 0x1E3B, + 0x1E3D, + 0x1E3F, + 0x1E41, + 0x1E43, + 0x1E45, + 0x1E47, + 0x1E49, + 0x1E4B, + 0x1E4D, + 0x1E4F, + 0x1E51, + 0x1E53, + 0x1E55, + 0x1E57, + 0x1E59, + 0x1E5B, + 0x1E5D, + 0x1E5F, + 0x1E61, + 0x1E63, + 0x1E65, + 0x1E67, + 0x1E69, + 0x1E6B, + 0x1E6D, + 0x1E6F, + 0x1E71, + 0x1E73, + 0x1E75, + 0x1E77, + 0x1E79, + 0x1E7B, + 0x1E7D, + 0x1E7F, + 0x1E81, + 0x1E83, + 0x1E85, + 0x1E87, + 0x1E89, + 0x1E8B, + 0x1E8D, + 0x1E8F, + 0x1E91, + 0x1E93, + 0x1E9F, + 0x1EA1, + 0x1EA3, + 0x1EA5, + 0x1EA7, + 0x1EA9, + 0x1EAB, + 0x1EAD, + 0x1EAF, + 0x1EB1, + 0x1EB3, + 0x1EB5, + 0x1EB7, + 0x1EB9, + 0x1EBB, + 0x1EBD, + 0x1EBF, + 0x1EC1, + 0x1EC3, + 0x1EC5, + 0x1EC7, + 0x1EC9, + 0x1ECB, + 0x1ECD, + 0x1ECF, + 0x1ED1, + 0x1ED3, + 0x1ED5, + 0x1ED7, + 0x1ED9, + 0x1EDB, + 0x1EDD, + 0x1EDF, + 0x1EE1, + 0x1EE3, + 0x1EE5, + 0x1EE7, + 0x1EE9, + 0x1EEB, + 0x1EED, + 0x1EEF, + 0x1EF1, + 0x1EF3, + 0x1EF5, + 0x1EF7, + 0x1EF9, + 0x1EFB, + 0x1EFD, + 0x1FBE, + 0x210A, + 0x2113, + 0x212F, + 0x2134, + 0x2139, + 0x214E, + 0x2184, + 0x2C61, + 0x2C68, + 0x2C6A, + 0x2C6C, + 0x2C71, + 0x2C81, + 0x2C83, + 0x2C85, + 0x2C87, + 0x2C89, + 0x2C8B, + 0x2C8D, + 0x2C8F, + 0x2C91, + 0x2C93, + 0x2C95, + 0x2C97, + 0x2C99, + 0x2C9B, + 0x2C9D, + 0x2C9F, + 0x2CA1, + 0x2CA3, + 0x2CA5, + 0x2CA7, + 0x2CA9, + 0x2CAB, + 0x2CAD, + 0x2CAF, + 0x2CB1, + 0x2CB3, + 0x2CB5, + 0x2CB7, + 0x2CB9, + 0x2CBB, + 0x2CBD, + 0x2CBF, + 0x2CC1, + 0x2CC3, + 0x2CC5, + 0x2CC7, + 0x2CC9, + 0x2CCB, + 0x2CCD, + 0x2CCF, + 0x2CD1, + 0x2CD3, + 0x2CD5, + 0x2CD7, + 0x2CD9, + 0x2CDB, + 0x2CDD, + 0x2CDF, + 0x2CE1, + 0x2CEC, + 0x2CEE, + 0x2CF3, + 0x2D27, + 0x2D2D, + 0xA641, + 0xA643, + 0xA645, + 0xA647, + 0xA649, + 0xA64B, + 0xA64D, + 0xA64F, + 0xA651, + 0xA653, + 0xA655, + 0xA657, + 0xA659, + 0xA65B, + 0xA65D, + 0xA65F, + 0xA661, + 0xA663, + 0xA665, + 0xA667, + 0xA669, + 0xA66B, + 0xA66D, + 0xA681, + 0xA683, + 0xA685, + 0xA687, + 0xA689, + 0xA68B, + 0xA68D, + 0xA68F, + 0xA691, + 0xA693, + 0xA695, + 0xA697, + 0xA723, + 0xA725, + 0xA727, + 0xA729, + 0xA72B, + 0xA72D, + 0xA733, + 0xA735, + 0xA737, + 0xA739, + 0xA73B, + 0xA73D, + 0xA73F, + 0xA741, + 0xA743, + 0xA745, + 0xA747, + 0xA749, + 0xA74B, + 0xA74D, + 0xA74F, + 0xA751, + 0xA753, + 0xA755, + 0xA757, + 0xA759, + 0xA75B, + 0xA75D, + 0xA75F, + 0xA761, + 0xA763, + 0xA765, + 0xA767, + 0xA769, + 0xA76B, + 0xA76D, + 0xA76F, + 0xA77A, + 0xA77C, + 0xA77F, + 0xA781, + 0xA783, + 0xA785, + 0xA787, + 0xA78C, + 0xA78E, + 0xA791, + 0xA793, + 0xA7A1, + 0xA7A3, + 0xA7A5, + 0xA7A7, + 0xA7A9, + 0xA7FA, + 0x1D4BB, + 0x1D7CB, +}; + +int +islowerrune(Rune r) +{ + if(bsearch(&r, lower2, nelem(lower2), sizeof *lower2, &rune2cmp)) + return 1; + if(bsearch(&r, lower1, nelem(lower1), sizeof *lower1, &rune1cmp)) + return 1; + return 0; +} + +static Rune title2[][2] = { + { 0x1F88, 0x1F8F }, + { 0x1F98, 0x1F9F }, + { 0x1FA8, 0x1FAF }, +}; + +static Rune title1[] = { + 0x01C5, + 0x01C8, + 0x01CB, + 0x01F2, + 0x1FBC, + 0x1FCC, + 0x1FFC, +}; + +int +istitlerune(Rune r) +{ + if(bsearch(&r, title2, nelem(title2), sizeof *title2, &rune2cmp)) + return 1; + if(bsearch(&r, title1, nelem(title1), sizeof *title1, &rune1cmp)) + return 1; + return 0; +} + +static Rune digit2[][2] = { + { 0x0030, 0x0039 }, + { 0x0660, 0x0669 }, + { 0x06F0, 0x06F9 }, + { 0x07C0, 0x07C9 }, + { 0x0966, 0x096F }, + { 0x09E6, 0x09EF }, + { 0x0A66, 0x0A6F }, + { 0x0AE6, 0x0AEF }, + { 0x0B66, 0x0B6F }, + { 0x0BE6, 0x0BEF }, + { 0x0C66, 0x0C6F }, + { 0x0CE6, 0x0CEF }, + { 0x0D66, 0x0D6F }, + { 0x0E50, 0x0E59 }, + { 0x0ED0, 0x0ED9 }, + { 0x0F20, 0x0F29 }, + { 0x1040, 0x1049 }, + { 0x1090, 0x1099 }, + { 0x17E0, 0x17E9 }, + { 0x1810, 0x1819 }, + { 0x1946, 0x194F }, + { 0x19D0, 0x19D9 }, + { 0x1A80, 0x1A89 }, + { 0x1A90, 0x1A99 }, + { 0x1B50, 0x1B59 }, + { 0x1BB0, 0x1BB9 }, + { 0x1C40, 0x1C49 }, + { 0x1C50, 0x1C59 }, + { 0xA620, 0xA629 }, + { 0xA8D0, 0xA8D9 }, + { 0xA900, 0xA909 }, + { 0xA9D0, 0xA9D9 }, + { 0xAA50, 0xAA59 }, + { 0xABF0, 0xABF9 }, + { 0xFF10, 0xFF19 }, + { 0x104A0, 0x104A9 }, + { 0x11066, 0x1106F }, + { 0x110F0, 0x110F9 }, + { 0x11136, 0x1113F }, + { 0x111D0, 0x111D9 }, + { 0x116C0, 0x116C9 }, + { 0x1D7CE, 0x1D7FF }, +}; + +int +isdigitrune(Rune r) +{ + if(bsearch(&r, digit2, nelem(digit2), sizeof *digit2, &rune2cmp)) + return 1; + return 0; +} + diff --git a/sbase/seq.1 b/sbase/seq.1 @@ -0,0 +1,36 @@ +.TH SEQ 1 sbase\-VERSION +.SH NAME +seq \- print a sequence of numbers +.SH SYNOPSIS +.B seq +.RB [ \-w ] +.RB [ \-f +.IR fmt ] +.RB [ \-s +.IR separator ] +.RI [ start +.RI [ step ]] +.IR end +.SH DESCRIPTION +.B seq +will print a sequence of numbers from +.I start +(default 1) to +.IR end , +in +.IR step +intervals (default 1). +.SH OPTIONS +.TP +.BI \-f " format" +specifies the format used for output lines, as per +.IR printf (3). +.TP +.BI \-s " separator" +specifies the separator to print between output lines +.TP +.BI \-w +tells seq to print out lines in equal width + +.SH SEE ALSO +.IR printf (3) diff --git a/sbase/seq.c b/sbase/seq.c @@ -0,0 +1,153 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static int digitsleft(const char *); +static int digitsright(const char *); +static int validfmt(const char *); + +static void +usage(void) +{ + eprintf("usage: %s [-f fmt] [-s separator] [-w width] [start" + " [step]] end\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + const char *starts = "1", *steps = "1", *ends = "1", *sep = "\n"; + int wflag = 0; + char *tmp, ftmp[BUFSIZ], *fmt = ftmp; + double start, step, end, out, dir; + int left, right; + + ARGBEGIN { + case 'f': + if(!validfmt(tmp=EARGF(usage()))) + eprintf("%s: invalid format\n", tmp); + fmt = tmp; + break; + case 's': + sep = EARGF(usage()); + break; + case 'w': + wflag = 1; + break; + default: + usage(); + } ARGEND; + + switch (argc) { + case 3: + steps = argv[1]; + argv[1] = argv[2]; + /* fallthrough */ + case 2: + starts = argv[0]; + argv++; + /* fallthrough */ + case 1: + ends = argv[0]; + break; + default: + usage(); + } + start = estrtod(starts); + step = estrtod(steps); + end = estrtod(ends); + + dir = (step > 0) ? 1.0 : -1.0; + if (step == 0 || start * dir > end * dir) + return 1; + + if (fmt == ftmp) { + right = MAX(digitsright(starts), + MAX(digitsright(ends), + digitsright(steps))); + + if (wflag) { + left = MAX(digitsleft(starts), digitsleft(ends)); + + snprintf(ftmp, sizeof ftmp, "%%0%d.%df", + right + left + (right != 0), right); + } else + snprintf(ftmp, sizeof ftmp, "%%.%df", right); + } + for (out = start; out * dir <= end * dir; out += step) { + if (out != start) + fputs(sep, stdout); + printf(fmt, out); + } + printf("\n"); + + return 0; +} + +static int +digitsleft(const char *d) +{ + char *exp; + int shift; + + if (*d == '+') + d++; + exp = strpbrk(d, "eE"); + shift = exp ? estrtol(&exp[1], 10) : 0; + + return MAX(0, strspn(d, "-0123456789") + shift); +} + +static int +digitsright(const char *d) +{ + char *exp; + int shift, after; + + exp = strpbrk(d, "eE"); + shift = exp ? estrtol(&exp[1], 10) : 0; + after = (d = strchr(d, '.')) ? strspn(&d[1], "0123456789") : 0; + + return MAX(0, after - shift); +} + +static int +validfmt(const char *fmt) +{ + int occur = 0; + +literal: + while (*fmt) + if (*fmt++ == '%') + goto format; + return occur == 1; + +format: + if (*fmt == '%') { + fmt++; + goto literal; + } + fmt += strspn(fmt, "-+#0 '"); + fmt += strspn(fmt, "0123456789"); + if (*fmt == '.') { + fmt++; + fmt += strspn(fmt, "0123456789"); + } + if (*fmt == 'L') + fmt++; + + switch (*fmt) { + case 'f': case 'F': + case 'g': case 'G': + case 'e': case 'E': + case 'a': case 'A': + occur++; + goto literal; + default: + return 0; + } +} diff --git a/sbase/setsid.1 b/sbase/setsid.1 @@ -0,0 +1,7 @@ +.TH SETSID 1 sbase\-VERSION +.SH NAME +setsid \- run a program in a new session +.SH SYNOPSIS +.B setsid +.RI program +.RI [ arg ...] diff --git a/sbase/setsid.c b/sbase/setsid.c @@ -0,0 +1,45 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s cmd [arg ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int savederrno; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + if (getpgrp() == getpid()) { + switch (fork()) { + case -1: + eprintf("fork:"); + case 0: + break; + default: + return 0; + } + } + if (setsid() < 0) + eprintf("setsid:"); + execvp(argv[0], argv); + savederrno = errno; + weprintf("execvp %s:", argv[0]); + return (savederrno == ENOENT) ? 127 : 126; +} diff --git a/sbase/sha1.h b/sbase/sha1.h @@ -0,0 +1,18 @@ +/* public domain sha1 implementation based on rfc3174 and libtomcrypt */ + +struct sha1 { + uint64_t len; /* processed message length */ + uint32_t h[5]; /* hash state */ + uint8_t buf[64]; /* message block buffer */ +}; + +enum { SHA1_DIGEST_LENGTH = 20 }; + +/* reset state */ +void sha1_init(void *ctx); +/* process message */ +void sha1_update(void *ctx, const void *m, unsigned long len); +/* get message digest */ +/* state is ruined after sum, keep a copy if multiple sum is needed */ +/* part of the message might be left in s, zero it if secrecy is needed */ +void sha1_sum(void *ctx, uint8_t md[SHA1_DIGEST_LENGTH]); diff --git a/sbase/sha1sum.1 b/sbase/sha1sum.1 @@ -0,0 +1,12 @@ +.TH SHA1SUM 1 sbase\-VERSION +.SH NAME +sha1sum \- compute SHA-1 message digest +.SH SYNOPSIS +.B sha1sum +.RB [\-c] +.RI [ file ...] +.TP +.B \-c +read list of SHA1 checksums from file and check them +.SH DESCRIPTION +Print SHA-1 (160-bit) checksums. With no file, read standard input. diff --git a/sbase/sha1sum.c b/sbase/sha1sum.c @@ -0,0 +1,43 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "crypt.h" +#include "sha1.h" +#include "util.h" + +static struct sha1 s; +struct crypt_ops sha1_ops = { + sha1_init, + sha1_update, + sha1_sum, + &s, +}; + +static void +usage(void) +{ + eprintf("usage: %s [-c] [file...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + uint8_t md[SHA1_DIGEST_LENGTH]; + char *checkfile = NULL; + int cflag = 0; + + ARGBEGIN { + case 'c': + cflag = 1; + checkfile = ARGF(); + break; + default: + usage(); + } ARGEND; + + if (cflag) + return cryptcheck(checkfile, argc, argv, &sha1_ops, md, sizeof(md)); + return cryptmain(argc, argv, &sha1_ops, md, sizeof(md)); +} diff --git a/sbase/sha256.h b/sbase/sha256.h @@ -0,0 +1,18 @@ +/* public domain sha256 implementation based on fips180-3 */ + +struct sha256 { + uint64_t len; /* processed message length */ + uint32_t h[8]; /* hash state */ + uint8_t buf[64]; /* message block buffer */ +}; + +enum { SHA256_DIGEST_LENGTH = 32 }; + +/* reset state */ +void sha256_init(void *ctx); +/* process message */ +void sha256_update(void *ctx, const void *m, unsigned long len); +/* get message digest */ +/* state is ruined after sum, keep a copy if multiple sum is needed */ +/* part of the message might be left in s, zero it if secrecy is needed */ +void sha256_sum(void *ctx, uint8_t md[SHA256_DIGEST_LENGTH]); diff --git a/sbase/sha256sum.1 b/sbase/sha256sum.1 @@ -0,0 +1,12 @@ +.TH SHA256SUM 1 sbase\-VERSION +.SH NAME +sha256sum \- compute SHA256 message digest +.SH SYNOPSIS +.B sha256sum +.RB [\-c] +.RI [ file ...] +.TP +.B \-c +read list of SHA256 checksums from file and check them +.SH DESCRIPTION +Print SHA256 (256-bit) checksums. With no file, read standard input. diff --git a/sbase/sha256sum.c b/sbase/sha256sum.c @@ -0,0 +1,43 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "crypt.h" +#include "sha256.h" +#include "util.h" + +static struct sha256 s; +struct crypt_ops sha256_ops = { + sha256_init, + sha256_update, + sha256_sum, + &s, +}; + +static void +usage(void) +{ + eprintf("usage: %s [-c] [file...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + uint8_t md[SHA256_DIGEST_LENGTH]; + char *checkfile = NULL; + int cflag = 0; + + ARGBEGIN { + case 'c': + cflag = 1; + checkfile = ARGF(); + break; + default: + usage(); + } ARGEND; + + if (cflag) + return cryptcheck(checkfile, argc, argv, &sha256_ops, md, sizeof(md)); + return cryptmain(argc, argv, &sha256_ops, md, sizeof(md)); +} diff --git a/sbase/sha512.h b/sbase/sha512.h @@ -0,0 +1,18 @@ +/* public domain sha512 implementation based on fips180-3 */ + +struct sha512 { + uint64_t len; /* processed message length */ + uint64_t h[8]; /* hash state */ + uint8_t buf[128]; /* message block buffer */ +}; + +enum { SHA512_DIGEST_LENGTH = 64 }; + +/* reset state */ +void sha512_init(void *ctx); +/* process message */ +void sha512_update(void *ctx, const void *m, unsigned long len); +/* get message digest */ +/* state is ruined after sum, keep a copy if multiple sum is needed */ +/* part of the message might be left in s, zero it if secrecy is needed */ +void sha512_sum(void *ctx, uint8_t md[SHA512_DIGEST_LENGTH]); diff --git a/sbase/sha512sum.1 b/sbase/sha512sum.1 @@ -0,0 +1,12 @@ +.TH SHA512SUM 1 sbase\-VERSION +.SH NAME +sha512sum \- compute SHA512 message digest +.SH SYNOPSIS +.B sha512sum +.RB [\-c] +.RI [ file ...] +.TP +.B \-c +read list of SHA512 checksums from file and check them +.SH DESCRIPTION +Print SHA512 (512-bit) checksums. With no file, read standard input. diff --git a/sbase/sha512sum.c b/sbase/sha512sum.c @@ -0,0 +1,43 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "crypt.h" +#include "sha512.h" +#include "util.h" + +static struct sha512 s; +struct crypt_ops sha512_ops = { + sha512_init, + sha512_update, + sha512_sum, + &s, +}; + +static void +usage(void) +{ + eprintf("usage: %s [-c] [file...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + uint8_t md[SHA512_DIGEST_LENGTH]; + char *checkfile = NULL; + int cflag = 0; + + ARGBEGIN { + case 'c': + cflag = 1; + checkfile = ARGF(); + break; + default: + usage(); + } ARGEND; + + if (cflag) + return cryptcheck(checkfile, argc, argv, &sha512_ops, md, sizeof(md)); + return cryptmain(argc, argv, &sha512_ops, md, sizeof(md)); +} diff --git a/sbase/sleep.1 b/sbase/sleep.1 @@ -0,0 +1,11 @@ +.TH SLEEP 1 sbase\-VERSION +.SH NAME +sleep \- wait for a number of seconds +.SH SYNOPSIS +.B sleep +.I seconds +.SH DESCRIPTION +.B sleep +waits until the given number of seconds have elapsed. +.SH SEE ALSO +.IR sleep (3) diff --git a/sbase/sleep.c b/sbase/sleep.c @@ -0,0 +1,30 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s seconds\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + unsigned int seconds; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + seconds = estrtol(argv[0], 0); + while ((seconds = sleep(seconds)) > 0) + ; + return 0; +} diff --git a/sbase/sort.1 b/sbase/sort.1 @@ -0,0 +1,67 @@ +.TH SORT 1 sbase\-VERSION +.SH NAME +sort \- sort lines +.SH SYNOPSIS +.B sort +.RB [ \-bnru ] +.RB [ \-t +.IR delim ] +.RB [ \-k +.IR key ]... +.RI [ file ...] +.SH DESCRIPTION +.B sort +writes the sorted concatenation of the given files to stdout. If no file is +given, sort reads from stdin. +.SH OPTIONS +.TP +.B \-C +check that the concatenation of the given files is sorted rather than sorting +them. In this mode, no output is printed to stdout, and the exit status +indicates the result of the check. +.TP +.B \-b +skip leading whitespace of columns when sorting. +.TP +.B \-c +the same as +.B \-C +except that when disorder is detected, a message is printed to stderr +indicating the location of the disorder. +.TP +.BI \-k \ key +specifies a key definition of the form +.BR S [. s ][ f ][, E [. e ][ f ]] +where +.BR S , +.BR s , +.BR E , +and +.B e +are the starting column, starting character in that column, ending column and +the ending character of that column respectively. If they are not specified, +.B s +refers to the first character of the specified starting column, +.B E +refers to the last column of every line, and +.B e +refers to the last character of that last column. +.B f +can be used to specify options +.RB ( n , +.BR b ) +that only apply to this key definition. +.B b +is special in that it only applies to the column that it was specified after. +.TP +.B \-n +perform a numeric sort. +.TP +.B \-r +reverses the sort. +.TP +.BI \-t \ delim +specifies the field delimiter. +.TP +.B \-u +prints equal lines only once. diff --git a/sbase/sort.c b/sbase/sort.c @@ -0,0 +1,307 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "text.h" +#include "util.h" + +struct keydef { + int start_column; + int end_column; + int start_char; + int end_char; + int flags; +}; + +enum { + MOD_N = 1 << 1, + MOD_STARTB = 1 << 2, + MOD_ENDB = 1 << 3, + MOD_R = 1 << 4, +}; + +struct kdlist { + struct keydef keydef; + struct kdlist *next; +}; + +static struct kdlist *head = NULL; +static struct kdlist *tail = NULL; + +static void addkeydef(char *, int); +static void check(FILE *); +static int linecmp(const char **, const char **); +static char *skipblank(char *); +static int parse_flags(char **, int *, int); +static int parse_keydef(struct keydef *, char *, int); +static char *nextcol(char *); +static char *columns(char *, const struct keydef *); + +static int Cflag = 0, cflag = 0, uflag = 0; +static char *fieldsep = NULL; + +static void +usage(void) +{ + enprintf(2, "usage: %s [-Cbcnru] [-t delim] [-k def]... [file...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + long i; + FILE *fp; + struct linebuf linebuf = EMPTY_LINEBUF; + int global_flags = 0; + + ARGBEGIN { + case 'C': + Cflag = 1; + break; + case 'b': + global_flags |= MOD_STARTB | MOD_ENDB; + break; + case 'c': + cflag = 1; + break; + case 'k': + addkeydef(EARGF(usage()), global_flags); + break; + case 'n': + global_flags |= MOD_N; + break; + case 'r': + global_flags |= MOD_R; + break; + case 't': + fieldsep = EARGF(usage()); + if (strlen(fieldsep) != 1) + usage(); + break; + case 'u': + uflag = 1; + break; + default: + usage(); + } ARGEND; + + if (!head && global_flags) + addkeydef("1", global_flags); + addkeydef("1", global_flags & MOD_R); + + if (argc == 0) { + if (Cflag || cflag) { + check(stdin); + } else { + getlines(stdin, &linebuf); + } + } else for (; argc > 0; argc--, argv++) { + if (!(fp = fopen(argv[0], "r"))) { + enprintf(2, "fopen %s:", argv[0]); + continue; + } + if (Cflag || cflag) { + check(fp); + } else { + getlines(fp, &linebuf); + } + fclose(fp); + } + + if (!Cflag && !cflag) { + qsort(linebuf.lines, linebuf.nlines, sizeof *linebuf.lines, + (int (*)(const void *, const void *))linecmp); + + for (i = 0; i < linebuf.nlines; i++) { + if (!uflag || i == 0 || linecmp((const char **)&linebuf.lines[i], + (const char **)&linebuf.lines[i-1])) { + fputs(linebuf.lines[i], stdout); + } + } + } + + return 0; +} + +static void +addkeydef(char *def, int flags) +{ + struct kdlist *node; + + node = malloc(sizeof(*node)); + if (!node) + enprintf(2, "malloc:"); + if (!head) + head = node; + if (parse_keydef(&node->keydef, def, flags)) + enprintf(2, "faulty key definition\n"); + if (tail) + tail->next = node; + node->next = NULL; + tail = node; +} + +static void +check(FILE *fp) +{ + static struct { char *buf; size_t size; } prev, cur, tmp; + + if (!prev.buf) + getline(&prev.buf, &prev.size, fp); + while (getline(&cur.buf, &cur.size, fp) != -1) { + if (uflag > linecmp((const char **) &cur.buf, (const char **) &prev.buf)) { + if (!Cflag) + weprintf("disorder: %s", cur.buf); + exit(1); + } + tmp = cur; + cur = prev; + prev = tmp; + } +} + +static int +linecmp(const char **a, const char **b) +{ + char *s1, *s2; + int res = 0; + struct kdlist *node; + + for (node = head; node && res == 0; node = node->next) { + s1 = columns((char *)*a, &node->keydef); + s2 = columns((char *)*b, &node->keydef); + + /* if -u is given, don't use default key definition + * unless it is the only one */ + if (uflag && node == tail && head != tail) + res = 0; + else if (node->keydef.flags & MOD_N) + res = strtol(s1, 0, 10) - strtol(s2, 0, 10); + else + res = strcmp(s1, s2); + + if (node->keydef.flags & MOD_R) + res = -res; + + free(s1); + free(s2); + } + return res; +} + +static int +parse_flags(char **s, int *flags, int bflag) +{ + while (isalpha((int)**s)) + switch (*((*s)++)) { + case 'b': + *flags |= bflag; + break; + case 'n': + *flags |= MOD_N; + break; + case 'r': + *flags |= MOD_R; + break; + default: + return -1; + } + return 0; +} + +static int +parse_keydef(struct keydef *kd, char *s, int flags) +{ + char *rest = s; + + kd->start_column = 1; + kd->start_char = 1; + /* 0 means end of line */ + kd->end_column = 0; + kd->end_char = 0; + kd->flags = flags; + + kd->start_column = strtol(rest, &rest, 10); + if (kd->start_column < 1) + return -1; + if (*rest == '.') + kd->start_char = strtol(rest+1, &rest, 10); + if (kd->start_char < 1) + return -1; + if (parse_flags(&rest, &kd->flags, MOD_STARTB) < 0) + return -1; + if (*rest == ',') { + kd->end_column = strtol(rest+1, &rest, 10); + if (kd->end_column && kd->end_column < kd->start_column) + return -1; + if (*rest == '.') { + kd->end_char = strtol(rest+1, &rest, 10); + if (kd->end_char < 1) + return -1; + } + if (parse_flags(&rest, &kd->flags, MOD_ENDB) < 0) + return -1; + } + if (*rest != '\0') + return -1; + return 0; +} + +static char * +skipblank(char *s) +{ + while(*s && isblank(*s)) + s++; + return s; +} + +static char * +nextcol(char *s) +{ + if (!fieldsep) { + s = skipblank(s); + while(*s && !isblank(*s)) + s++; + } else { + if (!strchr(s, *fieldsep)) + s = strchr(s, '\0'); + else + s = strchr(s, *fieldsep) + 1; + } + return s; +} + +static char * +columns(char *line, const struct keydef *kd) +{ + char *start, *end; + char *res; + int i; + + for (i = 1, start = line; i < kd->start_column; i++) + start = nextcol(start); + if (kd->flags & MOD_STARTB) + start = skipblank(start); + start += MIN(kd->start_char, nextcol(start) - start) - 1; + + if (kd->end_column) { + for (i = 1, end = line; i < kd->end_column; i++) + end = nextcol(end); + if (kd->flags & MOD_ENDB) + end = skipblank(end); + if (kd->end_char) + end += MIN(kd->end_char, nextcol(end) - end); + else + end = nextcol(end); + } else { + if (!(end = strchr(line, '\n'))) + end = strchr(line, '\0'); + } + + if (!(res = strndup(start, end - start))) + enprintf(2, "strndup:"); + return res; +} diff --git a/sbase/split.1 b/sbase/split.1 @@ -0,0 +1,58 @@ +.TH SPLIT 1 sbase\-VERSION +.SH NAME +split \- split up a file +.SH SYNOPSIS +.B split +.RB [ \-d ] +.RB [ \-a +.IR len ] +.RB [ \-b +.RI [ bytes [k|m|g]]] +.RB [ \-l +.RI [ lines ]] +.RI [ input +.RI [ prefix ]] + +.SH DESCRIPTION +.B split +Reads a file, splitting it into smaller files, every +.IR bytes +bytes +or +.IR lines +lines. If +.B split +runs out of filenames before all the data can be written, it stops at the +last valid filename, leaving all the written data on the disk. + +The +.IR b +and +.IR l +flags are mutually exclusive. Only the last one specified will be obeyed. + +.SH OPTIONS +.TP +.B \-d +Use decimal suffixes rather than alphabetical. + +.TP +.B \-a "len" +Set the suffix length to +.IR len +characters long. + +.TP +.B \-b [bytes[k|m|g]] +Start a new file every +.IR bytes +bytes. The units k, m, and g are case insensitive, and powers of 2, not 10. + +.TP +.B \-l [lines] +Start a new file every +.IR lines +lines. + +.SH SEE ALSO +.IR cat (1) diff --git a/sbase/split.c b/sbase/split.c @@ -0,0 +1,141 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +static int itostr(char *, int, int); +static FILE *nextfile(FILE *, char *, int, int); + +static void +usage(void) +{ + eprintf("usage: split [-d] [-a len] [-b [bytes[k|m|g]]] [-l [lines]] [input [prefix]]\n"); +} + +static int base = 26, start = 'a'; + +int +main(int argc, char *argv[]) +{ + int plen, slen = 2; + int ch; + char name[NAME_MAX+1]; + char *prefix = "x"; + char *file = NULL; + char *tmp, *end; + uint64_t size = 1000, scale = 1, n; + int always = 0; + FILE *in = stdin, *out = NULL; + + ARGBEGIN { + case 'b': + always = 1; + tmp = ARGF(); + if (!tmp) + break; + + size = strtoull(tmp, &end, 10); + if (!*end) + break; + switch (toupper((int)*end)) { + case 'K': + scale = 1024; + break; + case 'M': + scale = 1024L * 1024L; + break; + case 'G': + scale = 1024L * 1024L * 1024L; + break; + default: + usage(); + } + if (size > (UINT64_MAX/scale)) + eprintf("'%s': out of range\n", tmp); + size *= scale; + break; + case 'l': + always = 0; + tmp = ARGF(); + if (tmp) + size = estrtol(tmp, 10); + break; + case 'a': + slen = estrtol(EARGF(usage()), 10); + break; + case 'd': + base = 10; + start = '0'; + break; + default: + usage(); + } ARGEND; + + if (*argv) + file = *argv++; + if (*argv) + prefix = *argv++; + if (*argv) + usage(); + + plen = strlen(prefix); + if (plen+slen > NAME_MAX) + eprintf("names cannot exceed %d bytes\n", NAME_MAX); + strlcpy(name, prefix, sizeof(name)); + + if (file && strcmp(file, "-") != 0) { + in = fopen(file, "r"); + if (!in) + eprintf("'%s':", file); + } + +Nextfile: + while ((out = nextfile(out, name, plen, slen))) { + n = 0; + while ((ch = getc(in)) != EOF) { + putc(ch, out); + n += (always || ch == '\n'); + if (n >= size) + goto Nextfile; + } + fclose(out); + break; + } + return 0; +} + +int +itostr(char *str, int x, int n) +{ + str[n] = '\0'; + while (n-- > 0) { + str[n] = start + (x % base); + x /= base; + } + if (x) + return -1; + return 0; +} + +FILE * +nextfile(FILE *f, char *buf, int plen, int slen) +{ + static int n = 0; + int s; + + if (f) + fclose(f); + s = itostr(buf+plen, n++, slen); + if (s < 0) + return NULL; + + f = fopen(buf, "w"); + if (!f) + eprintf("'%s':", buf); + return f; +} diff --git a/sbase/sponge.1 b/sbase/sponge.1 @@ -0,0 +1,15 @@ +.TH SPONGE 1 sbase\-VERSION +.SH NAME +sponge \- soak up standard input and write to a file +.SH SYNOPSIS +.B sponge +.IR file +.SH DESCRIPTION +.B sponge +reads stdin completely, then writes the saved contents to +.IR file . +This makes it possible to easily create pipes which read from and write to +the same file. + +If the given file is a symbolic link, it writes to the links's destination +instead. diff --git a/sbase/sponge.c b/sbase/sponge.c @@ -0,0 +1,41 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> + +#include "text.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: sponge file\n"); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp, *tmpfp; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc != 1) + usage(); + + if (!(tmpfp = tmpfile())) + eprintf("tmpfile:"); + + concat(stdin, "<stdin>", tmpfp, "<tmpfile>"); + rewind(tmpfp); + + if (!(fp = fopen(argv[0], "w"))) + eprintf("fopen %s:", argv[0]); + concat(tmpfp, "<tmpfile>", fp, argv[0]); + + fclose(fp); + fclose(tmpfp); + + return 0; +} diff --git a/sbase/strings.1 b/sbase/strings.1 @@ -0,0 +1,17 @@ +.Dd November 23, 2014 +.Dt STRINGS 1 sbase\-VERSION +.Os +.Sh NAME +.Nm strings +.Nd print the strings of pritable characters in files +.Sh SYNOPSIS +.Nm strings +.Op Ar file ... +.Sh DESCRIPTION +.Nm +prints the printable character sequences that are at least 6 characters +long. If no files are given then it uses stdin. +.Sh STANDARDS +.Nm +mirrors the semantics of Plan9 +.Xr strings 1 . diff --git a/sbase/strings.c b/sbase/strings.c @@ -0,0 +1,62 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> + +#include "util.h" + +static void dostrings(FILE *fp, const char *fname); + +static void +usage(void) +{ + eprintf("usage: %s [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + int ret = 0; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc == 0) { + dostrings(stdin, "<stdin>"); + } else { + for (; argc > 0; argc--, argv++) { + if (!(fp = fopen(argv[0], "r"))) { + weprintf("fopen %s:", argv[0]); + ret = 1; + continue; + } + dostrings(fp, argv[0]); + fclose(fp); + } + } + return ret; +} + +static void +dostrings(FILE *fp, const char *fname) +{ + unsigned char buf[BUFSIZ]; + int c, i = 0; + off_t offset = 0; + + do { + offset++; + if (isprint(c = getc(fp))) + buf[i++] = c; + if ((!isprint(c) && i >= 6) || i == sizeof(buf) - 1) { + buf[i] = '\0'; + printf("%8ld: %s\n", (long)offset - i - 1, buf); + i = 0; + } + } while (c != EOF); + if (ferror(fp)) + eprintf("%s: read error:", fname); +} diff --git a/sbase/sync.1 b/sbase/sync.1 @@ -0,0 +1,14 @@ +.TH SYNC 1 sbase\-VERSION +.SH NAME +sync \- flush disk cache +.SH SYNOPSIS +.B sync +.SH DESCRIPTION +.B sync +invokes +.IR sync(2) +to flush all unwritten changes to the disk. This is +usually done before shutting down, rebooting or halting. + +.SH SEE ALSO +.IR sync (2) fsync (2) diff --git a/sbase/sync.c b/sbase/sync.c @@ -0,0 +1,22 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: sync\n"); +} + +int +main(int argc, char *argv[]) +{ + if (argc != 1) + usage(); + sync(); + + return 0; +} diff --git a/sbase/tail.1 b/sbase/tail.1 @@ -0,0 +1,20 @@ +.TH TAIL 1 sbase\-VERSION +.SH NAME +tail \- output last part of a file +.SH SYNOPSIS +.B tail +.RB [ \-n +.IR lines ] +.RI [ file ] +.SH DESCRIPTION +.B tail +writes the last 10 lines of the file to stdout. If no file is given, tail reads +from stdin. +.SH OPTIONS +.TP +.BI \-n " lines" +outputs the given number of lines. If +.I lines +begins with '+' it is used as an offset from the beginning of the file. +.SH SEE ALSO +.IR head (1) diff --git a/sbase/tail.c b/sbase/tail.c @@ -0,0 +1,101 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "text.h" +#include "util.h" + +static void dropinit(FILE *, const char *, long); +static void taketail(FILE *, const char *, long); + +static void +usage(void) +{ + eprintf("usage: %s [-n lines] [file]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + long n = 10; + FILE *fp; + void (*tail)(FILE *, const char *, long) = taketail; + char *lines; + int ret = 0; + int newline, many; + + ARGBEGIN { + case 'n': + lines = EARGF(usage()); + n = abs(estrtol(lines, 0)); + if (lines[0] == '+') + tail = dropinit; + break; + ARGNUM: + n = ARGNUMF(0); + break; + default: + usage(); + } ARGEND; + if (argc == 0) { + tail(stdin, "<stdin>", n); + } else { + many = argc > 1; + for (newline = 0; argc > 0; argc--, argv++) { + if (!(fp = fopen(argv[0], "r"))) { + weprintf("fopen %s:", argv[0]); + ret = 1; + continue; + } + if (many) + printf("%s==> %s <==\n", + newline ? "\n" : "", argv[0]); + newline = 1; + tail(fp, argv[0], n); + fclose(fp); + } + } + return ret; +} + +static void +dropinit(FILE *fp, const char *str, long n) +{ + char *buf = NULL; + size_t size = 0; + ssize_t len; + unsigned long i = 0; + + while (i < n && ((len = getline(&buf, &size, fp)) != -1)) + if (len && buf[len - 1] == '\n') + i++; + free(buf); + concat(fp, str, stdout, "<stdout>"); +} + +static void +taketail(FILE *fp, const char *str, long n) +{ + char **ring = NULL; + long i, j; + size_t *size = NULL; + + ring = ecalloc(n, sizeof *ring); + size = ecalloc(n, sizeof *size); + + for (i = j = 0; getline(&ring[i], &size[i], fp) != -1; i = j = (i + 1) % n) + ; + if (ferror(fp)) + eprintf("%s: read error:", str); + + do { + if (ring[j]) { + fputs(ring[j], stdout); + free(ring[j]); + } + } while ((j = (j+1)%n) != i); + free(ring); + free(size); +} diff --git a/sbase/tar.1 b/sbase/tar.1 @@ -0,0 +1,62 @@ +.TH TAR 1 sbase\-VERSION +.SH NAME +tar \- create, list or extract a tape archive +.SH SYNOPSIS +.B tar +.RB [ \-f +.IR tarfile] +.RB [ \-C +.IR dir ] +.RB [ - ] x | t + +.B tar +.RB [ \-f +.IR tarfile] +.RB [ \-C +.IR dir ] +.RB [ - ] c +.I dir + +.B tar +.RB [ \-C +.IR dir ] +.B cf +.I tarfile +.I dir + +.B tar +.RB [ \-C +.IR dir ] +.B x|tf +.I tarfile + +.SH DESCRIPTION +.B tar +is the standard file archiver. Generally the archives +created with it are further compressed. +.SH OPTIONS +.TP +.B x +extract tarball from stdin +.TP +.B t +list all files in tarball from stdin +.TP +.BI c\ path +creates tarball from +.I path +and prints it to stdout +.TP +.BI f\ tarfile +Make +.I tarfile +be the archive, rather than stdin or stdout. +.TP +.BI C\ dir +Change dierctory to +.I dir +before beginning. +.SH SEE ALSO +.IR ar (1) +.IR gzip (1) +.IR bzip2 (1) diff --git a/sbase/tar.c b/sbase/tar.c @@ -0,0 +1,332 @@ +/* See LICENSE file for copyright and license details. */ +#include <grp.h> +#include <limits.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +#include "util.h" + +typedef struct Header Header; +struct Header { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char chksum[8]; + char type; + char link[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char major[8]; + char minor[8]; + char prefix[155]; +}; + +enum { + Blksiz = 512 +}; + +enum Type { + REG = '0', AREG = '\0', HARDLINK = '1', SYMLINK = '2', CHARDEV = '3', + BLOCKDEV = '4', DIRECTORY = '5', FIFO = '6' +}; + +static void putoctal(char *, unsigned, int); +static int archive(const char *); +static int unarchive(char *, int, char[Blksiz]); +static int print(char *, int , char[Blksiz]); +static void c(const char *); +static void xt(int (*)(char*, int, char[Blksiz])); + +static FILE *tarfile; +static ino_t tarinode; +static dev_t tardev; + +static int mflag = 0; + +static void +usage(void) +{ + eprintf("usage: tar [-f tarfile] [-C dir] [-]x[m]|t\n" + " tar [-f tarfile] [-C dir] [-]c dir\n" + " tar [-C dir] cf tarfile dir\n" + " tar [-C dir] x[m]|tf tarfile\n"); +} + +int +main(int argc, char *argv[]) +{ + struct stat st; + char *file = NULL, *dir = ".", *ap; + char mode = '\0'; + + ARGBEGIN { + case 'x': + case 'c': + case 't': + if (mode) + usage(); + mode = ARGC(); + break; + case 'C': + dir = EARGF(usage()); + break; + case 'f': + file = EARGF(usage()); + break; + case 'm': + mflag = 1; + break; + default: + usage(); + } ARGEND; + + if (!mode) { + if (argc < 1) + usage(); + + for (ap = argv[0]; *ap; ap++) { + switch (*ap) { + case 'x': + case 'c': + case 't': + if (mode) + usage(); + mode = *ap; + break; + case 'f': + if (argc < 2) + usage(); + argc--, argv++; + file = argv[0]; + break; + case 'C': + if (argc < 2) + usage(); + argc--, argv++; + dir = argv[0]; + break; + case 'm': + mflag = 1; + break; + default: + usage(); + } + } + argc--, argv++; + } + + if (!mode || argc != (mode == 'c')) + usage(); + + if (file) { + tarfile = fopen(file, (mode == 'c') ? "wb" : "rb"); + if (!tarfile) + eprintf("tar: open '%s':", file); + if (lstat(file, &st) < 0) + eprintf("tar: stat '%s':", file); + tarinode = st.st_ino; + tardev = st.st_dev; + } else { + tarfile = (mode == 'c') ? stdout : stdin; + } + + chdir(dir); + + if (mode == 'c') { + c(argv[0]); + } else { + xt((mode == 'x') ? unarchive : print); + } + + return 0; +} + +static void +putoctal(char *dst, unsigned num, int n) +{ + snprintf(dst, n, "%.*o", n-1, num); +} + +static int +archive(const char* path) +{ + unsigned char b[Blksiz]; + unsigned chksum; + int l, x; + Header *h = (void*)b; + FILE *f = NULL; + struct stat st; + struct passwd *pw; + struct group *gr; + mode_t mode; + + lstat(path, &st); + if (st.st_ino == tarinode && st.st_dev == tardev) { + fprintf(stderr, "ignoring '%s'\n", path); + return 0; + } + pw = getpwuid(st.st_uid); + gr = getgrgid(st.st_gid); + + memset(b, 0, sizeof b); + snprintf(h->name, sizeof h->name, "%s", path); + putoctal(h->mode, (unsigned)st.st_mode&0777, sizeof h->mode); + putoctal(h->uid, (unsigned)st.st_uid, sizeof h->uid); + putoctal(h->gid, (unsigned)st.st_gid, sizeof h->gid); + putoctal(h->size, 0, sizeof h->size); + putoctal(h->mtime, (unsigned)st.st_mtime, sizeof h->mtime); + memcpy(h->magic, "ustar", sizeof h->magic); + memcpy(h->version, "00", sizeof h->version); + snprintf(h->uname, sizeof h->uname, "%s", pw ? pw->pw_name : ""); + snprintf(h->gname, sizeof h->gname, "%s", gr ? gr->gr_name : ""); + + mode = st.st_mode; + if (S_ISREG(mode)) { + h->type = REG; + putoctal(h->size, (unsigned)st.st_size, sizeof h->size); + f = fopen(path, "r"); + } else if (S_ISDIR(mode)) { + h->type = DIRECTORY; + } else if (S_ISLNK(mode)) { + h->type = SYMLINK; + readlink(path, h->link, (sizeof h->link)-1); + } else if (S_ISCHR(mode) || S_ISBLK(mode)) { + h->type = S_ISCHR(mode) ? CHARDEV : BLOCKDEV; +#if defined(major) && defined(minor) + putoctal(h->major, (unsigned)major(st.st_dev), sizeof h->major); + putoctal(h->minor, (unsigned)minor(st.st_dev), sizeof h->minor); +#else + return 0; +#endif + } else if (S_ISFIFO(mode)) { + h->type = FIFO; + } + + memset(h->chksum, ' ', sizeof h->chksum); + for (x = 0, chksum = 0; x < sizeof *h; x++) + chksum += b[x]; + putoctal(h->chksum, chksum, sizeof h->chksum); + + fwrite(b, Blksiz, 1, tarfile); + if (!f) + return 0; + while ((l = fread(b, 1, Blksiz, f)) > 0) { + if (l < Blksiz) + memset(b+l, 0, Blksiz-l); + fwrite(b, Blksiz, 1, tarfile); + } + fclose(f); + return 0; +} + +static int +unarchive(char *fname, int l, char b[Blksiz]) +{ + char lname[101]; + FILE *f = NULL; + unsigned long mode, major, minor, type, mtime; + struct timeval times[2]; + Header *h = (void*)b; + + if (!mflag) + mtime = strtoul(h->mtime, 0, 8); + unlink(fname); + switch (h->type) { + case REG: + case AREG: + mode = strtoul(h->mode, 0, 8); + if (!(f = fopen(fname, "w")) || chmod(fname, mode)) + perror(fname); + break; + case HARDLINK: + case SYMLINK: + snprintf(lname, sizeof lname, "%s", h->link); + if (!((h->type == HARDLINK) ? link : symlink)(lname, fname)) + perror(fname); + break; + case DIRECTORY: + mode = strtoul(h->mode, 0, 8); + if (mkdir(fname, (mode_t)mode)) + perror(fname); + break; + case CHARDEV: + case BLOCKDEV: +#ifdef makedev + mode = strtoul(h->mode, 0, 8); + major = strtoul(h->major, 0, 8); + minor = strtoul(h->mode, 0, 8); + type = (h->type == CHARDEV) ? S_IFCHR : S_IFBLK; + if (mknod(fname, type | mode, makedev(major, minor))) + perror(fname); +#endif + break; + case FIFO: + mode = strtoul(h->mode, 0, 8); + if (mknod(fname, S_IFIFO | mode, 0)) + perror(fname); + break; + default: + fprintf(stderr, "usupported tarfiletype %c\n", h->type); + } + if (getuid() == 0 && chown(fname, strtoul(h->uid, 0, 8), + strtoul(h->gid, 0, 8))) + perror(fname); + + for (; l > 0; l -= Blksiz) { + fread(b, Blksiz, 1, tarfile); + if (f) + fwrite(b, MIN(l, 512), 1, f); + } + if (f) + fclose(f); + + if (!mflag) { + times[0].tv_sec = times[1].tv_sec = mtime; + times[0].tv_usec = times[1].tv_usec = 0; + if (utimes(fname, times)) + perror(fname); + } + return 0; +} + +static int +print(char * fname, int l, char b[Blksiz]) +{ + puts(fname); + for (; l > 0; l -= Blksiz) + fread(b, Blksiz, 1, tarfile); + return 0; +} + +static void +c(const char * path) +{ + archive(path); + recurse(path, c); +} + +static void +xt(int (*fn)(char*, int, char[Blksiz])) +{ + char b[Blksiz], fname[257], *s; + Header *h = (void*)b; + + while (fread(b, Blksiz, 1, tarfile) && h->name[0] != '\0') { + s = fname; + if (h->prefix[0] != '\0') + s += sprintf(s, "%.*s/", (int)sizeof h->prefix, h->prefix); + sprintf(s, "%.*s", (int)sizeof h->name, h->name); + fn(fname, strtol(h->size, 0, 8), b); + } +} diff --git a/sbase/tee.1 b/sbase/tee.1 @@ -0,0 +1,14 @@ +.TH TEE 1 sbase\-VERSION +.SH NAME +tee \- duplicate stdin +.SH SYNOPSIS +.B tee +.RB [ \-a ] +.RI [ file ...] +.SH DESCRIPTION +.B tee +writes from stdin to stdout, making copies in each file. +.SH OPTIONS +.TP +.B \-a +append to each file rather than overwriting. diff --git a/sbase/tee.c b/sbase/tee.c @@ -0,0 +1,49 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-a] [file...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int aflag = 0; + char buf[BUFSIZ]; + int i, nfps; + size_t n; + FILE **fps = NULL; + + ARGBEGIN { + case 'a': + aflag = 1; + break; + default: + usage(); + } ARGEND; + + nfps = argc + 1; + fps = ecalloc(nfps, sizeof *fps); + + for (i = 0; argc > 0; argc--, argv++, i++) + if (!(fps[i] = fopen(*argv, aflag ? "a" : "w"))) + eprintf("fopen %s:", *argv); + fps[i] = stdout; + + while ((n = fread(buf, 1, sizeof buf, stdin)) > 0) { + for (i = 0; i < nfps; i++) { + if (fwrite(buf, 1, n, fps[i]) != n) + eprintf("%s: write error:", buf); + } + } + if (ferror(stdin)) + eprintf("<stdin>: read error:"); + + return 0; +} diff --git a/sbase/test.1 b/sbase/test.1 @@ -0,0 +1,96 @@ +.TH TEST 1 sbase\-VERSION +.SH NAME +test \- check file types and compare values +.SH SYNOPSIS +.B test +.RB EXPRESSION +.SH DESCRIPTION +.B Exit with the status determined by EXPRESSION. +.SH OPTIONS +.TP +.B ! EXPRESSION +invert EXPORESSION +.TP +.B \-b FILE +FILE exists and is block special +.TP +.B \-c FILE +FILE exists and is character special +.TP +.B \-d FILE +FILE exists and is a directory +.TP +.B \-e FILE +FILE exists +.TP +.B \-f FILE +ILE exists and is a regular file +.TP +.B \-g FILE +FILE exists and is set-group-ID +.TP +.B \-k FILE +FILE exists and its sticky bit is set +.TP +.B \-h FILE +FILE exists and is a symbolic link (same as -L) +.TP +.B \-L FILE +FILE exists and is a symbolic link (same as -h) +.TP +.B \-n STRING +the length of STRING is nonzero +.TP +.B \-p FILE +FILE exists and is a named pipe +.TP +.B \-r FILE +FILE exists and read permission is granted +.TP +.B \-S FILE +FILE exists and is a socket +.TP +.B \-s FILE +FILE exists and has a size greater than zero +.TP +.B \-t FD +file descriptor FD is opened on a terminal +.TP +.B \-u FILE +exists and its set-user-ID bit is set +.TP +.B \-w FILE +FILE exists and write permission is granted +.TP +.B \-x FILE +FILE exists and execute (or search) permission is granted +.TP +.B \-z STRING +the length of STRING is zero +.TP +.B s1 = s2 +True if the strings s1 and s2 are identical +.TP +.B s1 != s2 +True if the strings s1 and s2 are not identical +.TP +.B s1 +True if s1 is not the null string +.TP +.B n1 -eq n2 +True if the integers n1 and n2 are equal +.TP +.B n1 -ne n2 +True if the integers n1 and n2 are not equal +.TP +.B n1 -gt n2 +True if the integer n1 is greater than the integer n2 +.TP +.B n1 -ge n2 +True if the integer n1 is great than or equal to the integer n2 +.TP +.B n1 -lt n2 +True if the integer n1 is less than the integer n2 +.TP +.B n1 -le n2 +True if the integer n1 is less than or equal to the integer n2 +\ No newline at end of file diff --git a/sbase/test.c b/sbase/test.c @@ -0,0 +1,169 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "util.h" + +static void +stoi(char *s, int *a) +{ + char *p; + errno = 0; + *a = strtol(s, &p, 0); + if (errno || !*s || *p) + enprintf(2, "bad integer %s\n", s); +} + +static int unary_b(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISBLK (buf.st_mode); } +static int unary_c(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISCHR (buf.st_mode); } +static int unary_d(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISDIR (buf.st_mode); } +static int unary_f(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISREG (buf.st_mode); } +static int unary_g(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISGID & buf.st_mode ; } +static int unary_h(char *s) { struct stat buf; if (lstat(s, &buf)) return 0; return S_ISLNK (buf.st_mode); } +static int unary_p(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISFIFO (buf.st_mode); } +static int unary_S(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISSOCK (buf.st_mode); } +static int unary_s(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return buf.st_size ; } +static int unary_u(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISUID & buf.st_mode ; } + +static int unary_n(char *s) { return strlen(s); } +static int unary_z(char *s) { return !strlen(s); } + +static int unary_e(char *s) { return access(s, F_OK); } +static int unary_r(char *s) { return access(s, R_OK); } +static int unary_w(char *s) { return access(s, W_OK); } +static int unary_x(char *s) { return access(s, X_OK); } + +static int unary_t(char *s) { int fd; stoi(s, &fd); return isatty(fd); } + +static int binary_se(char *s1, char *s2) { return strcmp(s1, s2) == 0; } +static int binary_sn(char *s1, char *s2) { return strcmp(s1, s2) != 0; } + +static int binary_eq(char *s1, char *s2) { int a, b; stoi(s1, &a); stoi(s2, &b); return a == b; } +static int binary_ne(char *s1, char *s2) { int a, b; stoi(s1, &a); stoi(s2, &b); return a != b; } +static int binary_gt(char *s1, char *s2) { int a, b; stoi(s1, &a); stoi(s2, &b); return a > b; } +static int binary_ge(char *s1, char *s2) { int a, b; stoi(s1, &a); stoi(s2, &b); return a >= b; } +static int binary_lt(char *s1, char *s2) { int a, b; stoi(s1, &a); stoi(s2, &b); return a < b; } +static int binary_le(char *s1, char *s2) { int a, b; stoi(s1, &a); stoi(s2, &b); return a <= b; } + +typedef struct { + char *name; + int (*func)(); +} Test; + +static Test unary[] = { + { "-b", unary_b }, + { "-c", unary_c }, + { "-d", unary_d }, + { "-e", unary_e }, + { "-f", unary_f }, + { "-g", unary_g }, + { "-h", unary_h }, + { "-L", unary_h }, + { "-n", unary_n }, + { "-p", unary_p }, + { "-r", unary_r }, + { "-S", unary_S }, + { "-s", unary_s }, + { "-t", unary_t }, + { "-u", unary_u }, + { "-w", unary_w }, + { "-x", unary_x }, + { "-z", unary_z }, + + { NULL, NULL }, +}; + +static Test binary[] = { + { "=" , binary_se }, + { "!=" , binary_sn }, + { "-eq", binary_eq }, + { "-ne", binary_ne }, + { "-gt", binary_gt }, + { "-ge", binary_ge }, + { "-lt", binary_lt }, + { "-le", binary_le }, + + { NULL, NULL }, +}; + +static Test * +find_test(Test *tests, char *name) +{ + Test *t; + + for (t = tests; t->name; ++t) + if (strcmp(t->name, name) == 0) + return t; + return NULL; +} + +static int +noarg(char **argv) +{ + return 0; +} + +static int +onearg(char **argv) +{ + return strlen(argv[0]); +} + +static int +twoarg(char **argv) +{ + Test *t = find_test(unary, *argv); + + if (strcmp(argv[0], "!") == 0) + return !onearg(argv + 1); + + if (t) + return t->func(argv[1]); + + return enprintf(2, "bad unary test %s\n", argv[0]), 0; +} + +static int +threearg(char **argv) +{ + Test *t = find_test(binary, argv[1]); + + if (t) + return t->func(argv[0], argv[2]); + + if (strcmp(argv[0], "!") == 0) + return !twoarg(argv + 1); + + return enprintf(2, "bad binary test %s\n", argv[1]), 0; +} + +static int +fourarg(char **argv) +{ + if (strcmp(argv[0], "!") == 0) + return !threearg(argv + 1); + + return enprintf(2, "too many arguments\n"), 0; +} + +int +main(int argc, char **argv) +{ + int (*narg[])(char**) = { noarg, onearg, twoarg, threearg, fourarg }; + int len = strlen(argv[0]); + + if (len && argv[0][len - 1] == '[') + if (strcmp(argv[--argc], "]") != 0) + enprintf(2, "no matching ]\n"); + + --argc; ++argv; + + if (argc > 4) + enprintf(2, "too many arguments\n"); + + return !narg[argc](argv); +} diff --git a/sbase/text.h b/sbase/text.h @@ -0,0 +1,11 @@ +/* See LICENSE file for copyright and license details. */ + +struct linebuf { + char **lines; + long nlines; + long capacity; +}; +#define EMPTY_LINEBUF {NULL, 0, 0,} +void getlines(FILE *, struct linebuf *); + +void concat(FILE *, const char *, FILE *, const char *); diff --git a/sbase/touch.1 b/sbase/touch.1 @@ -0,0 +1,32 @@ +.Dd January 20, 2014 +.Dt TOUCH 1 sbase\-VERSION +.Sh NAME +.Nm touch +.Nd set file timestamps +.Sh SYNOPSIS +.Nm touch +.Op Fl acm +.Op Fl t Ar stamp +.Ar file ... +.Sh DESCRIPTION +.Nm +sets the access and modification times of files to the current time of day. If the file +doesn't exist, it is created with the default permissions. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl a +Set the access time of the file. +.It Fl c +Do not create the file it it does not exist. The exit +status is not affected. +.It Fl m +Change the modification time of the file. +.It Fl t Ar stamp +Set the timestamp to be used with +.Op Fl am . +The format of the timestamp is simply the number of seconds +since Jan 1, 1970. This specification of time does not conform +to POSIX. +.El +.Sh SEE ALSO +.Xr date 1 diff --git a/sbase/touch.c b/sbase/touch.c @@ -0,0 +1,80 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> +#include <utime.h> + +#include "util.h" + +static int aflag; +static int cflag; +static int mflag; +static time_t t; + +static void +touch(const char *file) +{ + int fd; + struct stat st; + struct utimbuf ut; + int r; + + if ((r = stat(file, &st)) < 0) { + if (errno != ENOENT) + eprintf("stat %s:", file); + if (cflag) + return; + } else if (r == 0) { + ut.actime = aflag ? t : st.st_atime; + ut.modtime = mflag ? t : st.st_mtime; + if (utime(file, &ut) < 0) + eprintf("utime %s:", file); + return; + } + + if ((fd = open(file, O_CREAT | O_EXCL, 0644)) < 0) + eprintf("open %s:", file); + close(fd); + + touch(file); +} + +static void +usage(void) +{ + eprintf("usage: %s [-acm] [-t stamp] file ...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + t = time(NULL); + + ARGBEGIN { + case 'a': + aflag = 1; + break; + case 'c': + cflag = 1; + break; + case 'm': + mflag = 1; + break; + case 't': + t = estrtol(EARGF(usage()), 0); + break; + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + for (; argc > 0; argc--, argv++) + touch(argv[0]); + + return 0; +} diff --git a/sbase/tr.1 b/sbase/tr.1 @@ -0,0 +1,76 @@ +.Dd January 16, 2015 +.Dt TR 1 sbase\-VERSION +.Sh NAME +.Nm tr +.Nd translate characters +.Sh SYNOPSIS +.Nm tr +.Op Fl c | Fl C +.Op Fl sd +.Ar set1 set2 +.Sh DESCRIPTION +.Nm +matches characters from stdin and performs translations to stdout. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c | Fl C +Match to +.Ar set1 +complement. +.It Fl d +Delete characters matching +.Ar set1 . +.It Fl s +Squeeze repeated characters matching +.Ar set1 +or +.Ar set2 +if -d is set. +.El +.Sh SET +.Bl -tag -width Ds +.It Literal 'c' +.It Escape sequence '\ec' +\e\e, \ea, \eb, \ef, \en, \er, \et, \ev +.It Range 'c-d' +.It Repeat '[c*n]' +Only in +.Ar set2 . +If n = 0 or left out, set n to length of +.Ar set1 . +.It Character class '[:class:]' +See +.Xr wctype 3 . +.El +.Sh TRANSLATION +If no options are specified, +.Nm +translates from +.Ar set1 +to +.Ar set2 +by index or character class. +.Pp +If +.Ar set2 +is shorter than +.Ar set1 , +overflowing characters translate to the last character in +.Ar set2 . +.Sh EXIT STATUS +.Bl -tag -width Ds +.It 0 +Input processed successfully. +.It 1 +An error occurred. +.El +.Sh SEE ALSO +.Xr sed 1 , +.Xr awk 1 , +.Xr utf8 7 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +except from octal sequences and equivalence classes. diff --git a/sbase/tr.c b/sbase/tr.c @@ -0,0 +1,289 @@ +/* See LICENSE file for copyright and license details. */ +#include <wctype.h> +#include <stdio.h> +#include <stdlib.h> + +#include "utf.h" +#include "util.h" + +static int cflag = 0; +static int dflag = 0; +static int sflag = 0; + +struct range { + Rune start; + Rune end; + size_t quant; +}; + +static struct { + char *name; + int (*check)(wint_t); +} classes[] = { + { "alnum", iswalnum }, + { "alpha", iswalpha }, + { "blank", iswblank }, + { "cntrl", iswcntrl }, + { "digit", iswdigit }, + { "graph", iswgraph }, + { "lower", iswlower }, + { "print", iswprint }, + { "punct", iswpunct }, + { "space", iswspace }, + { "upper", iswupper }, + { "xdigit", iswxdigit }, +}; + +static struct range *set1 = NULL; +static size_t set1ranges = 0; +static int (*set1check)(wint_t) = NULL; +static struct range *set2 = NULL; +static size_t set2ranges = 0; +static int (*set2check)(wint_t) = NULL; + + +static size_t +rangelen(struct range r) +{ + return (r.end - r.start + 1) * r.quant; +} + +static size_t +setlen(struct range *set, size_t setranges) +{ + size_t len = 0, i; + + for (i = 0; i < setranges; i++) + len += rangelen(set[i]); + + return len; +} + +static int +rstrmatch(Rune *r, char *s, size_t n) +{ + size_t i; + + for (i = 0; i < n; i++) + if (r[i] != s[i]) + return 0; + return 1; +} + +static size_t +resolveescapes(Rune *r, size_t len) +{ + size_t i, off, m; + + for (i = 0; i < len - 1; i++) { + if (r[i] != '\\') + continue; + off = 0; + + switch (r[i + 1]) { + case '\\': r[i] = '\\'; off++; break; + case 'a': r[i] = '\a'; off++; break; + case 'b': r[i] = '\b'; off++; break; + case 'f': r[i] = '\f'; off++; break; + case 'n': r[i] = '\n'; off++; break; + case 'r': r[i] = '\r'; off++; break; + case 't': r[i] = '\t'; off++; break; + case 'v': r[i] = '\v'; off++; break; + default: continue; + } + + for (m = i + 1; m <= len - off; m++) + r[m] = r[m + off]; + len -= off; + } + + return len; +} + +static size_t +makeset(char *str, struct range **set, int (**check)(wint_t)) +{ + Rune *rstr; + size_t len, i, j, m, n; + size_t q, setranges = 0; + int factor, base; + + /* rstr defines at most len ranges */ + len = chartorunearr(str, &rstr); + len = resolveescapes(rstr, len); + *set = emalloc(len * sizeof(**set)); + + for (i = 0; i < len; i++) { + if (rstr[i] == '[') { + j = i; +nextbrack: + if (j == len) + goto literal; + for (m = j; m < len; m++) + if (rstr[m] == ']') { + j = m; + break; + } + if (j == i) + goto literal; + + /* CLASSES [=EQUIV=] (skip) */ + if (j - i > 3 && rstr[i + 1] == '=' && rstr[m - 1] == '=') { + i = j; + continue; + } + + /* CLASSES [:CLASS:] */ + if (j - i > 3 && rstr[i + 1] == ':' && rstr[m - 1] == ':') { + for (n = 0; n < LEN(classes); n++) { + if (rstrmatch(rstr + i + 2, classes[n].name, j - i - 3)) { + *check = classes[n].check; + return 0; + } + } + eprintf("Invalid character class.\n"); + } + + /* REPEAT [_*n] (only allowed in set2) */ + if (j - i > 2 && rstr[i + 2] == '*' && set1ranges > 0) { + /* check if right side of '*' is a number */ + q = 0; + factor = 1; + base = (rstr[i + 3] == '0') ? 8 : 10; + for (n = j - 1; n > i + 2; n--) { + if (rstr[n] < '0' && rstr[n] > '9') { + n = 0; + break; + } + q += (rstr[n] - '0') * factor; + factor *= base; + } + + if (n == 0) { + j = m + 1; + goto nextbrack; + } + (*set)[setranges].start = rstr[i + 1]; + (*set)[setranges].end = rstr[i + 1]; + (*set)[setranges].quant = q ? q : setlen(set1, set1ranges); + setranges++; + i = j; + continue; + } + + j = m + 1; + goto nextbrack; + } +literal: + /* RANGES [_-__-_], _-__-_ */ + /* LITERALS _______ */ + (*set)[setranges].start = rstr[i]; + + if (i < len - 2 && rstr[i + 1] == '-' && rstr[i + 2] >= rstr[i]) + i += 2; + (*set)[setranges].end = rstr[i]; + (*set)[setranges].quant = 1; + setranges++; + } + + free(rstr); + return setranges; +} + +static void +usage(void) +{ + eprintf("usage: %s [-cCds] set1 [set2]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + Rune r = 0, lastrune = 0; + size_t off1, off2, i, m; + + ARGBEGIN { + case 'c': + case 'C': + cflag = 1; + break; + case 'd': + dflag = 1; + break; + case 's': + sflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc < 1 || argc > 2 || (argc == 1 && dflag == sflag)) + usage(); + set1ranges = makeset(argv[0], &set1, &set1check); + if (argc == 2) + set2ranges = makeset(argv[1], &set2, &set2check); + if (dflag == sflag && !set2ranges && !set2check) + eprintf("set2 must be non-empty.\n"); + if (set2check && set2check != iswlower && set2check != iswupper) + eprintf("set2 can only be the 'lower' or 'upper' class.\n"); +read: + if (!readrune("<stdin>", stdin, &r)) + return 0; + off1 = off2 = 0; + for (i = 0; i < set1ranges; i++) { + if (set1[i].start <= r && r <= set1[i].end) { + if (dflag && !cflag) + goto read; + if (sflag) { + if (r == lastrune) + goto read; + else + goto write; + } + for (m = 0; m < i; m++) + off1 += rangelen(set1[m]); + off1 += r - set1[m].start; + if (off1 > setlen(set2, set2ranges) - 1) { + r = set2[set2ranges - 1].end; + goto write; + } + for (m = 0; m < set2ranges; m++) { + if (off2 + rangelen(set2[m]) > off1) { + m++; + break; + } + off2 += rangelen(set2[m]); + } + m--; + r = set2[m].start + (off1 - off2) / set2[m].quant; + + goto write; + } + } + if (set1check && set1check((wint_t)r)) { + if (dflag && !cflag) + goto read; + if (sflag) { + if (r == lastrune) + goto read; + else + goto write; + } + if (set1check == iswupper && set2check == iswlower) + r = towlower((wint_t)r); + else if (set1check == iswlower && set2check == iswupper) + r = towupper((wint_t)r); + else if (set2ranges > 0) + r = set2[set2ranges - 1].end; + else + eprintf("Misaligned character classes.\n"); + } + if (dflag && cflag) + goto read; + if (dflag && sflag && r == lastrune) + goto read; +write: + lastrune = r; + writerune("<stdout>", stdout, &r); + goto read; +} diff --git a/sbase/true.1 b/sbase/true.1 @@ -0,0 +1,16 @@ +.Dd January 16, 2015 +.Dt TRUE 1 sbase\-VERSION +.Sh NAME +.Nm true +.Nd return success +.Sh SYNOPSIS +.Nm true +.Sh DESCRIPTION +.Nm +returns a status code indicating success +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. diff --git a/sbase/true.c b/sbase/true.c @@ -0,0 +1,6 @@ +/* See LICENSE file for copyright and license details. */ +int +main(void) +{ + return 0; +} diff --git a/sbase/tty.1 b/sbase/tty.1 @@ -0,0 +1,13 @@ +.TH TTY 1 sbase\-VERSION +.SH NAME +tty \- print terminal name +.SH SYNOPSIS +.B tty +.SH DESCRIPTION +.B tty +prints the name of the terminal open on stdin. +.P +The status code is 0 if stdin is a terminal, and 1 if not. If an error occurred +the status code is 2. +.SH SEE ALSO +.IR ttyname (3) diff --git a/sbase/tty.c b/sbase/tty.c @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *tty; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + tty = ttyname(STDIN_FILENO); + puts(tty ? tty : "not a tty"); + return tty ? 0 : 1; +} diff --git a/sbase/uname.1 b/sbase/uname.1 @@ -0,0 +1,32 @@ +.TH UNAME 1 sbase\-VERSION +.SH NAME +uname \- print system information +.SH SYNOPSIS +.B uname +.RB [ \-amnrsv ] +.SH DESCRIPTION +.B uname +prints system information. If no flags are given, uname will print only the +name of the operating system +.RB ( \-s ). +.SH OPTIONS +.TP +.B \-a +print all the information below. +.TP +.B \-m +print the machine's architecture. +.TP +.B \-n +print the system's network name. +.TP +.B \-r +print the operating system's release name. +.TP +.B \-s +print the name of the operating system. +.TP +.B \-v +print the operating system's version name. +.SH SEE ALSO +.IR uname (2) diff --git a/sbase/uname.c b/sbase/uname.c @@ -0,0 +1,63 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <sys/utsname.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-amnrsv]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int mflag = 0; + int nflag = 0; + int rflag = 0; + int sflag = 0; + int vflag = 0; + struct utsname u; + + ARGBEGIN { + case 'a': + mflag = nflag = rflag = sflag = vflag = 1; + break; + case 'm': + mflag = 1; + break; + case 'n': + nflag = 1; + break; + case 'r': + rflag = 1; + break; + case 's': + sflag = 1; + break; + case 'v': + vflag = 1; + break; + default: + usage(); + } ARGEND; + if (uname(&u) < 0) + eprintf("uname:"); + + if (sflag || !(nflag || rflag || vflag || mflag)) + putword(u.sysname); + if (nflag) + putword(u.nodename); + if (rflag) + putword(u.release); + if (vflag) + putword(u.version); + if (mflag) + putword(u.machine); + putchar('\n'); + + return 0; +} diff --git a/sbase/unexpand.1 b/sbase/unexpand.1 @@ -0,0 +1,25 @@ +.TH EXPAND 1 sbase\-VERSION +.SH NAME +unexpand \- convert blanks to tabs +.SH SYNOPSIS +.B unexpand +.RB [ \-a ] +.RB [ \-t +.IR n ] +.RI [ file ...] +.SH DESCRIPTION +unexpand processes the named files or the standard input, writing the +standard output with consecutive blanks (spaces and tabs) converted +into tabs. Backspace characters are preserved into the output and +decrement the column count for tab calculations. +.SH OPTIONS +.TP +.BI \-a +convert blanks to tabs everywhere, not just at the start of lines +.TP +.BI \-t " n" +set tab size to +.I n +spaces (default: 8) +.SH SEE ALSO +.IR expand (1) diff --git a/sbase/unexpand.c b/sbase/unexpand.c @@ -0,0 +1,119 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <wchar.h> + +#include "utf.h" +#include "util.h" + +static void unexpand(const char *, FILE *); + +static int aflag = 0; +static int tabsize = 8; + +static void +usage(void) +{ + eprintf("usage: %s [-a] [-t n] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + int ret = 0; + + ARGBEGIN { + case 't': + tabsize = estrtol(EARGF(usage()), 0); + if (tabsize <= 0) + eprintf("unexpand: invalid tabsize\n"); + /* Fallthrough: -t implies -a */ + case 'a': + aflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc == 0) { + unexpand("<stdin>", stdin); + } else { + for (; argc > 0; argc--, argv++) { + if (!(fp = fopen(argv[0], "r"))) { + weprintf("fopen %s:", argv[0]); + ret = 1; + continue; + } + unexpand(argv[0], fp); + fclose(fp); + } + } + return ret; +} + +static void +unexpandspan(unsigned int n, unsigned int col) +{ + unsigned int off = (col-n) % tabsize; + Rune r; + + if (n + off >= tabsize && n > 1) + n += off; + + r = '\t'; + for (; n >= tabsize; n -= tabsize) + writerune("<stdout>", stdout, &r); + r = ' '; + while (n--) + writerune("<stdout>", stdout, &r); +} + +static void +unexpand(const char *file, FILE *fp) +{ + unsigned int n = 0, col = 0; + Rune r; + int bol = 1; + + while (1) { + if (!readrune(file, fp, &r)) + break; + + switch (r) { + case ' ': + if (bol || aflag) + n++; + col++; + break; + case '\t': + if (bol || aflag) + n += tabsize - col % tabsize; + col += tabsize - col % tabsize; + break; + case '\b': + if (bol || aflag) + unexpandspan(n, col); + col -= (col > 0); + n = 0; + bol = 0; + break; + case '\n': + if (bol || aflag) + unexpandspan(n, col); + n = col = 0; + bol = 1; + break; + default: + if (bol || aflag) + unexpandspan(n, col); + n = 0; + col++; + bol = 0; + } + if ((r != ' ' && r != '\t') || (!aflag && !bol)) + writerune("<stdout>", stdout, &r); + } + if (n > 0 && (bol || aflag)) + unexpandspan(n, col); +} diff --git a/sbase/uniq.1 b/sbase/uniq.1 @@ -0,0 +1,39 @@ +.TH UNIQ 1 sbase\-VERSION +.SH NAME +uniq \- multi-column +.SH SYNOPSIS +.B uniq +.RB [ \-cdu ] +.RI [ file ] +.SH DESCRIPTION +.B uniq +reads file and writes one copy of a line +from each group of consecutive duplicate lines +to stdout. +If no file is given, uniq reads from stdin. +.SH OPTIONS +.TP +.B \-c +prefixes each line with a count +of its consecutive occurrences in the input. +.TP +.B \-d +suppresses non-duplicate lines +(thus 'uniq -d' prints only duplicates). +.TP +.B \-u +suppresses non-unique lines +(thus 'uniq -u' prints only uniques). +.SH BUGS +The original sbase implementation of +.B uniq +supported multiple input-file arguments, +as e.g. cat and grep do. +Unfortunately, POSIX uniq treats its second argument (if present) +as an output filename and clobbers it. +Since users and scripts which rely on uniq +supporting multiple input-file arguments +would be at risk of data loss +if they ever ran into a POSIX-compatible uniq, +support for multiple input-file arguments +was removed from this implementation. diff --git a/sbase/uniq.c b/sbase/uniq.c @@ -0,0 +1,103 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "text.h" +#include "util.h" + +static void uniqline(char *); +static void uniq(FILE *, const char *); +static void uniqfinish(void); + +static const char *countfmt = ""; +static int dflag = 0; +static int uflag = 0; + +static char *prevline = NULL; +static long prevlinecount = 0; + +static void +usage(void) +{ + eprintf("usage: %s [-cdiu] [input]]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + + ARGBEGIN { + case 'i': + eprintf("not implemented\n"); + case 'c': + countfmt = "%7ld "; + break; + case 'd': + dflag = 1; + break; + case 'u': + uflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc == 0) { + uniq(stdin, "<stdin>"); + } else if (argc == 1) { + if (!(fp = fopen(argv[0], "r"))) + eprintf("fopen %s:", argv[0]); + uniq(fp, argv[0]); + fclose(fp); + } else + usage(); + uniqfinish(); + + return 0; +} + +static void +uniqline(char *l) +{ + int linesequel = (!l || !prevline) + ? l == prevline + : !strcmp(l, prevline); + + if (linesequel) { + ++prevlinecount; + return; + } + + if (prevline) { + if ((prevlinecount == 1 && !dflag) || + (prevlinecount != 1 && !uflag)) { + printf(countfmt, prevlinecount); + fputs(prevline, stdout); + } + free(prevline); + prevline = NULL; + } + + if (l) + prevline = estrdup(l); + prevlinecount = 1; +} + +static void +uniq(FILE *fp, const char *str) +{ + char *buf = NULL; + size_t size = 0; + + while (getline(&buf, &size, fp) != -1) + uniqline(buf); +} + +static void +uniqfinish(void) +{ + uniqline(NULL); +} diff --git a/sbase/unlink.1 b/sbase/unlink.1 @@ -0,0 +1,16 @@ +.TH UNLINK 1 sbase\-VERSION +.SH NAME +unlink \- call the unlink function +.SH SYNOPSIS +.B unlink +.RB file +.SH DESCRIPTION +.B unlink +calls the +.IR unlink +function on +.IR file. + + +.SH SEE ALSO +.IR unlink (2) diff --git a/sbase/unlink.c b/sbase/unlink.c @@ -0,0 +1,23 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: unlink file\n"); +} + +int +main(int argc, char *argv[]) +{ + if (argc != 2) + usage(); + + if (unlink(argv[1]) < 0) + eprintf("unlink: '%s':", argv[1]); + + return 0; +} diff --git a/sbase/utf.h b/sbase/utf.h @@ -0,0 +1,54 @@ +/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include <stdio.h> + +typedef int Rune; + +enum { + UTFmax = 6, /* maximum bytes per rune */ + Runeself = 0x80, /* rune and utf are equal (<) */ + Runeerror = 0xFFFD, /* decoding error in utf */ + Runemax = 0x10FFFF /* maximum rune value */ +}; + +int runetochar(char *, const Rune *); +int chartorune(Rune *, const char *); +int charntorune(Rune *, const char *, size_t); +int runelen(const Rune); +size_t runenlen(const Rune *, size_t); +int fullrune(const char *, size_t); +char *utfecpy(char *, char *, const char *); +size_t utflen(const char *); +size_t utfnlen(const char *, size_t); +char *utfrune(const char *, Rune); +char *utfrrune(const char *, Rune); +char *utfutf(const char *, const char *); + +int isalpharune(Rune); +int islowerrune(Rune); +int isspacerune(Rune); +int istitlerune(Rune); +int isupperrune(Rune); +int isdigitrune(Rune); + +int readrune(const char *, FILE *, Rune *); +void writerune(const char *, FILE *, Rune *); +int chartorunearr(const char*, Rune **); diff --git a/sbase/util.h b/sbase/util.h @@ -0,0 +1,55 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/types.h> + +#include <regex.h> +#include <stddef.h> + +#include "arg.h" +#include "compat.h" + +#define UTF8_POINT(c) (((c) & 0xc0) != 0x80) + +#undef MIN +#define MIN(x,y) ((x) < (y) ? (x) : (y)) +#undef MAX +#define MAX(x,y) ((x) > (y) ? (x) : (y)) + +#define LEN(x) (sizeof (x) / sizeof *(x)) + +extern char *argv0; + +char *agetcwd(void); +void apathmax(char **, long *); + +void *ecalloc(size_t, size_t); +void *emalloc(size_t size); +void *erealloc(void *, size_t); +char *estrdup(const char *); + +void enprintf(int, const char *, ...); +void eprintf(const char *, ...); +void weprintf(const char *, ...); + +double estrtod(const char *); +long estrtol(const char *, int); + +#undef strcasestr +char *strcasestr(const char *, const char *); + +#undef strlcat +size_t strlcat(char *, const char *, size_t); +#undef strlcpy +size_t strlcpy(char *, const char *, size_t); + +/* regex */ +int enregcomp(int, regex_t *, const char *, int); +int eregcomp(regex_t *, const char *, int); + +/* misc */ +void enmasse(int, char **, int (*)(const char *, const char *)); +void fnck(const char *, const char *, int (*)(const char *, const char *)); +mode_t getumask(void); +char *humansize(double); +mode_t parsemode(const char *, mode_t, mode_t); +void putword(const char *); +void recurse(const char *, void (*)(const char *)); diff --git a/sbase/uudecode.1 b/sbase/uudecode.1 @@ -0,0 +1,19 @@ +.TH UUDECODE 1 sbase\-VERSION +.SH NAME +uudecode \- decode a uuencoded file +.SH SYNOPSIS +.B uudecode +.RI [file] +.SH DESCRIPTION +.B uudecode +reads file (or by default, the standard input) and writes a decoded +version to the file specified in the uuencoded header. In case that +the file already exists, it is truncated. Otherwise a new file is +created. After the operation the permissions of the created/accessed +are changed to reflect the mode in the header. +.SH NOTES +This version of uudecode does not currently support the base64 +encoding algorithm. +For safety currently uudecode operates only on regular files and +stdout. Trying to uudecode to a link, directory, or special file +yields an error. diff --git a/sbase/uudecode.c b/sbase/uudecode.c @@ -0,0 +1,179 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "text.h" +#include "util.h" + +static void uudecode(FILE *, FILE *); +static void parseheader(FILE *, const char *, const char *, mode_t *, char **); +static FILE *parsefile(const char *); + +static void +usage(void) +{ + eprintf("usage: %s [file]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp = NULL, *nfp = NULL; + char *fname; + mode_t mode = 0; + + ARGBEGIN { + case 'm': + eprintf("-m not implemented\n"); + default: + usage(); + } ARGEND; + + if (argc > 1) + usage(); + + if (argc == 0) { + parseheader(stdin, "<stdin>", "begin ", &mode, &fname); + if (!(nfp = parsefile(fname))) + eprintf("fopen %s:", fname); + uudecode(stdin, nfp); + } else { + if (!(fp = fopen(argv[0], "r"))) + eprintf("fopen %s:", argv[0]); + parseheader(fp, argv[0], "begin ", &mode, &fname); + if (!(nfp = parsefile(fname))) + eprintf("fopen %s:", fname); + uudecode(fp, nfp); + } + + if (chmod(fname, mode) < 0) + eprintf("chmod %s:", fname); + if (fp) + fclose(fp); + if (nfp) + fclose(nfp); + + return 0; +} + +static FILE * +parsefile(const char *fname) +{ + struct stat st; + int ret; + + if (strcmp(fname, "/dev/stdout") == 0) + return stdout; + ret = lstat(fname, &st); + /* if it is a new file, try to open it */ + if (ret < 0 && errno == ENOENT) + goto tropen; + if (ret < 0) { + weprintf("lstat %s:", fname); + return NULL; + } + if (!S_ISREG(st.st_mode)) { + weprintf("for safety uudecode operates only on regular files and /dev/stdout\n"); + return NULL; + } +tropen: + return fopen(fname,"w"); +} + +static void +parseheader(FILE *fp, const char *s, const char *header, mode_t *mode, char **fname) +{ + char bufs[PATH_MAX + 11]; /* len header + mode + maxname */ + char *p, *q; + size_t n; + + if (!fgets(bufs, sizeof(bufs), fp)) + if (ferror(fp)) + eprintf("%s: read error:", s); + if (bufs[0] == '\0' || feof(fp)) + eprintf("empty or nil header string\n"); + if (!(p = strchr(bufs, '\n'))) + eprintf("header string too long or non-newline terminated file\n"); + p = bufs; + if (strncmp(bufs, header, strlen(header)) != 0) + eprintf("malformed header prefix\n"); + p += strlen(header); + if (!(q = strchr(p, ' '))) + eprintf("malformed mode string in header\n"); + *q++ = '\0'; + /* now mode should be null terminated, q points to fname */ + *mode = parsemode(p, *mode, 0); + n = strlen(q); + while (n > 0 && (q[n - 1] == '\n' || q[n - 1] == '\r')) + q[--n] = '\0'; + if (n > 0) + *fname = q; +} + +static void +uudecode(FILE *fp, FILE *outfp) +{ + char *bufb = NULL, *p; + size_t n = 0; + ssize_t len; + int ch, i; + +#define DEC(c) (((c) - ' ') & 077) /* single character decode */ +#define IS_DEC(c) ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) ) +#define OUT_OF_RANGE(c) eprintf("character %c out of range: [%d-%d]", (c), 1 + ' ', 077 + ' ' + 1) + + while ((len = getline(&bufb, &n, fp)) != -1) { + p = bufb; + /* trim newlines */ + if (len && bufb[len - 1] != '\n') + bufb[len - 1] = '\0'; + else + eprintf("no newline found, aborting\n"); + /* check for last line */ + if ((i = DEC(*p)) <= 0) + break; + for (++p; i > 0; p += 4, i -= 3) { + if (i >= 3) { + if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) && + IS_DEC(*(p + 2)) && IS_DEC(*(p + 3)))) + OUT_OF_RANGE(*p); + + ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; + putc(ch, outfp); + ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; + putc(ch, outfp); + ch = DEC(p[2]) << 6 | DEC(p[3]); + putc(ch, outfp); + } else { + if (i >= 1) { + if (!(IS_DEC(*p) && IS_DEC(*(p + 1)))) + OUT_OF_RANGE(*p); + + ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; + putc(ch, outfp); + } + if (i >= 2) { + if (!(IS_DEC(*(p + 1)) && + IS_DEC(*(p + 2)))) + OUT_OF_RANGE(*p); + + ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; + putc(ch, outfp); + } + } + } + if (ferror(fp)) + eprintf("read error:"); + } + /* check for end or fail */ + len = getline(&bufb, &n, fp); + if (len < 3 || strncmp(bufb, "end", 3) != 0 || bufb[3] != '\n') + eprintf("invalid uudecode footer \"end\" not found\n"); + free(bufb); +} diff --git a/sbase/uuencode.1 b/sbase/uuencode.1 @@ -0,0 +1,16 @@ +.TH UUENCODE 1 sbase\-VERSION +.SH NAME +uuencode \- encode a binary file +.SH SYNOPSIS +.B uuencode +.RI [file] +.RB name +.SH DESCRIPTION +.B uuencode +reads file (or by default, the standard input) and writes an encoded +version to the standard output. The encoding uses only printing ASCII +characters and includes the mode of the file and the operand name +for use by uudecode. +.SH NOTES +This version of uuencode does not currently support the base64 +encoding algorithm. diff --git a/sbase/uuencode.c b/sbase/uuencode.c @@ -0,0 +1,78 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "util.h" + +static void uuencode(FILE *, const char *, const char *); + +static void +usage(void) +{ + eprintf("usage: %s [file] name\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + + ARGBEGIN { + case 'm': + eprintf("-m not implemented\n"); + default: + usage(); + } ARGEND; + + if (argc == 0 || argc > 2) + usage(); + + if (argc == 1) { + uuencode(stdin, argv[0], "<stdin>"); + } else { + if (!(fp = fopen(argv[0], "r"))) + eprintf("fopen %s:", argv[0]); + uuencode(fp, argv[1], argv[0]); + fclose(fp); + } + return 0; +} + +static void +uuencode(FILE *fp, const char *name, const char *s) +{ + struct stat st; + unsigned char buf[45], *p; + ssize_t n; + int ch; + + if (fstat(fileno(fp), &st) < 0) + eprintf("fstat %s:", s); + printf("begin %o %s\n", st.st_mode & 0777, name); + while ((n = fread(buf, 1, sizeof(buf), fp))) { + ch = ' ' + (n & 0x3f); + putchar(ch == ' ' ? '`' : ch); + for (p = buf; n > 0; n -= 3, p += 3) { + if (n < 3) { + p[2] = '\0'; + if (n < 2) + p[1] = '\0'; + } + ch = ' ' + ((p[0] >> 2) & 0x3f); + putchar(ch == ' ' ? '`' : ch); + ch = ' ' + (((p[0] << 4) | ((p[1] >> 4) & 0xf)) & 0x3f); + putchar(ch == ' ' ? '`' : ch); + ch = ' ' + (((p[1] << 2) | ((p[2] >> 6) & 0x3)) & 0x3f); + putchar(ch == ' ' ? '`' : ch); + ch = ' ' + (p[2] & 0x3f); + putchar(ch == ' ' ? '`' : ch); + } + putchar('\n'); + } + if (ferror(fp)) + eprintf("'%s' read error:", s); + printf("%c\nend\n", '`'); +} diff --git a/sbase/wc.1 b/sbase/wc.1 @@ -0,0 +1,25 @@ +.TH WC 1 sbase\-VERSION +.SH NAME +wc \- word count +.SH SYNOPSIS +.B wc +.RB [ \-clmw ] +.RI [ file ...] +.SH DESCRIPTION +.B wc +prints the number of lines, words, and bytes in each file. If any flags are +given, wc will print only the requested information. If no files are given, wc +reads stdin. +.SH OPTIONS +.TP +.B \-c +print the number of bytes. +.TP +.B \-l +print the number of lines. +.TP +.B \-m +print the number of characters, not bytes. +.TP +.B \-w +print the number of words. diff --git a/sbase/wc.c b/sbase/wc.c @@ -0,0 +1,104 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void output(const char *, long, long, long); +static void wc(FILE *, const char *); + +static int lflag = 0; +static int wflag = 0; +static char cmode = 0; +static long tc = 0, tl = 0, tw = 0; + +static void +usage(void) +{ + eprintf("usage: %s [-clmw] [files...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + int i; + + ARGBEGIN { + case 'c': + cmode = 'c'; + break; + case 'm': + cmode = 'm'; + break; + case 'l': + lflag = 1; + break; + case 'w': + wflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc == 0) { + wc(stdin, NULL); + } else { + for (i = 0; i < argc; i++) { + if (!(fp = fopen(argv[i], "r"))) { + weprintf("fopen %s:", argv[i]); + continue; + } + wc(fp, argv[i]); + fclose(fp); + } + if (argc > 1) + output("total", tc, tl, tw); + } + return 0; +} + +void +output(const char *str, long nc, long nl, long nw) +{ + int noflags = !cmode && !lflag && !wflag; + + if (lflag || noflags) + printf(" %5ld", nl); + if (wflag || noflags) + printf(" %5ld", nw); + if (cmode || noflags) + printf(" %5ld", nc); + if (str) + printf(" %s", str); + putchar('\n'); +} + +void +wc(FILE *fp, const char *str) +{ + int word = 0; + int c; + long nc = 0, nl = 0, nw = 0; + + while ((c = getc(fp)) != EOF) { + if (cmode != 'm' || UTF8_POINT(c)) + nc++; + if (c == '\n') + nl++; + if (!isspace(c)) + word = 1; + else if (word) { + word = 0; + nw++; + } + } + if (word) + nw++; + tc += nc; + tl += nl; + tw += nw; + output(str, nc, nl, nw); +} diff --git a/sbase/xargs.1 b/sbase/xargs.1 @@ -0,0 +1,50 @@ +.TH XARGS 1 sbase\-VERSION +.SH NAME +xargs \- constuct argument list(s) and execute command +.SH SYNOPSIS +.B xargs +.RB [\-n +.IR maxargs ] +.RB [ \-r ] +.RB [ \-E +.IR eofstr ] +.RI [ cmd +.IR [arg... ] ] +.SH DESCRIPTION +xargs reads space, tab, newline and EOF delimited strings from stdin +and executes the specified cmd with the strings as arguments. + +Any arguments specified on the command line are given to the command upon +each invocation, followed by some number of the arguments read from +stdin. The command is repeatedly executed one or more times until stdin +is exhausted. + +Spaces, tabs and newlines may be embedded in arguments using single (`'') +or double (`"') quotes or backslashes ('\\'). 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. +.SH OPTIONS +.TP +.B \-n maxargs +Use at most maxargs arguments per command line. +.TP +.BI \-r +Do not run the command if there are no arguments. Normally the command is +executed at least once even if there are no arguments. +.TP +.B \-E eofstr +Use eofstr as a logical EOF marker. +.SH EXIT STATUS +xargs exits with one of the following values: + + 0 All invocations of command returned a zero exit + status. + 123 One or more invocations of command returned a + nonzero exit status. + 124 The command exited with a 255 exit status. + 125 The command was killed or stopped by a signal. + 126 The command was found but could not be executed. + 127 The command could not be found. + 1 Some other error occurred. diff --git a/sbase/xargs.c b/sbase/xargs.c @@ -0,0 +1,260 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "text.h" +#include "util.h" + +enum { + NARGS = 10000 +}; + +static int inputc(void); +static void deinputc(int); +static void fillargbuf(int); +static int eatspace(void); +static int parsequote(int); +static int parseescape(void); +static char *poparg(void); +static void waitchld(void); +static void spawn(void); + +static char *cmd[NARGS]; +static char *argb; +static size_t argbsz; +static size_t argbpos; +static long maxargs = 0; +static int nerrors = 0; +static char *eofstr; +static int rflag = 0, nflag = 0; + +static void +usage(void) +{ + eprintf("usage: %s [-n maxargs] [-r] [-E eofstr] [cmd [arg...]]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int leftover = 0; + long argsz, argmaxsz; + char *arg = ""; + int i, a; + + ARGBEGIN { + case 'n': + nflag = 1; + if ((maxargs = strtol(EARGF(usage()), NULL, 10)) <= 0) + eprintf("%s: value for -n option should be >= 1\n", argv0); + break; + case 'r': + rflag = 1; + break; + case 'E': + eofstr = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + + argmaxsz = sysconf(_SC_ARG_MAX); + if (argmaxsz < 0) + eprintf("sysconf:"); + /* Leave some room for environment variables */ + argmaxsz -= 4 * 1024; + + do { + argsz = 0; i = 0; a = 0; + if (argc > 0) { + for (; i < argc; i++) { + cmd[i] = estrdup(argv[i]); + argsz += strlen(cmd[i]) + 1; + } + } else { + cmd[i] = estrdup("/bin/echo"); + argsz += strlen(cmd[i]) + 1; + i++; + } + while (leftover == 1 || (arg = poparg())) { + if (argsz + strlen(arg) + 1 > argmaxsz || + i >= NARGS - 1) { + if (strlen(arg) + 1 > argmaxsz) + eprintf("insufficient argument space\n"); + leftover = 1; + break; + } + cmd[i] = estrdup(arg); + argsz += strlen(cmd[i]) + 1; + i++; + a++; + leftover = 0; + if (nflag == 1 && a >= maxargs) + break; + } + cmd[i] = NULL; + if (a >= maxargs && nflag == 1) + spawn(); + else if (!a || (i == 1 && rflag == 1)) + ; + else + spawn(); + for (; i >= 0; i--) + free(cmd[i]); + } while (arg); + + free(argb); + + return nerrors > 0 ? 123 : 0; +} + +static int +inputc(void) +{ + int ch; + + ch = getc(stdin); + if (ch == EOF && ferror(stdin)) + eprintf("stdin: read error:"); + return ch; +} + +static void +deinputc(int ch) +{ + ungetc(ch, stdin); +} + +static void +fillargbuf(int ch) +{ + if (argbpos >= argbsz) { + argbsz = argbpos == 0 ? 1 : argbsz * 2; + argb = erealloc(argb, argbsz); + } + argb[argbpos] = ch; +} + +static int +eatspace(void) +{ + int ch; + + while ((ch = inputc()) != EOF) { + switch (ch) { + case ' ': case '\t': case '\n': + break; + default: + deinputc(ch); + return ch; + } + } + return -1; +} + +static int +parsequote(int q) +{ + int ch; + + while ((ch = inputc()) != EOF) { + if (ch == q) + return 0; + if (ch != '\n') { + fillargbuf(ch); + argbpos++; + } + } + return -1; +} + +static int +parseescape(void) +{ + int ch; + + if ((ch = inputc()) != EOF) { + fillargbuf(ch); + argbpos++; + return ch; + } + return -1; +} + +static char * +poparg(void) +{ + int ch; + + argbpos = 0; + if (eatspace() < 0) + return NULL; + while ((ch = inputc()) != EOF) { + switch (ch) { + case ' ': case '\t': case '\n': + goto out; + case '\'': + if (parsequote('\'') < 0) + eprintf("unterminated single quote\n"); + break; + case '\"': + if (parsequote('\"') < 0) + eprintf("unterminated double quote\n"); + break; + case '\\': + if (parseescape() < 0) + eprintf("backslash at EOF\n"); + break; + default: + fillargbuf(ch); + argbpos++; + break; + } + } +out: + fillargbuf('\0'); + if (eofstr && strcmp(argb, eofstr) == 0) + return NULL; + return argb; +} + +static void +waitchld(void) +{ + int status; + + wait(&status); + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) == 255) + exit(124); + if (WEXITSTATUS(status) == 127 || + WEXITSTATUS(status) == 126) + exit(WEXITSTATUS(status)); + if (status != 0) + nerrors++; + } + if (WIFSIGNALED(status)) + exit(125); +} + +static void +spawn(void) +{ + pid_t pid; + int savederrno; + + pid = fork(); + if (pid < 0) + eprintf("fork:"); + if (pid == 0) { + execvp(*cmd, cmd); + savederrno = errno; + weprintf("execvp %s:", *cmd); + _exit(savederrno == ENOENT ? 127 : 126); + } + waitchld(); +} diff --git a/sbase/yes.1 b/sbase/yes.1 @@ -0,0 +1,10 @@ +.TH YES 1 sbase\-VERSION +.SH NAME +yes \- output a string repeatedly +.SH SYNOPSIS +.B yes +.RB [ string ... ] +.SH DESCRIPTION +.B yes +will repeatedly output 'y' or the strings specified. + diff --git a/sbase/yes.c b/sbase/yes.c @@ -0,0 +1,24 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [string]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + default: + usage(); + } ARGEND; + + for (;;) + puts(argc >= 1 ? argv[0] : "y"); + return 1; /* should not reach */ +} diff --git a/sdhcp/LICENSE b/sdhcp/LICENSE @@ -0,0 +1,22 @@ +MIT/X Consortium License + +© 2012 David Galos (galosd83 (at) students.rowan.edu) +© 2014 Hiltjo Posthuma <hiltjo at codemadness dot org> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/sdhcp/Makefile b/sdhcp/Makefile @@ -0,0 +1,65 @@ +include config.mk + +.POSIX: +.SUFFIXES: .c .o + +HDR = util.h arg.h +LIB = \ + util/strlcpy.o \ + util/eprintf.o + +SRC = sdhcp.c + +OBJ = $(SRC:.c=.o) $(LIB) +BIN = $(SRC:.c=) +MAN = $(SRC:.c=.1) + +all: options binlib + +options: + @echo sdhcp build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +binlib: util.a + $(MAKE) bin + +bin: $(BIN) + +$(OBJ): $(HDR) config.mk + +.o: + @echo LD $@ + @$(LD) -o $@ $< util.a $(LDFLAGS) + +.c.o: + @echo CC $< + @$(CC) -c -o $@ $< $(CFLAGS) + +util.a: $(LIB) + @echo AR $@ + @$(AR) -r -c $@ $(LIB) + @ranlib $@ + +install: all + @echo installing executables to $(DESTDIR)$(PREFIX)/sbin + @mkdir -p $(DESTDIR)$(PREFIX)/sbin + @cp -f $(BIN) $(DESTDIR)$(PREFIX)/sbin + @cd $(DESTDIR)$(PREFIX)/sbin && chmod 755 $(BIN) + @echo installing manual pages to $(DESTDIR)$(MANPREFIX)/man1 + @mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + @for m in $(MAN); do sed "s/VERSION/$(VERSION)/g" < "$$m" > $(DESTDIR)$(MANPREFIX)/man1/"$$m"; done + @cd $(DESTDIR)$(MANPREFIX)/man1 && chmod 644 $(MAN) + +uninstall: + @echo removing executables from $(DESTDIR)$(PREFIX)/sbin + @cd $(DESTDIR)$(PREFIX)/sbin && rm -f $(BIN) + @echo removing manual pages from $(DESTDIR)$(MANPREFIX)/man1 + @cd $(DESTDIR)$(MANPREFIX)/man1 && rm -f $(MAN) + +clean: + @echo cleaning + @rm -f $(BIN) $(OBJ) util.a + +.PHONY: all options clean install uninstall diff --git a/sdhcp/TODO b/sdhcp/TODO @@ -0,0 +1,41 @@ +TODO: + [ ] manual check memcpy bounds. + [ ] add flag (-s?) to probe a specific DHCP server, not broadcast? + probably skip in run() Init: etc stages. + [ ] sane default value for client-id and test it. + [ ] add new options to man page (-d, -i). + [ ] update LICENSE. + [ ] replace unsigned char ip[4] and so on from function declarations. + [?] ipv6 support ? + [ ] allow sdhcp to run in the foreground (-f?) + +Changed (for now): + - cleanup + - code style. + - trailing whitespace and use tabs. + - remove debug (dbgprintf()) code in sdhcp.c. + - code compiles more cleanly (ansi and c99), + -D_BSD_SOURCE added and explicitly added missing headers (time.h and unistd.h). + - moved man page from sdhcp.8 to sdhcp.1 + - typos: + - sdhcp.c: interface typo. + - sdhcp.1: shdcp typo. + - make exit([01]), EXIT_SUCCESS or EXIT_FAILURE. + - replace write() for stdout messages with fprintf() + - replace die() with eprintf(). + - makefile: + - man page install should respect $DESTDIR. + - make sure on install /sbin and mandir exists. + - add config.mk, and follow suckless Makefile style. + - add arg.h + - first parameter remains interface. + - second parameter is optional client-id, used to be hardcoded to + "vaio". + - add -d flag, don't update /etc/resolv.conf. + - add -i flag, don't set ip. + - add -e flag, run program, this has the following variables set: + $SERVER, DHCP ip. + $DNS, DNS ip. + $ROUTER, router ip. + $MASK, network mask. + $CLIENT, client ip. diff --git a/sdhcp/arg.h b/sdhcp/arg.h @@ -0,0 +1,63 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][1]\ + && argv[0][0] == '-';\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +/* Handles obsolete -NUM syntax */ +#define ARGNUM case '0':\ + case '1':\ + case '2':\ + case '3':\ + case '4':\ + case '5':\ + case '6':\ + case '7':\ + case '8':\ + case '9' + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define ARGNUMF(base) (brk_ = 1, estrtol(argv[0], (base))) + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/sdhcp/config.mk b/sdhcp/config.mk @@ -0,0 +1,13 @@ +# sdhcp version +VERSION = 0.1 + +PREFIX = /usr/local +DESTDIR = +MANPREFIX = $(PREFIX)/share/man + +#CC = gcc +#CC = musl-gcc +LD = $(CC) +CPPFLAGS = -D_BSD_SOURCE +CFLAGS = -g -Wall -Wextra -O0 -ansi $(CPPFLAGS) +LDFLAGS = -g diff --git a/sdhcp/debug.c b/sdhcp/debug.c @@ -0,0 +1,148 @@ +#include <stdarg.h> + +void bpdump(unsigned char *p, int n); + +unsigned short +nhgets(unsigned char c[2]) +{ + return ((c[0] << 8) + c[1]) & 0xffff; +} + +unsigned long +nhgetl(unsigned char c[4]) +{ + return (nhgets(c) << 16) + nhgets(c + 2); +} + +char * +ipstr(unsigned char *ip) +{ + char * ch = malloc(3 * 4 + 3 + 10); + sprintf(ch, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + return ch; +} + +#if 0 +void +dbgprintf(char *str, ...) +{ + va_list ap; + va_start(ap, str); + vfprintf(stderr, str, ap); + va_end(ap); +} +#endif + +void +bpdump(unsigned char *p, int n) +{ + int len, i, code; + Bootp *bp; + unsigned char type; + char *types[] = { + "discover", "offer", "request", + "decline", "ack", "nak", "release", "inform" + }; + /* Udphdr *up; */ + + bp = (Bootp*)p; + /* up = (Udphdr*)bp->udphdr; */ + + if(n < bp->magic - p) { + fprintf(stderr, "dhcpclient: short bootp packet"); + return; + } + + optget(bp, &type, ODtype, sizeof type); + fprintf(stderr, "DHCP%s\n", types[type - 1]); + /* fprintf(stderr, "laddr=%I lport=%d raddr=%I rport=%d\n", up->laddr, + nhgets(up->lport), up->raddr, nhgets(up->rport)); */ + fprintf(stderr, "op = %d htype = %d hlen = %d hops = %d\n", *bp->op, *bp->htype, + *bp->hlen, *bp->hops); + fprintf(stderr, "xid = %x secs = %d flags = %x\n", nhgetl(bp->xid), + nhgets(bp->secs), nhgets(bp->flags)); + fprintf(stderr, "ciaddr = %s, yiaddr = %s, siaddr = %s, giaddr = %s\n", + ipstr(bp->ciaddr), ipstr(bp->yiaddr), ipstr(bp->siaddr), ipstr(bp->giaddr)); + fprintf(stderr, "chaddr ="); + for(i=0; i<15; i++) + fprintf(stderr, "%.2x:", bp->chaddr[i]); + fprintf(stderr, "%.2x\n", bp->chaddr[15]); + fprintf(stderr, "sname = %s\n", bp->sname); + fprintf(stderr, "file = %s\n", bp->file); + + n -= bp->magic - p; + p = bp->magic; + + if(n < 4) + return; + if(memcmp(magic, p, 4) != 0) + fprintf(stderr, "dhcpclient: bad opt magic %#x %#x %#x %#x\n", + p[0], p[1], p[2], p[3]); + p += 4; + n -= 4; + + while(n > 0) { + code = *p++; + n--; + if(code == OBpad) + continue; + if(code == OBend) + break; + if(n == 0) { + fprintf(stderr, " bad option: %d", code); + return; + } + len = *p++; + n--; + if(len > n) { + fprintf(stderr, " bad option: %d", code); + return; + } + switch(code) { + case ODtype: + fprintf(stderr, "DHCP type %d\n", p[0]); + break; + case ODclientid: + fprintf(stderr, "client id="); + for(i = 0; i<len; i++) + fprintf(stderr, "%x ", p[i]); + fprintf(stderr, "\n"); + break; + case ODlease: + fprintf(stderr, "lease=%d sec\n", nhgetl(p)); + break; + case ODserverid: + fprintf(stderr, "server id=%s\n", ipstr(p)); + break; + case OBmask: + fprintf(stderr, "mask=%s\n", ipstr(p)); + break; + case OBrouter: + fprintf(stderr, "router=%s\n", ipstr(p)); + break; + case ODipaddr: + fprintf(stderr, "ip addr=%s\n", ipstr(p)); + break; + case OBdnsserver: + fprintf(stderr, "dns=%s\n", ipstr(p)); + break; + case OBbaddr: + fprintf(stderr, "broadcast=%s\n", ipstr(p)); + break; + case ODrenewaltime: + fprintf(stderr, "renew time=%d sec\n", nhgetl(p)); + break; + case ODrebindingtime: + fprintf(stderr, "rebind time=%d sec\n", nhgetl(p)); + break; + default: + fprintf(stderr, "unknown option %d\n", code); + for(i = 0; i<len; i++) + fprintf(stderr, "%x ", p[i]); + fprintf(stderr, "\n"); + break; + } + p += len; + n -= len; + } +} diff --git a/sdhcp/sdhcp.1 b/sdhcp/sdhcp.1 @@ -0,0 +1,49 @@ +.TH SDHCP-VERSION 1 +.SH NAME +sdhcp \- a simple dhcp client +.SH SYNOPSIS +.B sdhcp +.RB [ \-d ] +.RB [ \-i ] +.RB "[ \-e" +.IR "program" +.RB "]" +.RB "[ " +.IR interface +.RB "]" +.RB "[" +.IR "client\-id" +.RB "]" +.SH DESCRIPTION +sdhcp is a simple, tiny dhcp client. It runs until it enters the "Bound" +state, then forks to the background and runs as a daemon to keep +the lease alive. +.SH OPTIONS +.TP +.B \-d +don't change DNS in /etc/resolv.conf. +.TP +.B \-i +don't change interface information such as an IP address. +.TP +.B "\-e program" +run program. Variables will be set, see VARIABLES. +.SH VARIABLES +.LP +The following variables are set: +.LP +$SERVER DHCP IP. +.LP +$DNS DNS IP. +.LP +$ROUTER router IP. +.LP +$MASK network mask. +.LP +$CLIENT your client IP. +.SH BUGS +I'm sure there's plenty. It only currently supports a small subset of +DHCP options, and has been untested on larger networks. It ignores most of +the DHCP options it understands. Send bug reports to me! +.SH AUTHOR +see LICENSE file diff --git a/sdhcp/sdhcp.c b/sdhcp/sdhcp.c @@ -0,0 +1,495 @@ +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <netinet/in.h> +#include <net/if.h> +#include <net/route.h> +#include <signal.h> +#include <poll.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "util.h" +#include "arg.h" + +typedef struct bootp { + unsigned char op [1]; + unsigned char htype [1]; + unsigned char hlen [1]; + unsigned char hops [1]; + unsigned char xid [4]; + unsigned char secs [2]; + unsigned char flags [2]; + unsigned char ciaddr [4]; + unsigned char yiaddr [4]; + unsigned char siaddr [4]; + unsigned char giaddr [4]; + unsigned char chaddr [16]; + unsigned char sname [64]; + unsigned char file [128]; + unsigned char magic [4]; + unsigned char optdata [312-4]; +} Bootp; + +enum { + DHCPdiscover = 1, + DHCPoffer, + DHCPrequest, + DHCPdecline, + DHCPack, + DHCPnak, + DHCPrelease, + DHCPinform, + Timeout = 200, + + Bootrequest = 1, + Bootreply = 2, + /* bootp flags */ + Fbroadcast = 1 << 15, + + OBpad = 0, + OBmask = 1, + OBrouter = 3, + OBnameserver = 5, + OBdnsserver = 6, + OBbaddr = 28, + ODipaddr = 50, /* 0x32 */ + ODlease = 51, + ODoverload = 52, + ODtype = 53, /* 0x35 */ + ODserverid = 54, /* 0x36 */ + ODparams = 55, /* 0x37 */ + ODmessage = 56, + ODmaxmsg = 57, + ODrenewaltime = 58, + ODrebindingtime = 59, + ODvendorclass = 60, + ODclientid = 61, /* 0x3d */ + ODtftpserver = 66, + ODbootfile = 67, + OBend = 255, +}; + +enum { Broadcast, Unicast}; + +Bootp bp; +unsigned char magic[] = {99, 130, 83, 99}; + +/* conf */ +static unsigned char xid[sizeof bp.xid]; +static unsigned char hwaddr[16]; +static time_t starttime; +static char *ifname = "eth0"; +static char *cid = ""; +static char *program = ""; +static int sock; +/* sav */ +static unsigned char server[4]; +static unsigned char client[4]; +static unsigned char mask[4]; +static unsigned char router[4]; +static unsigned char dns[4]; +static unsigned long t1; + +static int dflag = 1; /* change DNS in /etc/resolv.conf ? */ +static int iflag = 1; /* set IP ? */ + +#define IP(a,b,c,d) (unsigned char[4]){a,b,c,d} + +static void +hnput(unsigned char *dst, unsigned long long src, size_t n) +{ + unsigned int i; + + for(i = 0; n--; i++) + dst[i] = (src >> (n * 8)) & 0xff; +} + +static struct sockaddr * +iptoaddr(struct sockaddr *ifaddr, unsigned char ip[4], int port) +{ + struct sockaddr_in *in = (struct sockaddr_in *)ifaddr; + + in->sin_family = AF_INET; + in->sin_port = htons(port); + memcpy(&(in->sin_addr), ip, sizeof in->sin_addr); + return ifaddr; +} + +/* sendto UDP wrapper */ +static ssize_t +udpsend(unsigned char ip[4], int fd, void *data, size_t n) +{ + struct sockaddr addr; + socklen_t addrlen = sizeof addr; + ssize_t sent; + + iptoaddr(&addr, ip, 67); /* bootp server */ + if((sent = sendto(fd, data, n, 0, &addr, addrlen)) == -1) + eprintf("sendto:"); + return sent; +} + +/* recvfrom UDP wrapper */ +static ssize_t +udprecv(unsigned char ip[4], int fd, void *data, size_t n) +{ + struct sockaddr addr; + socklen_t addrlen = sizeof addr; + ssize_t r; + + iptoaddr(&addr, ip, 68); /* bootp client */ + if((r = recvfrom(fd, data, n, 0, &addr, &addrlen)) == -1) + eprintf("recvfrom:"); + return r; +} + +static void +setip(unsigned char ip[4], unsigned char mask[4], unsigned char gateway[4]) +{ + struct ifreq ifreq; + struct rtentry rtreq; + int fd; + + memset(&ifreq, 0, sizeof(ifreq)); + memset(&rtreq, 0, sizeof(rtreq)); + + strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE); + iptoaddr(&(ifreq.ifr_addr), ip, 0); + if((fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)) == -1) + eprintf("can't set ip, socket:"); + ioctl(fd, SIOCSIFADDR, &ifreq); + iptoaddr(&(ifreq.ifr_netmask), mask, 0); + ioctl(fd, SIOCSIFNETMASK, &ifreq); + ifreq.ifr_flags = IFF_UP | IFF_RUNNING | IFF_BROADCAST | IFF_MULTICAST; + ioctl(fd, SIOCSIFFLAGS, &ifreq); + /* gw */ + rtreq.rt_flags = (RTF_UP | RTF_GATEWAY); + iptoaddr(&(rtreq.rt_gateway), gateway, 0); + iptoaddr(&(rtreq.rt_genmask), IP(0, 0, 0, 0), 0); + iptoaddr(&(rtreq.rt_dst), IP(0, 0, 0, 0), 0); + ioctl(fd, SIOCADDRT, &rtreq); + + close(fd); +} + +static void +cat(int dfd, char *src) +{ + char buf[BUFSIZ]; + int n, fd; + + if((fd = open(src, O_RDONLY)) == -1) + return; /* can't read, but don't error out */ + while((n = read(fd, buf, sizeof buf)) > 0) + write(dfd, buf, n); + close(fd); +} + +static void +setdns(unsigned char dns[4]) +{ + char buf[128]; + int fd; + + if((fd = creat("/etc/resolv.conf", 0644)) == -1) { + weprintf("can't change /etc/resolv.conf:"); + return; + } + cat(fd, "/etc/resolv.conf.head"); + if(snprintf(buf, sizeof(buf) - 1, "\nnameserver %d.%d.%d.%d\n", + dns[0], dns[1], dns[2], dns[3]) > 0) + write(fd, buf, strlen(buf)); + cat(fd, "/etc/resolv.conf.tail"); + close(fd); +} + +static void +optget(Bootp *bp, void *data, int opt, int n) +{ + unsigned char *p = bp->optdata; + unsigned char *top = ((unsigned char *)bp) + sizeof *bp; + int code, len; + + while(p < top) { + code = *p++; + if(code == OBpad) + continue; + if(code == OBend || p == top) + break; + len = *p++; + if(len > top - p) + break; + if(code == opt) { + memcpy(data, p, MIN(len, n)); + return; + } + p += len; + } +} + +static unsigned char * +optput(unsigned char *p, int opt, unsigned char *data, size_t len) +{ + *p++ = opt; + *p++ = (unsigned char)len; + memcpy(p, data, len); + return p + len; +} + +static unsigned char * +hnoptput(unsigned char *p, int opt, long long data, size_t len) +{ + *p++ = opt; + *p++ = (unsigned char)len; + hnput(p, data, len); + return p + len; +} + +static void +dhcpsend(int type, int how) +{ + unsigned char *ip, *p; + + memset(&bp, 0, sizeof bp); + hnput(bp.op, Bootrequest, 1); + hnput(bp.htype, 1, 1); + hnput(bp.hlen, 6, 1); + memcpy(bp.xid, xid, sizeof xid); + hnput(bp.flags, Fbroadcast, sizeof bp.flags); + hnput(bp.secs, time(NULL) - starttime, sizeof bp.secs); + memcpy(bp.magic, magic, sizeof bp.magic); + memcpy(bp.chaddr, hwaddr, sizeof bp.chaddr); + p = bp.optdata; + p = hnoptput(p, ODtype, type, 1); + p = optput(p, ODclientid, (unsigned char*)cid, strlen(cid)); + + switch(type) { + case DHCPdiscover: + break; + case DHCPrequest: + /* memcpy(bp.ciaddr, client, sizeof bp.ciaddr); */ + p = hnoptput(p, ODlease, t1, sizeof t1); + p = optput(p, ODipaddr, client, sizeof client); + p = optput(p, ODserverid, server, sizeof server); + break; + case DHCPrelease: + memcpy(bp.ciaddr, client, sizeof client); + p = optput(p, ODipaddr, client, sizeof client); + p = optput(p, ODserverid, server, sizeof server); + break; + } + *p++ = OBend; + + ip = (how == Broadcast) ? IP(255, 255, 255, 255) : server; + udpsend(ip, sock, &bp, p - (unsigned char *)&bp); +} + +static int +dhcprecv(void) +{ + unsigned char type; + struct pollfd pfd; + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = sock; + pfd.events = POLLIN; + + memset(&bp, 0, sizeof bp); + if(poll(&pfd, 1, -1) == -1) { + if(errno != EINTR) + eprintf("poll:"); + else + return Timeout; + } + udprecv(IP(255, 255, 255, 255), sock, &bp, sizeof bp); + optget(&bp, &type, ODtype, sizeof type); + return type; +} + +static void +acceptlease(void) +{ + char buf[128]; + + if(iflag == 1) + setip(client, mask, router); + if(dflag == 1) + setdns(dns); + if(*program) { + snprintf(buf, sizeof(buf), "%d.%d.%d.%d", server[0], server[1], server[2], server[3]); + setenv("SERVER", buf, 1); + snprintf(buf, sizeof(buf), "%d.%d.%d.%d", client[0], client[1], client[2], client[3]); + setenv("CLIENT", buf, 1); + snprintf(buf, sizeof(buf), "%d.%d.%d.%d", mask[0], mask[1], mask[2], mask[3]); + setenv("MASK", buf, 1); + snprintf(buf, sizeof(buf), "%d.%d.%d.%d", router[0], router[1], router[2], router[3]); + setenv("ROUTER", buf, 1); + snprintf(buf, sizeof(buf), "%d.%d.%d.%d", dns[0], dns[1], dns[2], dns[3]); + setenv("DNS", buf, 1); + system(program); + } + alarm(t1); +} + +static void +run(void) +{ +#if 0 +InitReboot: + /* send DHCPrequest to old server */ + dhcpsend(DHCPrequest, Broadcast); + goto Rebooting; +Rebooting: + switch (dhcprecv()) { + case DHCPnak: + goto Init; + case DHCPack: + acceptoffer(); + goto Bound; + } +#endif +Init: + dhcpsend(DHCPdiscover, Broadcast); + alarm(1); + goto Selecting; +Selecting: + switch(dhcprecv()) { + case DHCPoffer: + alarm(0); + memcpy(client, bp.yiaddr, sizeof client); + optget(&bp, server, ODserverid, sizeof server); + optget(&bp, mask, OBmask, sizeof mask); + optget(&bp, router, OBrouter, sizeof router); + optget(&bp, dns, OBdnsserver, sizeof dns); + optget(&bp, &t1, ODlease, sizeof t1); + t1 = ntohl(t1); + dhcpsend(DHCPrequest, Broadcast); + goto Requesting; + case Timeout: + goto Init; + default: + goto Selecting; + } +Requesting: + switch(dhcprecv()) { + case DHCPoffer: + goto Requesting; /* ignore other offers. */ +#if 0 + case DHCPack: /* (and you don't want it) ? */ + dhcpsend(DHCPdecline, Unicast); + goto Init; +#endif + case DHCPack: + acceptlease(); + goto Bound; + } +Bound: + fputs("Congrats! You should be on the 'net.\n", stdout); + if(fork()) + exit(EXIT_SUCCESS); + switch (dhcprecv()) { + case DHCPoffer: + case DHCPack: + case DHCPnak: + goto Bound; /* discard offer, ACK or NAK */ + case Timeout: + dhcpsend(DHCPrequest, Unicast); + goto Renewing; + } +Renewing: + switch(dhcprecv()) { + case DHCPack: + acceptlease(); + goto Bound; + case DHCPnak: + goto Init; + case Timeout: + dhcpsend(DHCPrequest, Broadcast); + goto Rebinding; + } +Rebinding: + switch(dhcprecv()) { + case DHCPnak: /* lease expired */ + goto Init; + case DHCPack: + acceptlease(); + goto Bound; + } +} + +static void nop(int unused) { + (void) unused; +} + +static void cleanexit(int unused) { + (void) unused; + dhcpsend(DHCPrelease, Unicast); + exit(EXIT_SUCCESS); +} + +static void +usage(void) { + eprintf("usage: sdhcp [-i] [-d] [-e program] [ifname] [clientid]\n"); +} + +int +main(int argc, char *argv[]) +{ + int bcast = 1; + struct ifreq ifreq; + struct sockaddr addr; + int rnd; + + ARGBEGIN { + case 'e': /* run program */ + program = EARGF(usage()); + break; + case 'i': /* don't set ip */ + iflag = 0; + break; + case 'd': /* don't update DNS in/etc/resolv.conf */ + dflag = 0; + break; + default: + usage(); + break; + } ARGEND; + + if(argc >= 1) + ifname = argv[0]; /* interface name */ + if(argc >= 2) + cid = argv[1]; /* client-id */ + + memset(&ifreq, 0, sizeof(ifreq)); + signal(SIGALRM, nop); + signal(SIGTERM, cleanexit); + + if((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + eprintf("socket:"); + if(setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof bcast) == -1) + eprintf("setsockopt:"); + + strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE); + ioctl(sock, SIOCGIFINDEX, &ifreq); + if(setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof ifreq) == -1) + eprintf("setsockopt:"); + iptoaddr(&addr, IP(255, 255, 255, 255), 68); + if(bind(sock, (void*)&addr, sizeof addr) != 0) + eprintf("bind:"); + ioctl(sock, SIOCGIFHWADDR, &ifreq); + memcpy(hwaddr, ifreq.ifr_hwaddr.sa_data, sizeof ifreq.ifr_hwaddr.sa_data); + + if((rnd = open("/dev/urandom", O_RDONLY)) == -1) + eprintf("can't open /dev/urandom to generate unique transaction identifier:"); + read(rnd, xid, sizeof xid); + close(rnd); + + starttime = time(NULL); + run(); + return EXIT_SUCCESS; +} diff --git a/sdhcp/util.h b/sdhcp/util.h @@ -0,0 +1,9 @@ +#define MIN(a,b) (((a)<(b))?(a):(b)) +#define bpdump(p,n) 1 + +#undef strlcpy +size_t strlcpy(char *, const char *, size_t); + +void weprintf(const char *, ...); +void eprintf(const char *, ...); +void enprintf(int, const char *, ...); diff --git a/sdhcp/util/eprintf.c b/sdhcp/util/eprintf.c @@ -0,0 +1,67 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../util.h" + +char *argv0; + +static void venprintf(int, const char *, va_list); + +void +eprintf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + venprintf(EXIT_FAILURE, fmt, ap); + va_end(ap); +} + +void +enprintf(int status, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + venprintf(status, fmt, ap); + va_end(ap); +} + +void +venprintf(int status, const char *fmt, va_list ap) +{ +#ifdef DEBUG + fprintf(stderr, "%s: ", argv0); +#endif + + vfprintf(stderr, fmt, ap); + + if(fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } + + exit(status); +} + +void +weprintf(const char *fmt, ...) +{ + va_list ap; + +#ifdef DEBUG + fprintf(stderr, "%s: ", argv0); +#endif + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } +} diff --git a/sdhcp/util/strlcpy.c b/sdhcp/util/strlcpy.c @@ -0,0 +1,32 @@ +/* Taken from OpenBSD */ +#include <sys/types.h> +#include <string.h> +#include "../util.h" + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + return(s - src - 1); /* count does not include NUL */ +} diff --git a/sed/Makefile b/sed/Makefile @@ -0,0 +1,9 @@ +OBJ = compile.o fgetln.o main.o misc.o process.o reallocarray.o strlcat.o strlcpy.o +TARG = sed + +all: $(TARG) + +include ../std.mk + +clean: + rm -f $(TARG) $(OBJ) diff --git a/sed/compile.c b/sed/compile.c @@ -0,0 +1,861 @@ +/* $OpenBSD: compile.c,v 1.36 2014/10/08 04:19:08 deraadt 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. + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <regex.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "defs.h" +#include "extern.h" +#include "util.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 char *compile_re(char *, regex_t **); +static char *compile_subst(char *, struct s_subst *); +static char *compile_text(void); +static char *compile_tr(char *, char **); +static struct s_command + **compile_stream(struct s_command **); +static char *duptoeol(char *, char *, 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(); + appends = xreallocarray(NULL, appendnum, sizeof(struct s_appends)); + match = xreallocarray(NULL, maxnsub + 1, sizeof(regmatch_t)); +} + +#define EATSPACE() do { \ + if (p) \ + while (isascii((unsigned char)*p) && \ + isspace((unsigned char)*p)) \ + p++; \ + } while (0) + +static struct s_command ** +compile_stream(struct s_command **link) +{ + char *p; + static char *lbuf; /* To avoid excessive malloc calls */ + static size_t bufsize; + struct s_command *cmd, *cmd2, *stack; + struct s_format *fp; + int naddr; /* Number of addresses */ + + stack = 0; + for (;;) { + if ((p = cu_fgets(&lbuf, &bufsize)) == NULL) { + if (stack != 0) + err(COMPILE, "unexpected EOF (pending }'s)"); + return (link); + } + +semicolon: EATSPACE(); + if (*p == '#' || *p == '\0') + continue; + if (*p == ';') { + p++; + goto semicolon; + } + *link = cmd = xmalloc(sizeof(struct s_command)); + link = &cmd->next; + cmd->nonsel = cmd->inrange = 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) + err(COMPILE, "command expected"); + cmd->code = *p; + for (fp = cmd_fmts; fp->code; fp++) + if (fp->code == *p) + break; + if (!fp->code) + err(COMPILE, "invalid command code %c", *p); + if (naddr > fp->naddr) + err(COMPILE, + "command %c expects up to %d address(es), found %d", + *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) + err(COMPILE, "unexpected }"); + 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(); + if (*p == ';') { + p++; + link = &cmd->next; + goto semicolon; + } + if (*p) + err(COMPILE, +"extra characters at the end of %c command", cmd->code); + break; + case TEXT: /* a c i */ + p++; + EATSPACE(); + if (*p != '\\') + err(COMPILE, "command %c expects \\ followed by" + " text", cmd->code); + p++; + EATSPACE(); + if (*p) + err(COMPILE, "extra characters after \\ at the" + " end of %c command", cmd->code); + cmd->t = compile_text(); + break; + case COMMENT: /* \0 # */ + break; + case WFILE: /* w */ + p++; + EATSPACE(); + if (*p == '\0') + err(COMPILE, "filename expected"); + cmd->t = duptoeol(p, "w command", NULL); + if (aflag) + cmd->u.fd = -1; + else if ((cmd->u.fd = open(p, + O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, + DEFFILEMODE)) == -1) + err(FATAL, "%s: %s", p, strerror(errno)); + break; + case RFILE: /* r */ + p++; + EATSPACE(); + cmd->t = duptoeol(p, "read command", NULL); + break; + case BRANCH: /* b t */ + p++; + EATSPACE(); + if (*p == '\0') + cmd->t = NULL; + else + cmd->t = duptoeol(p, "branch", &p); + if (*p == ';') { + p++; + goto semicolon; + } + break; + case LABEL: /* : */ + p++; + EATSPACE(); + cmd->t = duptoeol(p, "label", &p); + if (strlen(cmd->t) == 0) + err(COMPILE, "empty label"); + enterlabel(cmd); + if (*p == ';') { + p++; + goto semicolon; + } + break; + case SUBST: /* s */ + p++; + if (*p == '\0' || *p == '\\') + err(COMPILE, "substitute pattern can not be" + " delimited by newline or backslash"); + cmd->u.s = xmalloc(sizeof(struct s_subst)); + p = compile_re(p, &cmd->u.s->re); + if (p == NULL) + err(COMPILE, "unterminated substitute pattern"); + --p; + p = compile_subst(p, cmd->u.s); + p = compile_flags(p, cmd->u.s); + EATSPACE(); + if (*p == ';') { + p++; + link = &cmd->next; + goto semicolon; + } + break; + case TR: /* y */ + p++; + p = compile_tr(p, (char **)&cmd->u.y); + EATSPACE(); + if (*p == ';') { + p++; + link = &cmd->next; + goto semicolon; + } + if (*p) + err(COMPILE, "extra text at the end of a" + " transform command"); + 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 == '\\') + err(COMPILE, "\\ can not be used as a string delimiter"); + else if (c == '\n') + err(COMPILE, "newline can not be used as a string delimiter"); + while (*p) { + if (*p == '[' && *p != c) { + if ((d = compile_ccl(&p, d)) == NULL) + err(COMPILE, "unbalanced brackets ([])"); + 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; + } else if (*s == '\\' && s[1] == 'n') { + *t = '\n'; + s++; + } + if (*s == ']') { + *sp = ++s; + return (++t); + } else { + return (NULL); + } +} + +/* + * Get a regular expression. P points to the delimiter of the regular + * expression; repp points to the address of a regexp pointer. 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 regular expression. The regexp + * pointer is set to the compiled regular expression. + * Cflags are passed to regcomp. + */ +static char * +compile_re(char *p, regex_t **repp) +{ + int eval; + char *re; + + re = xmalloc(strlen(p) + 1); /* strlen(re) <= strlen(p) */ + p = compile_delimited(p, re, 0); + if (p && strlen(re) == 0) { + *repp = NULL; + free(re); + return (p); + } + *repp = xmalloc(sizeof(regex_t)); + if (p && (eval = regcomp(*repp, re, Eflag ? REG_EXTENDED : 0)) != 0) + err(COMPILE, "RE error: %s", strregerror(eval, *repp)); + if (maxnsub < (*repp)->re_nsub) + maxnsub = (*repp)->re_nsub; + free(re); + return (p); +} + +/* + * 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; + static size_t bufsize; + int asize, ref, size; + char c, *text, *op, *sp; + int sawesc = 0; + + c = *p++; /* Terminator character */ + if (c == '\0') + return (NULL); + + s->maxbref = 0; + s->linenum = linenum; + text = NULL; + asize = size = 0; + do { + size_t len = ROUNDLEN(strlen(p) + 1); + if (asize - size < len) { + do { + asize += len; + } while (asize - size < len); + text = xrealloc(text, asize); + } + 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 = *p - '0'; + if (s->re != NULL && + ref > s->re->re_nsub) + err(COMPILE, +"\\%c not defined in the RE", *p); + if (s->maxbref < ref) + s->maxbref = ref; + } else if (*p == '&' || *p == '\\') + *sp++ = '\\'; + } else if (*p == c) { + p++; + *sp++ = '\0'; + size += sp - op; + s->new = xrealloc(text, size); + return (p); + } else if (*p == '\n') { + err(COMPILE, +"unescaped newline inside substitute pattern"); + /* NOTREACHED */ + } + *sp++ = *p; + } + size += sp - op; + } while ((p = cu_fgets(&lbuf, &bufsize))); + err(COMPILE, "unterminated substitute in regular expression"); + /* NOTREACHED */ + return NULL; +} + +/* + * 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 */ + long l; + char wfile[PATH_MAX], *q; + + s->n = 1; /* Default */ + s->p = 0; + s->wfile = NULL; + s->wfd = -1; + for (gn = 0;;) { + EATSPACE(); /* EXTENSION */ + switch (*p) { + case 'g': + if (gn) + err(COMPILE, "more than one number or 'g' in" + " substitute flags"); + gn = 1; + s->n = 0; + break; + case '\0': + case '\n': + case ';': + return (p); + case 'p': + s->p = 1; + break; + case '1': case '2': case '3': + case '4': case '5': case '6': + case '7': case '8': case '9': + if (gn) + err(COMPILE, "more than one number or 'g' in" + " substitute flags"); + gn = 1; + l = strtol(p, &p, 10); + if (l <= 0 || l >= INT_MAX) + err(COMPILE, + "number in substitute flags out of range"); + s->n = (int)l; + continue; + case 'w': + p++; +#ifdef HISTORIC_PRACTICE + if (*p != ' ') { + err(WARNING, "space missing before w wfile"); + return (p); + } +#endif + EATSPACE(); + q = wfile; + while (*p) { + if (*p == '\n') + break; + *q++ = *p++; + } + *q = '\0'; + if (q == wfile) + err(COMPILE, "no wfile specified"); + s->wfile = strdup(wfile); + if (!aflag && (s->wfd = open(wfile, + O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, + DEFFILEMODE)) == -1) + err(FATAL, "%s: %s", wfile, strerror(errno)); + return (p); + default: + err(COMPILE, + "bad flag in substitute command: '%c'", *p); + break; + } + p++; + } +} + +/* + * Compile a translation set of strings into a lookup table. + */ +static char * +compile_tr(char *p, char **transtab) +{ + int i; + char *lt, *op, *np; + char *old = NULL, *new = NULL; + + if (*p == '\0' || *p == '\\') + err(COMPILE, +"transform pattern can not be delimited by newline or backslash"); + old = xmalloc(strlen(p) + 1); + p = compile_delimited(p, old, 1); + if (p == NULL) { + err(COMPILE, "unterminated transform source string"); + goto bad; + } + new = xmalloc(strlen(p) + 1); + p = compile_delimited(--p, new, 1); + if (p == NULL) { + err(COMPILE, "unterminated transform target string"); + goto bad; + } + EATSPACE(); + if (strlen(new) != strlen(old)) { + err(COMPILE, "transform strings are not the same length"); + goto bad; + } + /* We assume characters are 8 bits */ + lt = xmalloc(UCHAR_MAX + 1); + for (i = 0; i <= UCHAR_MAX; i++) + lt[i] = (char)i; + for (op = old, np = new; *op; op++, np++) + lt[(u_char)*op] = *np; + *transtab = lt; + free(old); + free(new); + return (p); +bad: + free(old); + free(new); + return (NULL); +} + +/* + * Compile the text following an a, c, or i command. + */ +static char * +compile_text(void) +{ + int asize, esc_nl, size; + char *lbuf, *text, *p, *op, *s; + size_t bufsize; + + lbuf = text = NULL; + asize = size = 0; + while ((p = cu_fgets(&lbuf, &bufsize))) { + size_t len = ROUNDLEN(strlen(p) + 1); + if (asize - size < len) { + do { + asize += len; + } while (asize - size < len); + text = xrealloc(text, asize); + } + op = s = text + size; + for (esc_nl = 0; *p != '\0'; p++) { + if (*p == '\\' && p[1] != '\0' && *++p == '\n') + esc_nl = 1; + *s++ = *p; + } + size += s - op; + if (!esc_nl) { + *s = '\0'; + break; + } + } + free(lbuf); + text = xrealloc(text, size + 1); + text[size] = '\0'; + return (text); +} + +/* + * 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; + + switch (*p) { + case '\\': /* Context address */ + ++p; + /* FALLTHROUGH */ + case '/': /* Context address */ + p = compile_re(p, &a->u.r); + if (p == NULL) + err(COMPILE, "unterminated regular expression"); + a->type = AT_RE; + return (p); + + case '$': /* Last line */ + a->type = AT_LAST; + return (p + 1); + /* Line number */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + a->type = AT_LINE; + a->u.l = strtoul(p, &end, 10); + return (end); + default: + err(COMPILE, "expected context address"); + return (NULL); + } +} + +/* + * duptoeol -- + * Return a copy of all the characters up to \n or \0. + */ +static char * +duptoeol(char *s, char *ctype, char **semi) +{ + size_t len; + int ws; + char *start; + + ws = 0; + if (semi) { + for (start = s; *s != '\0' && *s != '\n' && *s != ';'; ++s) + ws = isspace((unsigned char)*s); + } else { + for (start = s; *s != '\0' && *s != '\n'; ++s) + ws = isspace((unsigned char)*s); + *s = '\0'; + } + if (ws) + err(WARNING, "whitespace after %s", ctype); + len = s - start + 1; + if (semi) + *semi = s; + s = xmalloc(len); + strlcpy(s, start, len); + return (s); +} + +/* + * 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) + err(COMPILE2, "undefined label '%s'", 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) + err(COMPILE2, "duplicate label '%s'", 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) + err(WARNING, "unused label '%s'", + lh->lh_cmd->t); + free(lh); + } + } +} diff --git a/sed/defs.h b/sed/defs.h @@ -0,0 +1,148 @@ +/* * $OpenBSD: defs.h,v 1.4 2008/10/16 16:34:32 millert 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. + * + * from: @(#)defs.h 8.1 (Berkeley) 6/6/93 + */ + +/* + * Types of address specifications + */ +enum e_atype { + AT_RE, /* Line that match RE */ + AT_LINE, /* Specific 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 */ + char *wfile; /* NULL if no wfile */ + int wfd; /* Cached file descriptor */ + regex_t *re; /* Regular expression */ + int maxbref; /* Largest backreference. */ + u_long linenum; /* Line number. */ + char *new; /* Replacement text */ +}; + + +/* + * 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 */ + 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 */ + u_char *y; /* Replace command array */ + int fd; /* File descriptor for w */ + } u; + char code; /* Command code */ + u_int nonsel:1; /* True if ! */ + u_int inrange:1; /* True if in range */ +}; + +/* + * 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; + +/* + * Error severity codes: + */ +#define FATAL 0 /* Exit immediately with 1 */ +#define ERROR 1 /* Continue, but change exit value */ +#define WARNING 2 /* Just print the warning */ +#define COMPILE 3 /* Print error, count and finish script */ +#define COMPILE2 3 /* Print error, count and finish script */ + +/* + * Round up to the nearest multiple of _POSIX2_LINE_MAX + */ +#define ROUNDLEN(x) \ + (((x) + _POSIX2_LINE_MAX - 1) & ~(_POSIX2_LINE_MAX - 1)) diff --git a/sed/extern.h b/sed/extern.h @@ -0,0 +1,57 @@ +/* * $OpenBSD: extern.h,v 1.7 2014/10/08 04:19:08 deraadt 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. + * + * from: @(#)extern.h 8.1 (Berkeley) 6/6/93 + */ + +extern struct s_command *prog; +extern struct s_appends *appends; +extern regmatch_t *match; +extern size_t maxnsub; +extern u_long linenum; +extern int appendnum; +extern int lastline; +extern int Eflag, aflag, eflag, nflag; +extern char *fname; + +void cfclose(struct s_command *, struct s_command *); +void compile(void); +void cspace(SPACE *, char *, size_t, enum e_spflag); +char *cu_fgets(char **, size_t *); +void err(int, const char *, ...); +int mf_fgets(SPACE *, enum e_spflag); +void process(void); +char *strregerror(int, regex_t *); +void *xmalloc(size_t); +void *xreallocarray(void *, size_t, size_t); +void *xrealloc(void *, size_t); diff --git a/sed/fgetln.c b/sed/fgetln.c @@ -0,0 +1,68 @@ +/* + * Copyright © 2005 Hector Garcia Alvarez + * Copyright © 2005, 2008-2012 Guillem Jover <guillem@hadrons.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 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 ``AS IS'' AND ANY EXPRESS OR IMPLIED 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 <stdio.h> +#include <sys/types.h> +#include <string.h> + +struct filebuf { + FILE *fp; + char *buf; + size_t len; +}; + +#define FILEBUF_POOL_ITEMS 32 + +static struct filebuf fb_pool[FILEBUF_POOL_ITEMS]; +static int fb_pool_cur; + +char * +fgetln(FILE *stream, size_t *len) +{ + struct filebuf *fb; + ssize_t nread; + + /* Try to diminish the possibility of several fgetln() calls being + * used on different streams, by using a pool of buffers per file. */ + fb = &fb_pool[fb_pool_cur]; + if (fb->fp != stream && fb->fp != NULL) { + fb_pool_cur++; + fb_pool_cur %= FILEBUF_POOL_ITEMS; + fb = &fb_pool[fb_pool_cur]; + } + fb->fp = stream; + + nread = getline(&fb->buf, &fb->len, stream); + /* Note: the getdelim/getline API ensures nread != 0. */ + if (nread == -1) { + *len = 0; + return NULL; + } else { + *len = (size_t)nread; + return fb->buf; + } +} diff --git a/sed/main.c b/sed/main.c @@ -0,0 +1,358 @@ +/* $OpenBSD: main.c,v 1.17 2009/10/27 23:59:43 deraadt 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. + */ + +#include <sys/types.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <regex.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "defs.h" +#include "extern.h" +#include "util.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; + +int Eflag, aflag, eflag, nflag; + +/* + * Current file and line number; line numbers restart across compilation + * units, but span across input files. + */ +char *fname; /* File name. */ +u_long linenum; +int lastline; /* TRUE on the last line of the last file */ + +static void add_compunit(enum e_cut, char *); +static void add_file(char *); + +int +main(int argc, char *argv[]) +{ + int c, fflag; + + fflag = 0; + while ((c = getopt(argc, argv, "Eae:f:nru")) != -1) + switch (c) { + case 'E': + case 'r': + Eflag = 1; + break; + case 'a': + aflag = 1; + break; + case 'e': + eflag = 1; + add_compunit(CU_STRING, optarg); + break; + case 'f': + fflag = 1; + add_compunit(CU_FILE, optarg); + break; + case 'n': + nflag = 1; + break; + case 'u': + setlinebuf(stdout); + break; + default: + case '?': + (void)fprintf(stderr, + "usage: sed [-aEnru] command [file ...]\n" + " sed [-aEnru] [-e command] [-f command_file] [file ...]\n"); + exit(1); + } + 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(FATAL, "stdout: %s", strerror(errno)); + exit (0); +} + +/* + * Like fgets, but go through the chain of compilation units chaining them + * together. Empty strings and files are ignored. + */ +char * +cu_fgets(char **outbuf, size_t *outsize) +{ + 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]; + size_t len; + char *p; + + if (*outbuf == NULL) + *outsize = 0; + +again: + switch (state) { + case ST_EOF: + if (script == NULL) + return (NULL); + linenum = 0; + switch (script->type) { + case CU_FILE: + if ((f = fopen(script->s, "r")) == NULL) + err(FATAL, + "%s: %s", script->s, strerror(errno)); + fname = script->s; + state = ST_FILE; + goto again; + case CU_STRING: + if ((snprintf(string_ident, + sizeof(string_ident), "\"%s\"", script->s)) >= + sizeof(string_ident)) + strlcpy(string_ident + + sizeof(string_ident) - 6, " ...\"", 5); + fname = string_ident; + s = script->s; + state = ST_STRING; + goto again; + } + case ST_FILE: + if ((p = fgetln(f, &len)) != NULL) { + linenum++; + if (len >= *outsize) { + free(*outbuf); + *outsize = ROUNDLEN(len + 1); + *outbuf = xmalloc(*outsize); + } + memcpy(*outbuf, p, len); + (*outbuf)[len] = '\0'; + if (linenum == 1 && p[0] == '#' && p[1] == 'n') + nflag = 1; + return (*outbuf); + } + 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 = *outbuf; + len = *outsize; + for (;;) { + if (len <= 1) { + *outbuf = xrealloc(*outbuf, + *outsize + _POSIX2_LINE_MAX); + p = *outbuf + *outsize - len; + len += _POSIX2_LINE_MAX; + *outsize += _POSIX2_LINE_MAX; + } + switch (*s) { + case '\0': + state = ST_EOF; + if (s == script->s) { + script = script->next; + goto again; + } else { + script = script->next; + *p = '\0'; + linenum++; + return (*outbuf); + } + case '\n': + *p++ = '\n'; + *p = '\0'; + s++; + linenum++; + return (*outbuf); + default: + *p++ = *s++; + len--; + } + } + } + /* 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) +{ + static FILE *f; /* Current open file */ + size_t len; + char *p; + int c; + + if (f == NULL) + /* Advance to first non-empty file */ + for (;;) { + if (files == NULL) { + lastline = 1; + return (0); + } + if (files->fname == NULL) { + f = stdin; + fname = "stdin"; + } else { + fname = files->fname; + if ((f = fopen(fname, "r")) == NULL) + err(FATAL, "%s: %s", + fname, strerror(errno)); + } + if ((c = getc(f)) != EOF) { + (void)ungetc(c, f); + break; + } + (void)fclose(f); + files = files->next; + } + + if (lastline) { + sp->len = 0; + return (0); + } + + /* + * Use fgetln so that we can handle essentially infinite input data. + * Can't use the pointer into the stdio buffer as the process space + * because the ungetc() can cause it to move. + */ + p = fgetln(f, &len); + if (ferror(f)) + err(FATAL, "%s: %s", fname, strerror(errno ? errno : EIO)); + cspace(sp, p, len, spflag); + + linenum++; + /* Advance to next non-empty file */ + while ((c = getc(f)) == EOF) { + (void)fclose(f); + files = files->next; + if (files == NULL) { + lastline = 1; + return (1); + } + if (files->fname == NULL) { + f = stdin; + fname = "stdin"; + } else { + fname = files->fname; + if ((f = fopen(fname, "r")) == NULL) + err(FATAL, "%s: %s", fname, strerror(errno)); + } + } + (void)ungetc(c, f); + 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; +} diff --git a/sed/misc.c b/sed/misc.c @@ -0,0 +1,124 @@ +/* $OpenBSD: misc.c,v 1.10 2014/10/08 04:19:08 deraadt 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. + */ + +#include <sys/types.h> + +#include <errno.h> +#include <regex.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + +#include "defs.h" +#include "extern.h" +#include "util.h" + +/* + * malloc with result test + */ +void * +xmalloc(size_t size) +{ + void *p; + + if ((p = malloc(size)) == NULL) + err(FATAL, "%s", strerror(errno)); + return (p); +} + +void * +xreallocarray(void *o, size_t nmemb, size_t size) +{ + void *p; + + if ((p = reallocarray(o, nmemb, size)) == NULL) + err(FATAL, "%s", strerror(errno)); + return (p); +} + +/* + * realloc with result test + */ +void * +xrealloc(void *p, size_t size) +{ + + if ((p = realloc(p, size)) == NULL) + err(FATAL, "%s", strerror(errno)); + return (p); +} + +/* + * Return a string for a regular expression error passed. This is a overkill, + * because of the silly semantics of regerror (we can never know the size of + * the buffer). + */ +char * +strregerror(int errcode, regex_t *preg) +{ + static char *oe; + size_t s; + + free(oe); + s = regerror(errcode, preg, "", 0); + oe = xmalloc(s); + (void)regerror(errcode, preg, oe, s); + return (oe); +} + +/* + * Error reporting function + */ +void +err(int severity, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + (void)fprintf(stderr, "sed: "); + switch (severity) { + case WARNING: + case COMPILE: + (void)fprintf(stderr, "%lu: %s: ", linenum, fname); + } + (void)vfprintf(stderr, fmt, ap); + va_end(ap); + (void)fprintf(stderr, "\n"); + if (severity == WARNING) + return; + exit(1); + /* NOTREACHED */ +} diff --git a/sed/process.c b/sed/process.c @@ -0,0 +1,625 @@ +/* $OpenBSD: process.c,v 1.19 2013/11/28 18:24:55 deraadt 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. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/uio.h> + +#include <ctype.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 "defs.h" +#include "extern.h" +#include "util.h" + +static SPACE HS, PS, SS; +#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 flush_appends(void); +static void lputs(char *); +static inline int regexec_e(regex_t *, 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 int appendx; /* Index into appends array. */ +int 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(s) do { fwrite(s, sizeof(u_char), psl, stdout); } while (0) + +void +process(void) +{ + struct s_command *cp; + SPACE tspace; + size_t len, oldpsl; + char *p; + + 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)); + 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) + (void)printf("%s", cp->t); + break; + 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 -= (p + 1) - ps; + memmove(ps, p + 1, psl); + goto top; + } + case 'g': + cspace(&PS, hs, hsl, REPLACE); + break; + case 'G': + if (hs == NULL) + cspace(&HS, "\n", 1, REPLACE); + cspace(&PS, hs, hsl, 0); + break; + case 'h': + cspace(&HS, ps, psl, REPLACE); + break; + case 'H': + cspace(&HS, ps, psl, 0); + break; + case 'i': + (void)printf("%s", cp->t); + break; + case 'l': + lputs(ps); + break; + case 'n': + if (!nflag && !pd) + OUT(ps); + flush_appends(); + if (!mf_fgets(&PS, REPLACE)) + exit(0); + pd = 0; + break; + case 'N': + flush_appends(); + if (!mf_fgets(&PS, 0)) { + if (!nflag && !pd) + OUT(ps); + exit(0); + } + break; + case 'p': + if (pd) + break; + OUT(ps); + break; + case 'P': + if (pd) + break; + if (psl != 0 && + (p = memchr(ps, '\n', psl - 1)) != NULL) { + oldpsl = psl; + psl = (p + 1) - ps; + } + OUT(ps); + if (p != NULL) + psl = oldpsl; + break; + case 'q': + if (!nflag && !pd) + OUT(ps); + 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(FATAL, "%s: %s", + cp->t, strerror(errno)); + if (write(cp->u.fd, ps, psl) != psl) + err(FATAL, "%s: %s", + cp->t, strerror(errno)); + break; + case 'x': + if (hs == NULL) + cspace(&HS, "\n", 1, REPLACE); + tspace = PS; + PS = HS; + HS = tspace; + break; + case 'y': + if (pd || psl == 0) + break; + for (p = ps, len = psl; --len; ++p) + *p = cp->u.y[(unsigned char)*p]; + break; + case ':': + case '}': + break; + case '=': + (void)printf("%lu\n", linenum); + } + cp = cp->next; + } /* for all cp */ + +new: if (!nflag && !pd) + OUT(ps); + 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 inrange + * flag to 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->inrange) { + if (MATCH(cp->a2)) { + cp->inrange = 0; + lastaddr = 1; + } + r = 1; + } else if (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 + */ + if (cp->a2->type == AT_LINE && + linenum >= cp->a2->u.l) + lastaddr = 1; + else + cp->inrange = 1; + r = 1; + } else + r = 0; + else + r = MATCH(cp->a1); + return (cp->nonsel ? !r : r); +} + +/* + * 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 slen; + int n, lastempty; + 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; + err(COMPILE, "\\%d not defined in the RE", + cp->u.s->maxbref); + } + } + if (!regexec_e(re, s, 0, 0, psl)) + return (0); + + SS.len = 0; /* Clean substitute space. */ + slen = psl; + n = cp->u.s->n; + lastempty = 1; + + do { + /* Copy the leading retained string. */ + if (n <= 1 && match[0].rm_so) + cspace(&SS, s, match[0].rm_so, APPEND); + + /* Skip zero-length matches right after other matches. */ + if (lastempty || match[0].rm_so || + match[0].rm_so != match[0].rm_eo) { + if (n <= 1) { + /* Want this match: append replacement. */ + regsub(&SS, s, cp->u.s->new); + if (n == 1) + n = -1; + } else { + /* Want a later match: append original. */ + if (match[0].rm_eo) + cspace(&SS, s, match[0].rm_eo, APPEND); + n--; + } + } + + /* Move past this match. */ + s += match[0].rm_eo; + slen -= match[0].rm_eo; + + /* + * After a zero-length match, advance one byte, + * and at the end of the line, terminate. + */ + if (match[0].rm_so == match[0].rm_eo) { + if (*s == '\0' || *s == '\n') + slen = -1; + else + slen--; + if (*s != '\0') + cspace(&SS, s++, 1, APPEND); + lastempty = 1; + } else + lastempty = 0; + + } while (n >= 0 && slen >= 0 && regexec_e(re, s, REG_NOTBOL, 0, slen)); + + /* Did not find the requested number of matches. */ + if (n > 1) + return (0); + + /* Copy the trailing retained string. */ + if (slen > 0) + cspace(&SS, s, slen, APPEND); + + /* + * 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(ps); + + /* 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(FATAL, "%s: %s", cp->u.s->wfile, strerror(errno)); + if (write(cp->u.s->wfd, ps, psl) != psl) + err(FATAL, "%s: %s", cp->u.s->wfile, strerror(errno)); + } + return (1); +} + +/* + * 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; + int 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, + stdout); + 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 file system. 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, stdout); + (void)fclose(f); + break; + } + if (ferror(stdout)) + err(FATAL, "stdout: %s", strerror(errno ? errno : EIO)); + appendx = sdone = 0; +} + +static void +lputs(char *s) +{ + int count; + char *escapes, *p; + struct winsize win; + static int termwidth = -1; + + if (termwidth == -1) { + if ((p = getenv("COLUMNS"))) + termwidth = atoi(p); + else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 && + win.ws_col > 0) + termwidth = win.ws_col; + else + termwidth = 60; + } + + for (count = 0; *s; ++s) { + if (count >= termwidth) { + (void)printf("\\\n"); + count = 0; + } + if (isascii((unsigned char)*s) && isprint((unsigned char)*s) + && *s != '\\') { + (void)putchar(*s); + count++; + } else if (*s != '\n') { + escapes = "\\\a\b\f\r\t\v"; + (void)putchar('\\'); + if ((p = strchr(escapes, *s))) { + (void)putchar("\\abfrtv"[p - escapes]); + count += 2; + } else { + (void)printf("%03o", *(u_char *)s); + count += 4; + } + } + } + (void)putchar('$'); + (void)putchar('\n'); + if (ferror(stdout)) + err(FATAL, "stdout: %s", strerror(errno ? errno : EIO)); +} + +static inline int +regexec_e(regex_t *preg, char *string, int eflags, + int nomatch, size_t slen) +{ + int eval; + int tmp; + + if (preg == NULL) { + if (defpreg == NULL) + err(FATAL, "first RE may not be empty"); + } else + defpreg = preg; + + /* Set anchors, discounting trailing newline (if any). */ + if (slen > 0 && string[slen - 1] == '\n') + slen--; + + /* workaround for systems that don't support REG_STARTEND */ + tmp = string[slen]; + string[slen] = '\0'; + + eval = regexec(defpreg, string, + nomatch ? 0 : maxnsub + 1, match, eflags); + + string[slen] = tmp; /* restore byte */ + + switch (eval) { + case 0: + return (1); + case REG_NOMATCH: + return (0); + } + err(FATAL, "RE error: %s", strregerror(eval, defpreg)); + /* NOTREACHED */ + return 0; +} + +/* + * regsub - perform substitutions after a regexp match + * Based on a routine by Henry Spencer + */ +static void +regsub(SPACE *sp, char *string, char *src) +{ + int len, no; + char c, *dst; + +#define NEEDSP(reqlen) \ + if (sp->len + (reqlen) + 1 >= sp->blen) { \ + size_t newlen = sp->blen + (reqlen) + 1024; \ + sp->space = sp->back = xrealloc(sp->back, newlen); \ + sp->blen = newlen; \ + 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 = 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'; +} + +/* + * aspace -- + * Append the source space to the destination space, allocating new + * space as necessary. + */ +void +cspace(SPACE *sp, 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) { + size_t newlen = tlen + 1024; + sp->space = sp->back = xrealloc(sp->back, newlen); + sp->blen = newlen; + } + + 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(FATAL, + "%s: %s", cp->u.s->wfile, strerror(errno)); + cp->u.s->wfd = -1; + break; + case 'w': + if (cp->u.fd != -1 && close(cp->u.fd)) + err(FATAL, "%s: %s", cp->t, strerror(errno)); + cp->u.fd = -1; + break; + case '{': + cfclose(cp->u.c, cp->next); + break; + } +} diff --git a/sed/reallocarray.c b/sed/reallocarray.c @@ -0,0 +1,38 @@ +/* $OpenBSD: reallocarray.c,v 1.1 2014/05/08 21:43:49 deraadt Exp $ */ +/* + * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net> + * + * 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 <sys/types.h> +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> + +/* + * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX + * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW + */ +#define MUL_NO_OVERFLOW (1UL << (sizeof(size_t) * 4)) + +void * +reallocarray(void *optr, size_t nmemb, size_t size) +{ + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) { + errno = ENOMEM; + return NULL; + } + return realloc(optr, size * nmemb); +} diff --git a/sed/sed.1 b/sed/sed.1 @@ -0,0 +1,566 @@ +.\" $OpenBSD: sed.1,v 1.44 2014/10/22 23:23:22 schwarze 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. +.\" +.\" from: @(#)sed.1 8.2 (Berkeley) 12/30/93 +.\" +.Dd $Mdocdate: October 22 2014 $ +.Dt SED 1 +.Os +.Sh NAME +.Nm sed +.Nd stream editor +.Sh SYNOPSIS +.Nm sed +.Op Fl aEnru +.Ar command +.Op Ar +.Nm sed +.Op Fl aEnru +.Op Fl e Ar command +.Op Fl f Ar command_file +.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 sed . +Multiple commands may be specified +separated by newlines or semicolons, +or 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 options are as follows: +.Bl -tag -width Ds +.It Fl a +The files listed as parameters for the +.Ic w +function or flag 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 +.Ic w +function or flag is applied to a line of input. +.It Fl E +Interpret regular expressions using POSIX extended regular expression syntax. +The default behaviour is to use POSIX basic regular expression syntax. +.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 r +An alias for +.Fl E , +for compatibility with GNU sed. +.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 u +Force output to be line buffered, +printing each line as it becomes available. +By default, output is line buffered when standard output is a terminal +and block buffered otherwise. +See +.Xr setbuf 3 +for a more detailed explanation. +.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 +.Ic 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. +.Sh SED ADDRESSES +An address is not required, but if specified must be a number (that counts +input lines +cumulatively across input files), a dollar character +.Pq Ql $ +that addresses the last line of input, or a context address +(which consists of a regular expression preceded and followed by a +delimiter). +.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 the inclusive range from +the first pattern space that matches the first address through the next +pattern space that matches the second. +(If the second address is a number less than or equal to the line number +first selected, only that line is selected.) +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 Ql \&! +function. +.Sh SED REGULAR EXPRESSIONS +By default, +.Nm +regular expressions are basic regular expressions +.Pq BREs . +Extended regular expressions are supported using the +.Fl E +and +.Fl r +options. +See +.Xr re_format 7 +for more information on regular expressions. +In addition, +.Nm +has the following two additions to BREs: +.Pp +.Bl -enum -compact +.It +In a context address, any character other than a backslash +.Pq Ql \e +or newline character may be used to delimit the regular expression. +The opening delimiter should be preceded by a backslash +unless it is a slash. +Putting a backslash character before the delimiting character +causes the character to be treated literally. +For example, in the context address \exabc\exdefx, the RE delimiter +is an +.Sq x +and the second +.Sq 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 can't, 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 . +.Sh 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 +.Ar 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 +.Ic r +and +.Ic w +functions, +as well as the +.Cm w +flag to the +.Ic s +function, +take an optional +.Ar file +parameter, +which should be separated from the function or flag by whitespace. +Files are created +(or their contents truncated) +before any input processing begins. +.Pp +The +.Ic b , +.Ic r , +.Ic s , +.Ic t , +.Ic w , +.Ic y , +and +.Ic \&: +functions all accept additional arguments. +The synopses below indicate which arguments have to be separated from +the function letters by whitespace characters. +.Pp +Functions can be combined to form a +.Em function list , +a list of +.Nm +functions each followed by a newline, as follows: +.Bd -literal -offset indent +{ function + function + ... + function +} +.Ed +.Pp +The braces can be preceded and followed by whitespace. +The functions can be preceded by whitespace as well. +.Pp +Functions and function lists may be preceded by an exclamation mark, +in which case they are applied only to lines that are +.Em not +selected by the addresses. +.Bl -tag -width Ds +.It [2addr] Ar function-list +Execute +.Ar function-list +only when the pattern space is selected. +.It Xo [1 addr] Ic a Ns \e +.br +.Ar text +.Xc +.Pp +Write +.Ar text +to standard output immediately before each attempt to read a line of input, +whether by executing the +.Ic N +function or by beginning a new cycle. +.It [2addr] Ns Ic b Bq Ar label +Branch to the +.Ic \&: +function with the specified +.Ar label . +If the label is not specified, branch to the end of the script. +.It Xo [2addr] Ic c Ns \e +.br +.Ar text +.Xc +.Pp +Delete the pattern space. +With 0 or 1 address or at the end of a 2-address range, +.Ar text +is written to the standard output. +.It [2addr] Ns Ic d +Delete the pattern space and start the next cycle. +.It [2addr] Ns Ic D +Delete the initial segment of the pattern space through the first +newline character and start the next cycle. +.It [2addr] Ns Ic g +Replace the contents of the pattern space with the contents of the +hold space. +.It [2addr] Ns Ic G +Append a newline character followed by the contents of the hold space +to the pattern space. +.It [2addr] Ns Ic h +Replace the contents of the hold space with the contents of the +pattern space. +.It [2addr] Ns Ic H +Append a newline character followed by the contents of the pattern space +to the hold space. +.It Xo [1addr] Ic i Ns \e +.br +.Ar text +.Xc +.Pp +Write +.Ar text +to the standard output. +.It [2addr] Ns Ic 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 backspace +\eb +.It form-feed +\ef +.It carriage-return +\er +.It tab +\et +.It vertical tab +\ev +.El +.Pp +Non-printable 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 +.Ql $ . +.It [2addr] Ns Ic 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. +.It [2addr] Ns Ic 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. +.It [2addr] Ns Ic p +Write the pattern space to standard output. +.It [2addr] Ns Ic P +Write the pattern space, up to the first newline character, +to the standard output. +.It [1addr] Ns Ic q +Branch to the end of the script and quit without starting a new cycle. +.It [1addr] Ns Ic r Ar file +Copy the contents of +.Ar file +to the standard output immediately before the next attempt to read a +line of input. +If +.Ar file +cannot be read for any reason, it is silently ignored and no error +condition is set. +.It [2addr] Ns Ic s Ns / Ns Ar RE Ns / Ns Ar replacement Ns / Ns Ar flags +Substitute the +.Ar replacement +string for the first instance of the regular expression +.Ar RE +in the pattern space. +Any character other than backslash or newline can be used instead of +a slash to delimit the regular expression and the replacement. +Within the regular expression and the replacement, +the regular expression delimiter itself can be used as +a literal character if it is preceded by a backslash. +.Pp +An ampersand +.Pq Ql & +appearing in the replacement is replaced by the string matching the +regular expression. +The special meaning of +.Ql & +in this context can be suppressed by preceding it by a backslash. +The string +.Ql \e# , +where +.Ql # +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 +.Ar flags +in the substitute function is zero or more of the following: +.Bl -tag -width "XXXXXX" -offset indent +.It Cm 0 No ... Cm 9 +Make the substitution only for the N'th occurrence of the regular +expression in the pattern space. +.It Cm g +Make the substitution for all non-overlapping matches of the +regular expression, not just the first one. +.It Cm 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 Cm w Ar file +Append the pattern space to +.Ar 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. +.El +.It [2addr] Ns Ic t Bq Ar label +Branch to the +.Ic \&: +function bearing the +.Ar label +if any substitutions have been made since the +most recent reading of an input line or execution of a +.Ic t +function. +If no label is specified, branch to the end of the script. +.It [2addr] Ns Ic w Ar file +Append the pattern space to the +.Ar file . +.It [2addr] Ns Ic x +Swap the contents of the pattern and hold spaces. +.It [2addr] Ns Ic y Ns / Ns Ar string1 Ns / Ns Ar string2 Ns / +Replace all occurrences of characters in +.Ar string1 +in the pattern space with the corresponding characters from +.Ar string2 . +Any character other than a backslash or newline can be used instead of +a slash to delimit the strings. +Within +.Ar string1 +and +.Ar string2 , +a backslash followed by any character other than a newline is that literal +character, and a backslash followed by an +.Sq n +is replaced by a newline character. +.It [0addr] Ns Ic \&: Ns Ar label +This function does nothing; it bears a +.Ar label +to which the +.Ic b +and +.Ic t +commands may branch. +.It [1addr] Ns Ic = +Write the line number to the standard output followed by a newline character. +.It [0addr] +Empty lines are ignored. +.It [0addr] Ns Ic # +The +.Ql # +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 +.Ql #n , +the default output is suppressed. +This is the same as specifying the +.Fl n +option on the command line. +.El +.Sh EXIT STATUS +.Ex -std sed +.Sh EXAMPLES +The following simulates the +.Xr cat 1 +.Fl s +command, +squeezing excess empty lines from standard input: +.Bd -literal -offset indent +$ sed -n ' +# Write non-empty lines. +/./ { + p + d + } +# Write a single empty line, then look for more empty lines. +/^$/ p +# Get the next line, discard the held <newline> (empty line), +# and look for more empty lines. +:Empty +/^$/ { + N + s/.// + b Empty + } +# Write the non-empty line before going back to search +# for the first in a set of empty lines. + p +\&' +.Ed +.Sh SEE ALSO +.Xr awk 1 , +.Xr ed 1 , +.Xr grep 1 , +.Xr re_format 7 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. +.Pp +The flags +.Op Fl aEru +are extensions to that specification. +.Pp +The use of newlines to separate multiple commands on the command line +is non-portable; +the use of newlines to separate multiple commands within a command file +.Pq Fl f Ar command_file +is portable. +.Sh HISTORY +A +.Nm +command appeared in +.At v7 . +.Sh CAVEATS +The use of semicolons to separate multiple commands +is not permitted for the following commands: +.Ic a , b , c , +.Ic i , r , t , +.Ic w , \&: , +and +.Ic # . diff --git a/sed/strlcat.c b/sed/strlcat.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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 <string.h> +#include <sys/types.h> + +#include "util.h" + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t +strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + return(dlen + (s - src)); /* count does not include NUL */ +} diff --git a/sed/strlcpy.c b/sed/strlcpy.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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 <string.h> +#include <sys/types.h> + +#include "util.h" + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + return(s - src - 1); /* count does not include NUL */ +} diff --git a/sed/util.h b/sed/util.h @@ -0,0 +1,12 @@ +#include <sys/stat.h> +#include <stdio.h> +#include <stddef.h> + +#define DEFFILEMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) + +char *fgetln(FILE *, size_t *); + +void *reallocarray(void *, size_t, size_t); + +size_t strlcat(char *, const char *, size_t); +size_t strlcpy(char *, const char *, size_t); diff --git a/sinit/LICENSE b/sinit/LICENSE @@ -0,0 +1,21 @@ +MIT/X Consortium License + +© 2014 Dimitris Papastamos <sin@2f30.org> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/sinit/Makefile b/sinit/Makefile @@ -0,0 +1,39 @@ +include config.mk + +OBJ = sinit.o +BIN = sinit + +all: $(BIN) + +$(BIN): $(OBJ) + $(CC) $(LDFLAGS) -o $@ $(OBJ) $(LDLIBS) + +sinit.o: config.h + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin + mkdir -p $(DESTDIR)$(MANPREFIX)/man8 + sed "s/VERSION/$(VERSION)/g" < $(BIN).8 > $(DESTDIR)$(MANPREFIX)/man8/$(BIN).8 + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN) + rm -f $(DESTDIR)$(MANPREFIX)/man8/$(BIN).8 + +dist: clean + mkdir -p sinit-$(VERSION) + cp LICENSE Makefile README config.def.h config.mk sinit.8 sinit.c sinit-$(VERSION) + tar -cf sinit-$(VERSION).tar sinit-$(VERSION) + gzip sinit-$(VERSION).tar + rm -rf sinit-$(VERSION) + +clean: + rm -f $(BIN) $(OBJ) sinit-$(VERSION).tar.gz + +.SUFFIXES: .def.h + +.def.h.h: + cp $< $@ + +.PHONY: + all install uninstall dist clean diff --git a/sinit/README b/sinit/README @@ -0,0 +1,26 @@ +sinit - suckless init +===================== + +sinit is a simple init. It was initially based on +Rich Felker's minimal init[1]. + +Why? +---- + +I wanted to get rid of Busybox init on my toy distro[2]. + +How? +---- + +There are 3 signals that sinit will act on. + + SIGUSR1: powers off the machine. + SIGINT: reboots the machine (or alternatively via ctrl-alt-del). + SIGCHLD: reap children + +To see how sinit integrates with the init scripts, then have +a look at [3]. + +[1] https://gist.github.com/rofl0r/6168719 +[2] http://git.2f30.org/morpheus/ +[3] http://git.2f30.org/ports/tree/fs/ diff --git a/sinit/config.def.h b/sinit/config.def.h @@ -0,0 +1,5 @@ +/* See LICENSE file for copyright and license details. */ + +static char *const rcinitcmd[] = { "/bin/rc.init", NULL }; +static char *const rcrebootcmd[] = { "/bin/rc.shutdown", "reboot", NULL }; +static char *const rcpoweroffcmd[] = { "/bin/rc.shutdown", "poweroff", NULL }; diff --git a/sinit/config.h b/sinit/config.h @@ -0,0 +1,5 @@ +/* See LICENSE file for copyright and license details. */ + +static char *const rcinitcmd[] = { "/bin/rc.init", NULL }; +static char *const rcrebootcmd[] = { "/bin/rc.shutdown", "reboot", NULL }; +static char *const rcpoweroffcmd[] = { "/bin/rc.shutdown", "poweroff", NULL }; diff --git a/sinit/config.mk b/sinit/config.mk @@ -0,0 +1,12 @@ +# sinit version +VERSION = 0.9.2 + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +CC = cc +LD = $(CC) +CPPFLAGS = +CFLAGS = -Wextra -Wall -Os +LDFLAGS = -s -static diff --git a/sinit/sinit.8 b/sinit/sinit.8 @@ -0,0 +1,62 @@ +.Dd December 4, 2014 +.Dt SINIT 8 sinit\-VERSION +.Os +.Sh NAME +.Nm sinit +.Nd simple init +.Sh DESCRIPTION +.Nm +is a simple init. It is configured by modifying +.Dq config.h +and recompiling the code. +.Ss Init sequence +By default +.Nm +will execute +.Dq /bin/rc.init . +You can chain your init scripts together at that point to allow for +single-user and multi-user operation. +.Ss Signal handling +.Nm +will respond to the following signals: +.Bl -tag -width xxxxxxxx +.It USR1 +Default action is to initiate the shutdown sequence by +executing +.Dq /bin/rc.shutdown poweroff . +.It INT +Default action is to initiate the reboot sequence by +executing +.Dq /bin/rc.shutdown reboot . +.It CHLD +Reap children. +.El +.Ss General considerations +Running the +.Dq rc.shutdown +script directly is not recommended. If any +process in your session has stale filesystem references then it is +likely your init scripts will fail to unmount the filesystem cleanly. +It is recommended to signal +.Nm +via a wrapper script. +.Pp +.Nm +does not clear utmp records. The version of +.Xr getty 8 +in ubase clears the utmp entries on the specified tty before it +spawns the given program, usually +.Xr login 1 . +.Pp +.Nm +does not restart +.Xr getty 8 +or interact with it in any way. You will need an external +respawning mechanism to restart it. +.Sh SEE ALSO +.Xr killall5 8 , +.Xr getty 8 +.Sh AUTHORS +The +.Nm +program was written by Dimitris Papastamos <sin@2f30.org>. diff --git a/sinit/sinit.c b/sinit/sinit.c @@ -0,0 +1,89 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/types.h> +#include <sys/wait.h> + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#define LEN(x) (sizeof (x) / sizeof *(x)) + +static void sigpoweroff(void); +static void sigreap(void); +static void sigreboot(void); +static void spawn(char *const []); + +static struct { + int sig; + void (*handler)(void); +} sigmap[] = { + { SIGUSR1, sigpoweroff }, + { SIGCHLD, sigreap }, + { SIGINT, sigreboot }, +}; + +#include "config.h" + +static sigset_t set; + +int +main(void) +{ + int sig; + size_t i; + + if (getpid() != 1) + return EXIT_FAILURE; + chdir("/"); + sigfillset(&set); + sigprocmask(SIG_BLOCK, &set, NULL); + spawn(rcinitcmd); + while (1) { + sigwait(&set, &sig); + for (i = 0; i < LEN(sigmap); i++) { + if (sigmap[i].sig == sig) { + sigmap[i].handler(); + break; + } + } + } + /* not reachable */ + return EXIT_SUCCESS; +} + +static void +sigpoweroff(void) +{ + spawn(rcpoweroffcmd); +} + +static void +sigreap(void) +{ + while (waitpid(-1, NULL, WNOHANG) > 0) + ; +} + +static void +sigreboot(void) +{ + spawn(rcrebootcmd); +} + +static void +spawn(char *const argv[]) +{ + pid_t pid; + + pid = fork(); + if (pid < 0) { + perror("fork"); + } else if (pid == 0) { + sigprocmask(SIG_UNBLOCK, &set, NULL); + setsid(); + execvp(argv[0], argv); + perror("execvp"); + _exit(EXIT_FAILURE); + } +} diff --git a/smdev/LICENSE b/smdev/LICENSE @@ -0,0 +1,22 @@ +MIT/X Consortium License + +© 2013 sin <sin@2f30.org> +© 2014 Hiltjo Posthuma <hiltjo@codemadness.org> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/smdev/Makefile b/smdev/Makefile @@ -0,0 +1,66 @@ +include config.mk + +.POSIX: +.SUFFIXES: .c .o + +LIB = \ + util/agetcwd.o \ + util/apathmax.o \ + util/dev.o \ + util/eprintf.o \ + util/estrtol.o \ + util/mkpath.o \ + util/recurse.o \ + util/strlcpy.o + +SRC = smdev.c + +OBJ = $(SRC:.c=.o) $(LIB) +BIN = $(SRC:.c=) +MAN = $(SRC:.c=.1) + +all: options binlib + +options: + @echo mdev build options: + @echo "CFLAGS = $(CFLAGS)" + @echo "LDFLAGS = $(LDFLAGS)" + @echo "CC = $(CC)" + +binlib: util.a + $(MAKE) bin + +bin: $(BIN) + +$(OBJ): config.h util.h config.mk + +config.h: + @echo creating $@ from config.def.h + @cp config.def.h $@ + +.o: + @echo LD $@ + @$(LD) -o $@ $< util.a $(LDFLAGS) + +.c.o: + @echo CC $< + @$(CC) -c -o $@ $< $(CFLAGS) + +util.a: $(LIB) + @echo AR $@ + @$(AR) -r -c $@ $(LIB) + @ranlib $@ + +install: all + @echo installing executable to $(DESTDIR)$(PREFIX)/bin + @mkdir -p $(DESTDIR)$(PREFIX)/bin + @cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin + @cd $(DESTDIR)$(PREFIX)/bin && chmod 755 $(BIN) + +uninstall: + @echo removing executable from $(DESTDIR)$(PREFIX)/bin + @cd $(DESTDIR)$(PREFIX)/bin && rm -f $(BIN) + +clean: + @echo cleaning + @rm -f $(BIN) $(OBJ) $(LIB) util.a diff --git a/smdev/README b/smdev/README @@ -0,0 +1,25 @@ +What is it? +=========== + +smdev is a simple program to manage device nodes. It is +mostly compatible with mdev but doesn't have all of its features. + +Building +======== + +You need to have the kernel headers available on your +system to build smdev. + +To build a statically linked smdev against musl-libc[0] +install the relevant cross-compiler[1], then install the kernel +headers into your cross-compiler prefix and finally run +the following: + +$ make CC=x86_64-musl-linux-gcc LDFLAGS=-static +$ x86_64-linux-musl-strip smdev + +On my system the above results in a 88kB statically linked +application. + +[0] http://www.musl-libc.org/ +[1] http://musl.codu.org/ diff --git a/smdev/TODO b/smdev/TODO @@ -0,0 +1,6 @@ +* remove memory allocations in the create/remove path +* grab latest recurse.c from sbase and rework code (remove agetcwd calls) +* use strlcpy()/strlcat() instead of snprintf() and check for path truncation +* populatedev() makes some weird assumptions, should be reworked +* simplify parsepath() +* rethink mdev compatibility diff --git a/smdev/arg.h b/smdev/arg.h @@ -0,0 +1,55 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef __ARG_H__ +#define __ARG_H__ + +extern char *argv0; + +#define USED(x) ((void)(x)) + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][1]\ + && argv[0][0] == '-';\ + argc--, argv++) {\ + char _argc;\ + char **_argv;\ + int brk;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk = 0, argv[0]++, _argv = argv;\ + argv[0][0] && !brk;\ + argv[0]++) {\ + if (_argv != argv)\ + break;\ + _argc = argv[0][0];\ + switch (_argc) + +#define ARGEND }\ + USED(_argc);\ + }\ + USED(argv);\ + USED(argc); + +#define ARGC() _argc + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif + diff --git a/smdev/bin/simevent b/smdev/bin/simevent @@ -0,0 +1,18 @@ +#!/bin/sh +# +# Simulate add/remove events by writing directly +# into the uevent files. + +if [ -z $1 ]; then + echo "usage: $(basename $0) add|remove" 2>&1 + exit 1 +fi + +ev=$1 +for i in $(find /sys/devices -type f); do + f=$(basename $i) + if [ "$f" = "dev" ]; then + d=$(dirname $i) + echo $ev > $d/uevent + fi +done diff --git a/smdev/config.def.h b/smdev/config.def.h @@ -0,0 +1,53 @@ +/* See LICENSE file for copyright and license details. */ +struct rule { + const char *devregex; + const char *user; + const char *group; + int mode; + const char *path; + const char *cmd; +} rules[] = { + { "null", "root", "root", 0666, NULL, NULL }, + { "zero", "root", "root", 0666, NULL, NULL }, + { "full", "root", "root", 0666, NULL, NULL }, + { "random", "root", "root", 0666, NULL, NULL }, + { "urandom", "root", "root", 0444, NULL, NULL }, + { "hwrandom", "root", "root", 0660, NULL, NULL }, + { "mem", "root", "root", 0640, NULL, NULL }, + { "kmem", "root", "root", 0640, NULL, NULL }, + { "port", "root", "root", 0640, NULL, NULL }, + { "console", "root", "tty", 0600, NULL, NULL }, + { "ptmx", "root", "tty", 0666, NULL, NULL }, + { "tty", "root", "tty", 0666, NULL, NULL }, + { "tty[0-9]", "root", "root", 0600, NULL, NULL }, + { "tty[0-9][0-9]","root", "tty", 0660, NULL, NULL }, + { "ttyS[0-9]*", "root", "tty", 0660, NULL, NULL, }, + { "pty.*", "root", "tty", 0660, NULL, NULL }, + { "vcs[0-9]*", "root", "tty", 0660, NULL, NULL }, + { "vcsa*[0-9]*", "root", "tty", 0660, NULL, NULL }, + { "sd[a-z].*", "root", "disk", 0660, NULL, NULL }, + { "sr[0-9]*", "root", "cdrom", 0660, NULL, "@ln -sf $DEVNAME /dev/cdrom" }, + { "ts[0-9]+", "root", "root", 0640, "=input/", NULL }, + { "input/.*", "root", "root", 0640, "=input/", NULL }, + { "dri/.*", "root", "video", 0660, "=dri/", NULL }, + { "snd/.*", "root", "audio", 0660, "=snd/", NULL }, + { "midi.*", "root", "audio", 0660, "=snd/", NULL }, + { "seq", "root", "audio", 0660, "=snd/", NULL }, + { "timer", "root", "audio", 0660, "=snd/", NULL }, + { "rtc[0-9]*", "root", "root", 0664, NULL, NULL }, + { "vbi[0-9]", "root", "video", 0660, NULL, NULL }, + { "video[0-9]", "root", "video", 0660, NULL, NULL }, + { "fuse", "root", "root", 0666, NULL, NULL }, + { ".*", "root", "root", 0660, NULL, NULL }, +}; + +/* Fill into this table if you want to rename the network interface + * identified by `mac' to `name'. By default no such renaming takes + * place. + */ +struct mac2name { + unsigned char mac[6]; + const char *name; +} mac2names[] = { + { .mac = { 0 }, .name = NULL } +}; diff --git a/smdev/config.h b/smdev/config.h @@ -0,0 +1,53 @@ +/* See LICENSE file for copyright and license details. */ +struct rule { + const char *devregex; + const char *user; + const char *group; + int mode; + const char *path; + const char *cmd; +} rules[] = { + { "null", "root", "root", 0666, NULL, NULL }, + { "zero", "root", "root", 0666, NULL, NULL }, + { "full", "root", "root", 0666, NULL, NULL }, + { "random", "root", "root", 0666, NULL, NULL }, + { "urandom", "root", "root", 0444, NULL, NULL }, + { "hwrandom", "root", "root", 0660, NULL, NULL }, + { "mem", "root", "root", 0640, NULL, NULL }, + { "kmem", "root", "root", 0640, NULL, NULL }, + { "port", "root", "root", 0640, NULL, NULL }, + { "console", "root", "tty", 0600, NULL, NULL }, + { "ptmx", "root", "tty", 0666, NULL, NULL }, + { "tty", "root", "tty", 0666, NULL, NULL }, + { "tty[0-9]", "root", "root", 0600, NULL, NULL }, + { "tty[0-9][0-9]","root", "tty", 0660, NULL, NULL }, + { "ttyS[0-9]*", "root", "tty", 0660, NULL, NULL, }, + { "pty.*", "root", "tty", 0660, NULL, NULL }, + { "vcs[0-9]*", "root", "tty", 0660, NULL, NULL }, + { "vcsa*[0-9]*", "root", "tty", 0660, NULL, NULL }, + { "sd[a-z].*", "root", "disk", 0660, NULL, NULL }, + { "sr[0-9]*", "root", "cdrom", 0660, NULL, "@ln -sf $DEVNAME /dev/cdrom" }, + { "ts[0-9]+", "root", "root", 0640, "=input/", NULL }, + { "input/.*", "root", "root", 0640, "=input/", NULL }, + { "dri/.*", "root", "video", 0660, "=dri/", NULL }, + { "snd/.*", "root", "audio", 0660, "=snd/", NULL }, + { "midi.*", "root", "audio", 0660, "=snd/", NULL }, + { "seq", "root", "audio", 0660, "=snd/", NULL }, + { "timer", "root", "audio", 0660, "=snd/", NULL }, + { "rtc[0-9]*", "root", "root", 0664, NULL, NULL }, + { "vbi[0-9]", "root", "video", 0660, NULL, NULL }, + { "video[0-9]", "root", "video", 0660, NULL, NULL }, + { "fuse", "root", "root", 0666, NULL, NULL }, + { ".*", "root", "root", 0660, NULL, NULL }, +}; + +/* Fill into this table if you want to rename the network interface + * identified by `mac' to `name'. By default no such renaming takes + * place. + */ +struct mac2name { + unsigned char mac[6]; + const char *name; +} mac2names[] = { + { .mac = { 0 }, .name = NULL } +}; diff --git a/smdev/config.mk b/smdev/config.mk @@ -0,0 +1,11 @@ +# smdev version +VERSION = 0.2.3 + +# paths +PREFIX = /usr/local + +#CC = musl-gcc +LD = $(CC) +CPPFLAGS = -D_BSD_SOURCE -D_GNU_SOURCE +CFLAGS = -std=c99 -Wall -pedantic $(CPPFLAGS) +LDFLAGS = -s diff --git a/smdev/mkpath.h b/smdev/mkpath.h @@ -0,0 +1,2 @@ +/* See LICENSE file for copyright and license details. */ +int mkpath(const char *, mode_t); diff --git a/smdev/smdev.c b/smdev/smdev.c @@ -0,0 +1,441 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <linux/sockios.h> +#include <linux/if_packet.h> +#include <net/if.h> +#include <netinet/in.h> +#include <ifaddrs.h> + +#include <errno.h> +#include <fcntl.h> +#include <grp.h> +#include <libgen.h> +#include <limits.h> +#include <pwd.h> +#include <regex.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "mkpath.h" +#include "util.h" + +enum action { + ADD_ACTION, + REMOVE_ACTION, + UNKNOWN_ACTION +}; + +struct event { + int minor; + int major; + enum action action; + char *devpath; + char *devname; + struct rule *rule; +}; + +/* Simple cache for regcomp() results */ +static struct pregentry { + regex_t preg; + int cached; +} pregcache[LEN(rules)]; + +/* The expanded/parsed path components of a rule */ +struct rulepath { + char path[PATH_MAX]; + char name[PATH_MAX]; +}; + +static int dohotplug(void); +static int matchrule(int, char *); +static void runrulecmd(struct rule *); +static void parsepath(struct rule *, struct rulepath *, const char *); +static int removedev(struct event *); +static int createdev(struct event *); +static int doevent(struct event *); +static int craftev(char *); +static void populatedev(const char *); +static int ifrename(void); + +static void +usage(void) +{ + eprintf("usage: %s [-s]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int sflag = 0; + int i; + + ARGBEGIN { + case 's': + sflag = 1; + break; + default: + usage(); + } ARGEND; + + umask(0); + if (sflag) { + recurse("/sys/devices", populatedev); + } else { + if (dohotplug() < 0) + eprintf("Environment not set up correctly for hotplugging\n"); + } + + for (i = 0; i < LEN(pregcache); i++) + if (pregcache[i].cached) + regfree(&pregcache[i].preg); + + if (ifrename() < 0) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} + +static enum action +mapaction(const char *action) +{ + if (strcmp(action, "add") == 0) + return ADD_ACTION; + if (strcmp(action, "remove") == 0) + return REMOVE_ACTION; + return UNKNOWN_ACTION; +} + +/* Handle hotplugging events */ +static int +dohotplug(void) +{ + char *minor, *major; + char *action; + char *devpath; + char *devname; + struct event ev; + + minor = getenv("MINOR"); + major = getenv("MAJOR"); + action = getenv("ACTION"); + devpath = getenv("DEVPATH"); + devname = getenv("DEVNAME"); + if (!minor || !major || !action || !devpath || !devname) + return -1; + + ev.minor = estrtol(minor, 10); + ev.major = estrtol(major, 10); + ev.action = mapaction(action); + ev.devpath = devpath; + ev.devname = devname; + return doevent(&ev); +} + +/* + * `ruleidx' indexes into the rules[] table. We assume + * pregcache[] is mapped 1-1 with the rules[] table. + */ +static int +matchrule(int ruleidx, char *devname) +{ + struct rule *rule = &rules[ruleidx]; + regex_t *preg; + regmatch_t pmatch; + int ret; + + if (!pregcache[ruleidx].cached) { + ret = regcomp(&pregcache[ruleidx].preg, + rule->devregex, REG_EXTENDED); + if (ret < 0) + eprintf("regcomp:"); + pregcache[ruleidx].cached = 1; + } + preg = &pregcache[ruleidx].preg; + + ret = regexec(preg, devname, 1, &pmatch, 0); + if (ret == REG_NOMATCH || pmatch.rm_so || + pmatch.rm_eo != strlen(devname)) + return -1; + return 0; +} + +static void +runrulecmd(struct rule *rule) +{ + if (rule->cmd) + system(&rule->cmd[1]); +} + +static void +parsepath(struct rule *rule, struct rulepath *rpath, + const char *devname) +{ + char buf[PATH_MAX], *path, *dirc; + const char *basedevname; + + if(!(basedevname = strrchr(devname, '/'))) + basedevname = devname; + + if (!rule->path) { + strlcpy(rpath->name, basedevname, sizeof(rpath->name)); + snprintf(rpath->path, sizeof(rpath->path), "/dev/%s", + rpath->name); + return; + } + + if (rule->path[0] != '=' && rule->path[0] != '>') + eprintf("Invalid path '%s'\n", rule->path); + + path = strdup(&rule->path[1]); + if (!path) + eprintf("strdup:"); + + /* No need to rename the device node */ + if (rule->path[strlen(rule->path) - 1] == '/') { + snprintf(rpath->path, sizeof(rpath->path), "/dev/%s%s", + path, basedevname); + strlcpy(rpath->name, basedevname, sizeof(rpath->name)); + free(path); + return; + } + + if (strchr(path, '/')) { + if (!(dirc = strdup(path))) + eprintf("strdup:"); + snprintf(buf, sizeof(buf), "/dev/%s", dirname(dirc)); + strlcpy(rpath->name, basename(path), sizeof(rpath->name)); + snprintf(rpath->path, sizeof(rpath->path), "%s/%s", buf, + rpath->name); + free(dirc); + } else { + strlcpy(rpath->name, path, sizeof(rpath->name)); + snprintf(rpath->path, sizeof(rpath->path), "/dev/%s", + rpath->name); + } + + free(path); +} + +static int +removedev(struct event *ev) +{ + struct rule *rule; + struct rulepath rpath; + char *ocwd; + char buf[PATH_MAX]; + + rule = ev->rule; + ocwd = agetcwd(); + + parsepath(rule, &rpath, ev->devname); + + if(rule->cmd) { + if (chdir("/dev") < 0) + eprintf("chdir /dev:"); + runrulecmd(rule); + } + + if (chdir(ocwd) < 0) + eprintf("chdir %s:", ocwd); + + free(ocwd); + + if (rule->path && rule->path[0] == '!') + return 0; + + /* Delete device node */ + unlink(rpath.path); + /* Delete symlink */ + if (rule->path && rule->path[0] == '>') { + snprintf(buf, sizeof(buf), "/dev/%s", ev->devname); + unlink(buf); + } + return 0; +} + +static int +createdev(struct event *ev) +{ + struct rule *rule; + struct rulepath rpath; + struct passwd *pw; + struct group *gr; + char *dirc, *ocwd; + char buf[BUFSIZ]; + int type; + + rule = ev->rule; + ocwd = agetcwd(); + + if (rule->path && rule->path[0] == '!') + goto runrule; + + snprintf(buf, sizeof(buf), "%d:%d", ev->major, ev->minor); + if ((type = devtype(buf)) < 0) + return -1; + + /* Parse path and create the directory tree */ + parsepath(rule, &rpath, ev->devname); + if (!(dirc = strdup(rpath.path))) + eprintf("strdup:"); + strlcpy(buf, dirname(dirc), sizeof(buf)); + free(dirc); + umask(022); + if (mkpath(buf, 0755) < 0) + eprintf("mkdir %s:", buf); + umask(0); + + if (mknod(rpath.path, rule->mode | type, + makedev(ev->major, ev->minor)) < 0 && + errno != EEXIST) + eprintf("mknod %s:", rpath.path); + + errno = 0; + pw = getpwnam(rule->user); + if (!pw) { + if (errno) + eprintf("getpwnam %s:", rule->user); + else + eprintf("getpwnam %s: no such user\n", + rule->user); + } + + errno = 0; + gr = getgrnam(rule->group); + if (!gr) { + if (errno) + eprintf("getgrnam %s:", rule->group); + else + eprintf("getgrnam %s: no such group\n", + rule->group); + } + + if (chown(rpath.path, pw->pw_uid, gr->gr_gid) < 0) + eprintf("chown %s:", rpath.path); + + if (chmod(rpath.path, rule->mode) < 0) + eprintf("chmod %s:", rpath.path); + + if (rule->path && rule->path[0] == '>') { + /* ev->devname is the original device name */ + snprintf(buf, sizeof(buf), "/dev/%s", ev->devname); + symlink(rpath.path, buf); + } + +runrule: + if(rule->cmd) { + if (chdir("/dev") < 0) + eprintf("chdir /dev:"); + runrulecmd(rule); + } + + if (chdir(ocwd) < 0) + eprintf("chdir %s:", ocwd); + + free(ocwd); + + return 0; +} + +/* Event dispatcher */ +static int +doevent(struct event *ev) +{ + int i; + + for (i = 0; i < LEN(rules); i++) { + if (matchrule(i, ev->devname) < 0) + continue; + ev->rule = &rules[i]; + switch (ev->action) { + case ADD_ACTION: + return createdev(ev); + case REMOVE_ACTION: + return removedev(ev); + default: + return 0; + } + } + return 0; +} + +/* Craft a fake event so the rest of the code can cope */ +static int +craftev(char *sysfspath) +{ + char path[PATH_MAX]; + char *devpath; + + devpath = sysfspath + strlen("/sys"); + snprintf(path, sizeof(path), "/sys%s/uevent", devpath); + + clearenv(); + setenv("ACTION", "add", 1); + setenv("DEVPATH", devpath, 1); + if(readuevent(path) < 0) + return -1; + return 0; +} + +static void +populatedev(const char *path) +{ + char *cwd; + + recurse(path, populatedev); + if (strcmp(path, "dev") == 0) { + cwd = agetcwd(); + if (!craftev(cwd)) + dohotplug(); + free(cwd); + } +} + +static int +ifrename(void) +{ + struct sockaddr_ll *sa; + struct ifaddrs *ifas, *ifa; + struct ifreq ifr; + int sd; + int i; + int r; + int ok = 0; + + sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sd < 0) + eprintf("socket:"); + r = getifaddrs(&ifas); + if (r < 0) + eprintf("getifaddrs:"); + for (ifa = ifas; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_flags & IFF_LOOPBACK) + continue; + if (ifa->ifa_addr->sa_family != AF_PACKET) + continue; + sa = (struct sockaddr_ll *)ifa->ifa_addr; + for (i = 0; mac2names[i].name; i++) { + if (memcmp(mac2names[i].mac, sa->sll_addr, 6) != 0) + continue; + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, + ifa->ifa_name, sizeof(ifr.ifr_name)); + strlcpy(ifr.ifr_newname, + mac2names[i].name, sizeof(ifr.ifr_newname)); + r = ioctl(sd, SIOCSIFNAME, &ifr); + if (r < 0) { + weprintf("SIOCSIFNAME:"); + ok = -1; + } + } + } + freeifaddrs(ifas); + close(sd); + return ok; +} diff --git a/smdev/util.h b/smdev/util.h @@ -0,0 +1,18 @@ +/* See LICENSE file for copyright and license details. */ +#include "arg.h" + +#define LEN(x) (sizeof (x) / sizeof *(x)) + +extern char *argv0; + +char *agetcwd(void); +void apathmax(char **, long *); +int readuevent(const char *); +int devtype(const char *); +void enprintf(int, const char *, ...); +void eprintf(const char *, ...); +void weprintf(const char *, ...); +long estrtol(const char *, int); +void recurse(const char *, void (*)(const char *)); +#undef strlcpy +size_t strlcpy(char *, const char *, size_t); diff --git a/smdev/util/agetcwd.c b/smdev/util/agetcwd.c @@ -0,0 +1,18 @@ +/* See LICENSE file for copyright and license details. */ +#include <unistd.h> + +#include "../util.h" + +char * +agetcwd(void) +{ + char *buf; + long size; + + apathmax(&buf, &size); + if(!getcwd(buf, size)) + eprintf("getcwd:"); + + return buf; +} + diff --git a/smdev/util/apathmax.c b/smdev/util/apathmax.c @@ -0,0 +1,25 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "../util.h" + +void +apathmax(char **p, long *size) +{ + errno = 0; + + if((*size = pathconf("/", _PC_PATH_MAX)) == -1) { + if(errno == 0) { + *size = BUFSIZ; + } else { + eprintf("pathconf:"); + } + } + + if(!(*p = malloc(*size))) + eprintf("malloc:"); +} + diff --git a/smdev/util/dev.c b/smdev/util/dev.c @@ -0,0 +1,55 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "../util.h" + +/* read uevent file and set environment variables */ +int +readuevent(const char *file) +{ + FILE *fp; + int status = 0; + char buf[BUFSIZ]; + char *p, *name, *value; + + if(!(fp = fopen(file, "r"))) + return -1; + while(!feof(fp)) { + fgets(buf, sizeof(buf) - 1, fp); + if(ferror(fp)) { + status = -2; + break; + } + if((p = strchr(buf, '\n'))) + *p = '\0'; + if(!(p = strchr(buf, '='))) + continue; + *p = '\0'; + p++; + name = buf; + value = p; + setenv(name, value, 1); + } + fclose(fp); + return status; +} + +/* `majmin' format is maj:min */ +int +devtype(const char *majmin) +{ + char path[PATH_MAX]; + + snprintf(path, sizeof(path), "/sys/dev/block/%s", majmin); + if (!access(path, F_OK)) + return S_IFBLK; + snprintf(path, sizeof(path), "/sys/dev/char/%s", majmin); + if (!access(path, F_OK)) + return S_IFCHR; + return -1; +} diff --git a/smdev/util/eprintf.c b/smdev/util/eprintf.c @@ -0,0 +1,59 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../util.h" + +char *argv0; + +static void venprintf(int, const char *, va_list); + +void +eprintf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + venprintf(EXIT_FAILURE, fmt, ap); + va_end(ap); +} + +void +enprintf(int status, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + venprintf(status, fmt, ap); + va_end(ap); +} + +void +venprintf(int status, const char *fmt, va_list ap) +{ + vfprintf(stderr, fmt, ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } + + exit(status); +} + +void +weprintf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } +} diff --git a/smdev/util/estrtol.c b/smdev/util/estrtol.c @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../util.h" + +long +estrtol(const char *s, int base) +{ + char *end; + long n; + + errno = 0; + n = strtol(s, &end, base); + if(*end != '\0') { + if(base == 0) + eprintf("%s: not an integer\n", s); + else + eprintf("%s: not a base %d integer\n", s, base); + } + if(errno != 0) + eprintf("%s:", s); + + return n; +} + diff --git a/smdev/util/mkpath.c b/smdev/util/mkpath.c @@ -0,0 +1,29 @@ +#include <sys/stat.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <limits.h> + +int +mkpath(const char *path, mode_t mode) +{ + char tmp[PATH_MAX]; + char *p = NULL; + size_t len; + + snprintf(tmp, sizeof(tmp),"%s",path); + len = strlen(tmp); + if(tmp[len - 1] == '/') + tmp[len - 1] = 0; + for(p = tmp + 1; *p; p++) + if(*p == '/') { + *p = 0; + if (mkdir(tmp, mode) < 0 && errno != EEXIST) + return -1; + *p = '/'; + } + if (mkdir(tmp, mode) < 0 && errno != EEXIST) + return -1; + return 0; +} + diff --git a/smdev/util/recurse.c b/smdev/util/recurse.c @@ -0,0 +1,40 @@ +/* See LICENSE file for copyright and license details. */ +#include <dirent.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "../util.h" + +void +recurse(const char *path, void (*fn)(const char *)) +{ + char *cwd; + struct dirent *d; + struct stat st; + DIR *dp; + + if(lstat(path, &st) == -1 || !S_ISDIR(st.st_mode)) { + return; + } else if(!(dp = opendir(path))) { + eprintf("opendir %s:", path); + } + + cwd = agetcwd(); + if(chdir(path) == -1) + eprintf("chdir %s:", path); + + while((d = readdir(dp))) { + if(strcmp(d->d_name, ".") && strcmp(d->d_name, "..")) + fn(d->d_name); + } + + closedir(dp); + if(chdir(cwd) == -1) + eprintf("chdir %s:", cwd); + + free(cwd); +} + diff --git a/smdev/util/strlcpy.c b/smdev/util/strlcpy.c @@ -0,0 +1,47 @@ +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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 <sys/types.h> +#include <string.h> +#include "../util.h" + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + return(s - src - 1); /* count does not include NUL */ +} diff --git a/std.mk b/std.mk @@ -0,0 +1,12 @@ +$(TARG): $(OBJ) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJ) $(LDLIBS) + +install: + mkdir -p $(DESTDIR)/$(PREFIX)/bin + cp -f $(TARG) $(DESTDIR)/$(PREFIX)/bin + mkdir -p $(DESTDIR)/$(MANPREFIX)/man1 + cp -f $(TARG).1 $(DESTDIR)/$(PREFIX)/man1 + +uninstall: + cd $(DESTDIR)/$(PREFIX)/bin && rm $(TARG) + cd $(DESTDIR)/$(MANPREFIX)/man1 && rm $(TARG).1 diff --git a/ubase/LICENSE b/ubase/LICENSE @@ -0,0 +1,33 @@ +MIT/X Consortium License + +© 2013-2015 Dimitris Papapastamos <sin@2f30.org> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +Authors/contributors include: + +© 2013 oblique <psyberbits@gmail.com> +© 2013 s-p-k <mr.dwts@gmail.com> +© 2013 Jakob Kramer <jakob.kramer@gmx.de> +© 2013 David Galos <galosd83@students.rowan.edu> +© 2014 Carlos J. Torres <vlaadbrain@gmail.com> +© 2014 Hiltjo Posthuma <hiltjo@codemadness.org> +© 2014 Laslo Hunhold <dev@frign.de> +© 2014 Roberto E. Vargas Caballero <k0ga@shike2.com> +© 2014 Jan Tatje <jan@jnt.io> diff --git a/ubase/Makefile b/ubase/Makefile @@ -0,0 +1,210 @@ +include config.mk + +.SUFFIXES: +.SUFFIXES: .o .c + +HDR = \ + arg.h \ + config.h \ + passwd.h \ + proc.h \ + queue.h \ + reboot.h \ + rtc.h \ + text.h \ + util.h + +LIBUTIL = libutil.a +LIBUTILSRC = \ + libutil/agetcwd.c \ + libutil/agetline.c \ + libutil/apathmax.c \ + libutil/concat.c \ + libutil/ealloc.c \ + libutil/eprintf.c \ + libutil/estrtol.c \ + libutil/estrtoul.c \ + libutil/explicit_bzero.c \ + libutil/passwd.c \ + libutil/proc.c \ + libutil/putword.c \ + libutil/recurse.c \ + libutil/strlcat.c \ + libutil/strlcpy.c \ + libutil/tty.c + +LIB = $(LIBUTIL) + +BIN = \ + chvt \ + clear \ + ctrlaltdel \ + dd \ + df \ + dmesg \ + eject \ + fallocate \ + free \ + freeramdisk \ + fsfreeze \ + getty \ + halt \ + hwclock \ + id \ + insmod \ + killall5 \ + last \ + lastlog \ + login \ + lsmod \ + lsusb \ + mesg \ + mknod \ + mkswap \ + mount \ + mountpoint \ + pagesize \ + passwd \ + pidof \ + pivot_root \ + ps \ + readahead \ + respawn \ + rmmod \ + stat \ + su \ + swaplabel \ + swapoff \ + swapon \ + switch_root \ + sysctl \ + truncate \ + umount \ + unshare \ + uptime \ + vtallow \ + watch \ + who + +MAN1 = \ + chvt.1 \ + clear.1 \ + dd.1 \ + df.1 \ + dmesg.1 \ + eject.1 \ + fallocate.1 \ + free.1 \ + id.1 \ + login.1 \ + mesg.1 \ + mknod.1 \ + mountpoint.1 \ + pagesize.1 \ + passwd.1 \ + pidof.1 \ + ps.1 \ + respawn.1 \ + stat.1 \ + su.1 \ + truncate.1 \ + unshare.1 \ + uptime.1 \ + vtallow.1 \ + watch.1 \ + who.1 + +MAN8 = \ + ctrlaltdel.8 \ + freeramdisk.8 \ + fsfreeze.8 \ + getty.8 \ + halt.8 \ + hwclock.8 \ + insmod.8 \ + killall5.8 \ + lastlog.8 \ + lsmod.8 \ + lsusb.8 \ + mkswap.8 \ + mount.8 \ + pivot_root.8 \ + readahead.8 \ + rmmod.8 \ + swaplabel.8 \ + swapoff.8 \ + swapon.8 \ + switch_root.8 \ + sysctl.8 \ + umount.8 + +LIBUTILOBJ = $(LIBUTILSRC:.c=.o) +OBJ = $(BIN:=.o) $(LIBUTILOBJ) +SRC = $(BIN:=.c) + +all: $(BIN) + +$(BIN): $(LIB) + +$(OBJ): $(HDR) config.mk + +config.h: + cp config.def.h $@ + +.o: + $(LD) $(LDFLAGS) -o $@ $< $(LIB) $(LDLIBS) + +.c.o: + $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -c $< + +$(LIBUTIL): $(LIBUTILOBJ) + $(AR) -r -c $@ $? + $(RANLIB) $@ + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin + cd $(DESTDIR)$(PREFIX)/bin && chmod 755 $(BIN) + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + for m in $(MAN1); do sed "s/VERSION/$(VERSION)/g" < "$$m" > $(DESTDIR)$(MANPREFIX)/man1/"$$m"; done + mkdir -p $(DESTDIR)$(MANPREFIX)/man8 + for m in $(MAN8); do sed "s/VERSION/$(VERSION)/g" < "$$m" > $(DESTDIR)$(MANPREFIX)/man8/"$$m"; done + cd $(DESTDIR)$(MANPREFIX)/man1 && chmod 644 $(MAN1) + cd $(DESTDIR)$(MANPREFIX)/man8 && chmod 644 $(MAN8) + +uninstall: + cd $(DESTDIR)$(PREFIX)/bin && rm -f $(BIN) + cd $(DESTDIR)$(MANPREFIX)/man1 && rm -f $(MAN1) + cd $(DESTDIR)$(MANPREFIX)/man8 && rm -f $(MAN8) + +dist: clean + mkdir -p ubase-$(VERSION) + cp -r LICENSE Makefile README TODO config.mk $(SRC) $(MAN1) $(MAN8) libutil $(HDR) config.def.h ubase-$(VERSION) + tar -cf ubase-$(VERSION).tar ubase-$(VERSION) + gzip ubase-$(VERSION).tar + rm -rf ubase-$(VERSION) + +ubase-box: $(LIB) $(SRC) + mkdir -p build + cp $(HDR) build + cp config.h build + for f in $(SRC); do sed "s/^main(/`basename $$f .c`_&/" < $$f > build/$$f; done + echo '#include <libgen.h>' > build/$@.c + echo '#include <stdio.h>' >> build/$@.c + echo '#include <stdlib.h>' >> build/$@.c + echo '#include <string.h>' >> build/$@.c + echo '#include "util.h"' >> build/$@.c + for f in $(SRC); do echo "int `basename $$f .c`_main(int, char **);" >> build/$@.c; done + echo 'int main(int argc, char *argv[]) { char *s = basename(argv[0]); if(!strcmp(s,"ubase-box")) { argc--; argv++; s = basename(argv[0]); } if(0) ;' >> build/$@.c + for f in $(SRC); do echo "else if(!strcmp(s, \"`basename $$f .c`\")) return `basename $$f .c`_main(argc, argv);" >> build/$@.c; done + echo 'else {' >> build/$@.c + for f in $(SRC); do echo "printf(\"`basename $$f .c`\"); putchar(' ');" >> build/$@.c; done + echo "putchar(0xa); }; return 0; }" >> build/$@.c + $(LD) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ build/*.c $(LIB) $(LDLIBS) + rm -r build + +clean: + rm -f $(BIN) $(OBJ) $(LIB) ubase-box ubase-$(VERSION).tar.gz + +.PHONY: + all install uninstall dist ubase-box clean diff --git a/ubase/README b/ubase/README @@ -0,0 +1,29 @@ +ubase - suckless linux base utils +================================= + +ubase is a collection of tools similar in spirit to util-linux but +much simpler. + +The complement of ubase is sbase[1] which mostly follows POSIX and +provides all the portable tools. Together they are intended to form a +base system similar to busybox but much smaller and suckless. + +Building +-------- + +To build ubase, simply type make. You may have to fiddle with +config.mk and config.h depending on your system. + +You can also build ubase-box, which generates a single binary +containing all the required tools. You can then symlink the +individual tools to ubase-box or run them directly as follows: + + ubase-box cmd [args] + +Ideally you will want to statically link ubase. We highly recommend +using musl-libc[2]. + +ubase is known to compile with gcc, clang and tcc. + +[1] http://git.suckless.org/sbase/ +[2] http://www.musl-libc.org/ diff --git a/ubase/TODO b/ubase/TODO @@ -0,0 +1,35 @@ +Tools +===== + +vmstat +top +Better ps support +losetup +lspci +mkswap [-L] +adduser +addgroup +rmuser +rmgroup +ifconfig +partprobe +rfkill +taskset +acpi +blkid +lsattr +pmap +pwdx +setcap +getcap +capchroot +fakeroot +less or pg +ionice +fuser + +Misc +==== + +Beautify passwd(1). +last(1) manpage. diff --git a/ubase/arg.h b/ubase/arg.h @@ -0,0 +1,63 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][1]\ + && argv[0][0] == '-';\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +/* Handles obsolete -NUM syntax */ +#define ARGNUM case '0':\ + case '1':\ + case '2':\ + case '3':\ + case '4':\ + case '5':\ + case '6':\ + case '7':\ + case '8':\ + case '9' + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define ARGNUMF(base) (brk_ = 1, estrtol(argv[0], (base))) + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/ubase/chvt.1 b/ubase/chvt.1 @@ -0,0 +1,8 @@ +.TH CHVT 1 ubase-VERSION +.SH NAME +\fBchvt\fR - Change foreground virtual terminal +.SH SYNOPSIS +\fBchvt\fI N +.SH DESCRIPTION +\fBchvt\fR brings /dev/ttyN to the foreground. This has the +same effect as Ctrl-Alt-FN. diff --git a/ubase/chvt.c b/ubase/chvt.c @@ -0,0 +1,61 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/ioctl.h> +#include <sys/types.h> + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +#define KDGKBTYPE 0x4B33 /* get keyboard type */ + +#define VT_ACTIVATE 0x5606 /* make vt active */ +#define VT_WAITACTIVE 0x5607 /* wait for vt active */ + +static char *vts[] = { + "/proc/self/fd/0", + "/dev/console", + "/dev/tty", + "/dev/tty0", +}; + +static void +usage(void) +{ + eprintf("usage: chvt N\n"); +} + +int +main(int argc, char *argv[]) +{ + unsigned int n, i; + int fd; + char c; + + if (argc != 2 || strspn(argv[1], "1234567890") != strlen(argv[1])) + usage(); + + n = estrtol(argv[1], 10); + for (i = 0; i < LEN(vts); i++) { + fd = open(vts[i], O_RDONLY); + if (fd < 0) + continue; + c = 0; + if (ioctl(fd, KDGKBTYPE, &c) == 0) + goto VTfound; + close(fd); + } + + eprintf("couldn't find a console.\n"); +VTfound: + if (ioctl(fd, VT_ACTIVATE, n) == -1) + eprintf("VT_ACTIVATE %d:", n); + if (ioctl(fd, VT_WAITACTIVE, n) == -1) + eprintf("VT_WAITACTIVE %d:", n); + close(fd); + + return 0; +} diff --git a/ubase/clear.1 b/ubase/clear.1 @@ -0,0 +1,3 @@ +.TH CLEAR 1 ubase-VERSION +.SH NAME +\fBclear\fR - Clear the screen diff --git a/ubase/clear.c b/ubase/clear.c @@ -0,0 +1,10 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> + +int +main(void) +{ + printf("\x1b[2J\x1b[H"); + return 0; +} diff --git a/ubase/config.def.h b/ubase/config.def.h @@ -0,0 +1,11 @@ +/* See LICENSE file for copyright and license details. */ + +#define ENV_SUPATH "/bin" +#define ENV_PATH "/bin" +#define PW_CIPHER "$6$" /* SHA-512 */ +#undef UTMP_PATH +#define UTMP_PATH "/var/run/utmp" +#undef BTMP_PATH +#define BTMP_PATH "/var/log/btmp" +#undef WTMP_PATH +#define WTMP_PATH "/var/log/wtmp" diff --git a/ubase/config.h b/ubase/config.h @@ -0,0 +1,11 @@ +/* See LICENSE file for copyright and license details. */ + +#define ENV_SUPATH "/bin" +#define ENV_PATH "/bin" +#define PW_CIPHER "$6$" /* SHA-512 */ +#undef UTMP_PATH +#define UTMP_PATH "/var/run/utmp" +#undef BTMP_PATH +#define BTMP_PATH "/var/log/btmp" +#undef WTMP_PATH +#define WTMP_PATH "/var/log/wtmp" diff --git a/ubase/config.mk b/ubase/config.mk @@ -0,0 +1,16 @@ +# ubase version +VERSION = 0.1 + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +CC = cc +LD = $(CC) +AR = ar +RANLIB = ranlib + +CPPFLAGS = -D_FILE_OFFSET_BITS=64 -D_XOPEN_SOURCE=700 -D_GNU_SOURCE +CFLAGS = -std=c99 -Wall -Wextra +LDLIBS = -lcrypt +LDFLAGS = -s diff --git a/ubase/ctrlaltdel.8 b/ubase/ctrlaltdel.8 @@ -0,0 +1,20 @@ +.TH CTRLALTDEL 8 ubase-VERSION +.SH NAME +\fBctrlaltdel\fR - Set the function of Ctrl-Alt-Del combination +.SH SYNOPSIS +\fBctrlaltdel\fR [\fB-hs\fR] +.SH DESCRIPTION +Based on examination of the linux/kernel/sys.c code, it is clear that +there are two supported functions that the Ctrl-Alt-Del sequence can +perform: a hard reset, which immediately reboots the computer without +calling sync(2) and without any other preparation; and a soft reset, +which sends the SIGINT (interrupt) signal to the init process (this is +always the process with PID 1). If this option is used, the init(8) +program must support this feature. +.SH OPTIONS +.TP +\fB-h\fR +Perform a hard reset +.TP +\fB-s\fR +Perform a soft reset diff --git a/ubase/ctrlaltdel.c b/ubase/ctrlaltdel.c @@ -0,0 +1,44 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/syscall.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "reboot.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-hs]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int hflag = 0; + int sflag = 0; + int cmd; + + ARGBEGIN { + case 'h': + hflag = 1; + break; + case 's': + sflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc > 0 || (hflag ^ sflag) == 0) + usage(); + + cmd = hflag ? LINUX_REBOOT_CMD_CAD_ON : LINUX_REBOOT_CMD_CAD_OFF; + + if (syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, + LINUX_REBOOT_MAGIC2, cmd, NULL) < 0) + eprintf("reboot:"); + return 0; +} diff --git a/ubase/dd.1 b/ubase/dd.1 @@ -0,0 +1,45 @@ +.TH DD 1 ubase-VERSION +.SH NAME +\fBdd\fR - Copy a file +.SH SYNOPSIS +\fBdd\fR [\fIoperand...\fR] +.SH DESCRIPTION +\fBdd\fR copies the standard input to the standard output. By default input +data is read and written in 64kB blocks. When finished, \fBdd\fR displays the +number of records read and written as well as the total number of bytes copied. +\fBdd\fR syncs the filesystem once it is done copying. If you want +to disable that use the \fInosync\fR option. +.SH OPERANDS +.TP +if=\fIfile\fR +Read input from \fIfile\fR instead of the standard input. +.TP +of=\fIfile\fR +Write output to \fIfile\fR instead of the standard output. If an initial +portion of the output \fIfile\fR is skipped using the seek operand, the output +file is truncated at that point. +.TP +bs[=\fIN\fR] +If bs is not specified, the default blocksize is 64kB. If bs is specified +without setting it to a specific value then an optimal value between the source +and target filesystem will be selected. If this process fails it will fallback +to the system's pagesize. Adjust \fIN\fR to set the block size of the transfers +in bytes. +.TP +seek=\fIN\fR +Seek \fIN\fR blocks from the beginning of the output before copying. +.TP +skip=\fIN\fR +Skip \fIN\fR blocks from the beginning of the input before copying. +.TP +count=\fIN\fR +Copy only \fIN\fR input blocks. +.TP +direct +Use direct I/O for data. +.TP +quiet +Enable quiet output. +.TP +nosync +Do not sync the filesystem once we are done copying. diff --git a/ubase/dd.c b/ubase/dd.c @@ -0,0 +1,297 @@ +/* (C) 2011-2012 Sebastian Krahmer all rights reserved. + * + * Optimized dd, to speed up backups etc. + * + * Permission has been granted to release this code under MIT/X. + * The original code is at https://github.com/stealth/odd. This + * version of the code has been modified by sin@2f30.org. + */ +#include <sys/ioctl.h> +#include <sys/mount.h> +#include <sys/select.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/vfs.h> + +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "util.h" + +struct dd_config { + const char *in, *out; + uint64_t skip, seek, count, b_in, b_out, rec_in, rec_out; + off_t fsize; + blksize_t bs; + char quiet, nosync, direct; + int saved_errno; + time_t t_start, t_end; +}; + +static int sigint = 0; + +static int +prepare_copy(struct dd_config *ddc, int *ifd, int *ofd) +{ + struct stat st; + int fli = O_RDONLY|O_LARGEFILE|O_NOCTTY, flo = O_WRONLY|O_LARGEFILE|O_NOATIME|O_NOCTTY; + uid_t euid = 0; + long pagesize; + + if (ddc->direct) { + fli |= O_DIRECT; + flo |= O_DIRECT; + } + + if (stat(ddc->in, &st) < 0) { + ddc->saved_errno = errno; + return -1; + } + + euid = geteuid(); + + if (!euid || st.st_uid == euid) + fli |= O_NOATIME; + + if ((*ifd = open(ddc->in, fli)) < 0) { + ddc->saved_errno = errno; + return -1; + } + + ddc->fsize = st.st_size; + + /* If "bsize" is not given, use optimum of both FS' */ + if (!ddc->bs) { + struct statfs fst; + memset(&fst, 0, sizeof(fst)); + pagesize = sysconf(_SC_PAGESIZE); + if (pagesize <= 0) + pagesize = 0x1000; + if (statfs(ddc->out, &fst) < 0 || fst.f_bsize == 0) + fst.f_bsize = pagesize; + if ((unsigned long)fst.f_bsize > (unsigned long)st.st_blksize) + ddc->bs = fst.f_bsize; + else + ddc->bs = st.st_blksize; + if (ddc->bs == 0) + ddc->bs = pagesize; + } + + /* check if device or regular file */ + if (!S_ISREG(st.st_mode)) { + if (S_ISBLK(st.st_mode)) { + if (ioctl(*ifd, BLKGETSIZE64, &ddc->fsize) < 0) { + ddc->saved_errno = errno; + close(*ifd); + return -1; + } + } else { + ddc->fsize = (off_t)-1; + if (ddc->count) + ddc->fsize = ddc->count*ddc->bs; + } + } + + /* skip and seek are in block items */ + ddc->skip *= ddc->bs; + ddc->seek *= ddc->bs; + + /* skip more bytes than are inside source file? */ + if (ddc->fsize != (off_t)-1 && ddc->skip >= (uint64_t)ddc->fsize) { + ddc->saved_errno = EINVAL; + close(*ifd); + return -1; + } + + if (!ddc->seek) + flo |= O_CREAT|O_TRUNC; + + if ((*ofd = open(ddc->out, flo, st.st_mode)) < 0) { + ddc->saved_errno = errno; + close(*ifd); + return -1; + } + + lseek(*ifd, ddc->skip, SEEK_SET); + lseek(*ofd, ddc->seek, SEEK_SET); + posix_fadvise(*ifd, ddc->skip, 0, POSIX_FADV_SEQUENTIAL); + posix_fadvise(*ofd, 0, 0, POSIX_FADV_DONTNEED); + + /* count is in block items too */ + ddc->count *= ddc->bs; + + /* If no count is given, its the filesize minus skip offset */ + if (ddc->count == 0) + ddc->count = ddc->fsize - ddc->skip; + + return 0; +} + +static int +copy_splice(struct dd_config *ddc) +{ + int ifd, ofd, p[2] = {-1, -1}; + ssize_t r = 0; + size_t n = 0; + fd_set rfd, wfd; + + if (prepare_copy(ddc, &ifd, &ofd) < 0) + return -1; + if (pipe(p) < 0) { + ddc->saved_errno = errno; + close(ifd); close(ofd); + close(p[0]); close(p[1]); + return -1; + } +#ifdef F_SETPIPE_SZ + for (n = 29; n >= 20; --n) { + if (fcntl(p[0], F_SETPIPE_SZ, 1<<n) != -1) + break; + } +#endif + n = ddc->bs; + for (;ddc->b_out != ddc->count && !sigint;) { + FD_ZERO(&rfd); + FD_ZERO(&wfd); + FD_SET(ifd, &rfd); + FD_SET(ofd, &wfd); + r = select(ifd > ofd ? ifd + 1 : ofd + 1, &rfd, &wfd, NULL, NULL); + if (r < 0) { + ddc->saved_errno = errno; + break; + } + if (FD_ISSET(ifd, &rfd) == 1 && FD_ISSET(ofd, &wfd) == 1) { + if (n > ddc->count - ddc->b_out) + n = ddc->count - ddc->b_out; + r = splice(ifd, NULL, p[1], NULL, n, SPLICE_F_MORE); + if (r <= 0) { + ddc->saved_errno = errno; + break; + } + ++ddc->rec_in; + r = splice(p[0], NULL, ofd, NULL, r, SPLICE_F_MORE); + if (r <= 0) { + ddc->saved_errno = errno; + break; + } + ddc->b_out += r; + ++ddc->rec_out; + } + } + close(ifd); + close(ofd); + close(p[0]); + close(p[1]); + if (r < 0) + return -1; + return 0; +} + +static int +copy(struct dd_config *ddc) +{ + int r = 0; + + ddc->t_start = time(NULL); + + r = copy_splice(ddc); + ddc->t_end = time(NULL); + + /* avoid div by zero */ + if (ddc->t_start == ddc->t_end) + ++ddc->t_end; + return r; +} + +static void +usage(void) +{ + eprintf("usage: %s [-h] [if=infile] [of=outfile] [bs[=N]] [seek=N] [skip=N] [count=N] [direct] [quiet] [nosync]\n", argv0); +} + +static void +print_stat(const struct dd_config *ddc) +{ + if (ddc->quiet) + return; + + fprintf(stderr, "%"PRIu64" records in\n", ddc->rec_in); + fprintf(stderr, "%"PRIu64" records out\n", ddc->rec_out); + fprintf(stderr, "%"PRIu64" bytes (%"PRIu64" MB) copied", ddc->b_out, + ddc->b_out/(1<<20)); + fprintf(stderr, ", %lu s, %f MB/s\n", + (unsigned long)ddc->t_end - ddc->t_start, + ((double)(ddc->b_out/(1<<20)))/(ddc->t_end - ddc->t_start)); +} + +static void +sig_int(int unused) +{ + (void) unused; + fprintf(stderr, "SIGINT! Aborting ...\n"); + sigint = 1; +} + +int +main(int argc, char *argv[]) +{ + int i = 0; + char buf[1024]; + struct dd_config config; + + argv0 = argv[0]; + memset(&config, 0, sizeof(config)); + config.bs = 1<<16; + config.in = "/dev/stdin"; + config.out = "/dev/stdout"; + + /* emulate 'dd' argument parsing */ + for (i = 1; i < argc; ++i) { + memset(buf, 0, sizeof(buf)); + if (sscanf(argv[i], "if=%1023s", buf) == 1) + config.in = strdup(buf); + else if (sscanf(argv[i], "of=%1023s", buf) == 1) + config.out = strdup(buf); + else if (sscanf(argv[i], "skip=%1023s", buf) == 1) + config.skip = estrtoul(buf, 0); + else if (sscanf(argv[i], "seek=%1023s", buf) == 1) + config.seek = estrtoul(buf, 0); + else if (sscanf(argv[i], "count=%1023s", buf) == 1) + config.count = estrtoul(buf, 0); + else if (strcmp(argv[i], "direct") == 0) + config.direct = 1; + else if (sscanf(argv[i], "bs=%1023s", buf) == 1) + config.bs = estrtoul(buf, 0); + else if (strcmp(argv[i], "bs") == 0) + config.bs = 0; + else if (strcmp(argv[i], "quiet") == 0) + config.quiet = 1; + else if (strcmp(argv[i], "nosync") == 0) + config.nosync = 1; + else if (strcmp(argv[i], "-h") == 0) + usage(); + } + + if (!config.in || !config.out) + usage(); + + signal(SIGPIPE, SIG_IGN); + signal(SIGINT, sig_int); + + if (copy(&config) < 0) + weprintf("copy:"); + print_stat(&config); + + if (config.nosync == 0) + sync(); sync(); + return config.saved_errno; +} diff --git a/ubase/df.1 b/ubase/df.1 @@ -0,0 +1,23 @@ +.TH DF 1 ubase-VERSION +.SH NAME +\fBdf\fR - Show file system usage +.SH SYNOPSIS +\fBdf\fR [\fB-ahsi\fR] +.SH DESCRIPTION +\fBdf\fR displays the amount of disk space available on a file system. +If no arguments are given, df shows all the file systems using 512-byte +blocks. +.SH OPTIONS +.TP +\fB-a\fR +Show all file systems including dummy ones. This is the default +option. +.TP +\fB-h\fR +Not implemented. +.TP +\fB-s\fR +Not implemented. +.TP +\fB-i\fR +Not implemented. diff --git a/ubase/df.c b/ubase/df.c @@ -0,0 +1,136 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/statvfs.h> + +#include <mntent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +static long blksize = 512; +static int aflag = 0; +static int hflag = 0; +static int kflag = 0; + +static void mnt_show(const char *fsname, const char *dir); + +static void +usage(void) +{ + eprintf("usage: %s [-a]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct mntent *me = NULL; + FILE *fp; + + ARGBEGIN { + case 'a': + aflag = 1; + break; + case 'h': + hflag = 1; + kflag = 0; + break; + case 'k': + kflag = 1; + hflag = 0; + blksize = 1024; + break; + case 's': + case 'i': + eprintf("not implemented\n"); + default: + usage(); + } ARGEND; + + if (hflag) + printf("Filesystem Size Used " + "Avail Capacity Mounted on\n"); + else + printf("Filesystem %ld-blocks Used " + "Avail Capacity Mounted on\n", blksize); + + fp = setmntent("/proc/mounts", "r"); + if (!fp) + eprintf("setmntent %s:", "/proc/mounts"); + while ((me = getmntent(fp)) != NULL) { + if (aflag == 0) + if (strcmp(me->mnt_type, "rootfs") == 0) + continue; + mnt_show(me->mnt_fsname, me->mnt_dir); + } + endmntent(fp); + + return 0; +} + +#define CALC_POWER(n, power, base, i) do { \ + while (n > power) { \ + power = power * base; \ + i++; \ + } \ +} while(0) + +static void +print_human( + const char *fsname, + unsigned long long total, + unsigned long long used, + unsigned long long avail, + int capacity, + const char *dir) +{ + long base = 1024; + unsigned long long power_total = base; + unsigned long long power_used = base; + unsigned long long power_avail = base; + char postfixes[] = {'B', 'K', 'M', 'G', 'T', 'P', 'E'}; + int i = 0, j = 0, k = 0; + + total = total * blksize; + used = used * blksize; + avail = avail * blksize; + + CALC_POWER(total, power_total, base, i); + CALC_POWER(used, power_used, base, j); + CALC_POWER(avail, power_avail, base, k); + + total = i ? total / (power_total / base) : total; + used = j ? used / (power_used / base) : used; + avail = k ? avail / (power_avail / base) : avail; + printf("%-12s %9llu%c %9llu%c %9llu%c %7d%% %s\n", + fsname, total, postfixes[i], used, postfixes[j], + avail, postfixes[k], capacity, dir); +} + +static void +mnt_show(const char *fsname, const char *dir) +{ + struct statvfs s; + unsigned long long total, used, avail; + int capacity = 0; + int bs; + + statvfs(dir, &s); + + bs = s.f_frsize / blksize; + total = s.f_blocks * bs; + avail = s.f_bfree * bs; + used = total - avail; + + if (used + avail) { + capacity = (used * 100) / (used + avail); + if (used * 100 != capacity * (used + avail)) + capacity++; + } + + if (hflag) + print_human(fsname, total, used, avail, capacity, dir); + else + printf("%-12s %9llu %9llu %9llu %7d%% %s\n", + fsname, total, used, avail, capacity, dir); +} diff --git a/ubase/dmesg.1 b/ubase/dmesg.1 @@ -0,0 +1,22 @@ +.TH DMESG 1 ubase-VERSION +.SH NAME +\fBdmesg\fR - Print or control the kernel ring buffer +.SH SYNOPSIS +\fBdmesg\fR [\fB-Ccr\fR] [\fB-n\fR \fIlevel\fR] +.SH DESCRIPTION +\fBdmesg\fR examines or controls the kernel ring buffer. By default +it reads all the messages from the kernel ring buffer and prints them +on stdout. +.SH OPTIONS +.TP +\fB-C\fR +Clear the ring buffer. +.TP +\fB-c\fR +Clear the ring buffer after printing its contents. +.TP +\fB-n\fR +Set the console \fIlevel\fR. +.TP +\fB-r\fR +Print the raw message buffer. diff --git a/ubase/dmesg.c b/ubase/dmesg.c @@ -0,0 +1,83 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/klog.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static void dmesg_show(const void *buf, size_t n); + +enum { + SYSLOG_ACTION_READ_ALL = 3, + SYSLOG_ACTION_CLEAR = 5, + SYSLOG_ACTION_CONSOLE_LEVEL = 8, + SYSLOG_ACTION_SIZE_BUFFER = 10 +}; + +static void +usage(void) +{ + eprintf("usage: [-Ccr] [-n level] %s\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int n; + char *buf; + int cflag = 0; + long level; + + ARGBEGIN { + case 'C': + if (klogctl(SYSLOG_ACTION_CLEAR, NULL, 0) < 0) + eprintf("klogctl:"); + return 0; + case 'c': + cflag = 1; + break; + case 'r': + break; + case 'n': + level = estrtol(EARGF(usage()), 10); + if (klogctl(SYSLOG_ACTION_CONSOLE_LEVEL, NULL, level) < 0) + eprintf("klogctl:"); + return 0; + default: + usage(); + } ARGEND; + + n = klogctl(SYSLOG_ACTION_SIZE_BUFFER, NULL, 0); + if (n < 0) + eprintf("klogctl:"); + + buf = emalloc(n); + + n = klogctl(SYSLOG_ACTION_READ_ALL, buf, n); + if (n < 0) + eprintf("klogctl:"); + + dmesg_show(buf, n); + + if (cflag && klogctl(SYSLOG_ACTION_CLEAR, NULL, 0) < 0) + eprintf("klogctl:"); + + free(buf); + return 0; +} + +static void +dmesg_show(const void *buf, size_t n) +{ + const char *p = buf; + ssize_t r; + + r = write(1, p, n); + if (r < 0) + eprintf("write:"); + if (r > 0 && p[r - 1] != '\n') + putchar('\n'); +} diff --git a/ubase/eject.1 b/ubase/eject.1 @@ -0,0 +1,12 @@ +.TH EJECT 1 ubase-VERSION +.SH NAME +\fBeject\fR - Eject removable media +.SH SYNOPSIS +\fBeject\fR [\fB-t\fR] +.SH DESCRIPTION +\fBeject\fR allows the CD-ROM tray to be opened or closed under software +control. If no arguments are given, the CD-ROM tray is opened. +.SH OPTIONS +.TP +\fB-t\fR +If supported, close the CD-ROM tray. diff --git a/ubase/eject.c b/ubase/eject.c @@ -0,0 +1,56 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +enum { + CDROM_EJECT = 0x5309, + CDROM_CLOSE_TRAY = 0x5319, +}; + +static void +usage(void) +{ + eprintf("usage: %s [-t] [devname]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int fd, out; + char *cdrom = "/dev/sr0"; + int tflag = 0; + + ARGBEGIN { + case 't': + tflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc > 1) + usage(); + else if (argc == 1) + cdrom = argv[0]; + + fd = open(cdrom, O_RDONLY | O_NONBLOCK); + if (fd < 0) + eprintf("open %s:", cdrom); + if (tflag) { + if (ioctl(fd, CDROM_CLOSE_TRAY, &out) < 0) + eprintf("ioctl:"); + } else { + if (ioctl(fd, CDROM_EJECT, &out) < 0) + eprintf("ioctl:"); + } + close(fd); + return 0; +} diff --git a/ubase/fallocate.1 b/ubase/fallocate.1 @@ -0,0 +1,20 @@ +.TH FALLOCATE 1 ubase-VERSION +.SH NAME +\fBfallocate\fR - Preallocate blocks to a file +.SH SYNOPSIS +\fBfallocate\fR [\fB-o\fI offset\fR] \fB-l\fR \fIlength file\fR +.SH DESCRIPTION +\fBfallocate\fR preallocates blocks to a file. Only certain filesystems +support the fallocate system call. This is a very fast operation to allocate +uninitialized blocks in a file without doing any IO. +As of the Linux kernel v2.6.31, the fallocate system call is supported +by the btrfs, ext4, ocfs2, and xfs filesystems. +.SH OPTIONS +.TP +\fB-o\fR +Specifies the beginning offset of the allocation, in bytes. +.TP +\fB-l\fR +Specifies the length of the allocation, in bytes. +.SH SEE ALSO +fallocate(2) diff --git a/ubase/fallocate.c b/ubase/fallocate.c @@ -0,0 +1,46 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-o offset] -l length file\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int fd; + off_t size = 0, offset = 0; + + ARGBEGIN { + case 'l': + size = estrtol(EARGF(usage()), 10); + break; + case 'o': + offset = estrtol(EARGF(usage()), 10); + break; + default: + usage(); + } ARGEND; + + if (argc != 1 || !size) + usage(); + + fd = open(argv[0], O_RDWR | O_CREAT, 0644); + if (fd < 0) + eprintf("open %s:", argv[0]); + + if (posix_fallocate(fd, offset, size) < 0) + eprintf("posix_fallocate:"); + + close(fd); + return 0; +} diff --git a/ubase/free.1 b/ubase/free.1 @@ -0,0 +1,21 @@ +.TH FREE 1 ubase-VERSION +.SH NAME +\fBfree\fR - Display amount of free and used memory in the system +.SH SYNOPSIS +\fBfree\fR [\fB-bkmg\fR] +.SH DESCRIPTION +\fBfree\fR displays the total amount of free and used physical and swap +memory in the system, as well as the buffers used by the kernel. +.SH OPTIONS +.TP +\fB-b\fR +Display the amount of memory in bytes. This is the default. +.TP +\fB-k\fR +Display the amount of memory in kilobytes. +.TP +\fB-m\fR +Display the amount of memory in megabytes. +.TP +\fB-g\fR +Display the amount of memory in gigabytes. diff --git a/ubase/free.c b/ubase/free.c @@ -0,0 +1,72 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/sysinfo.h> + +#include <stdio.h> +#include <stdlib.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-bkmg]\n", argv0); +} + +static unsigned int mem_unit = 1; +static unsigned int unit_shift; + +static unsigned long long +scale(unsigned long long v) +{ + return (v * mem_unit) >> unit_shift; +} + +int +main(int argc, char *argv[]) +{ + struct sysinfo info; + + if (sysinfo(&info) < 0) + eprintf("sysinfo:"); + mem_unit = info.mem_unit ? info.mem_unit : 1; + + ARGBEGIN { + case 'b': + unit_shift = 0; + break; + case 'k': + unit_shift = 10; + break; + case 'm': + unit_shift = 20; + break; + case 'g': + unit_shift = 30; + break; + default: + usage(); + } ARGEND; + + printf(" %13s%13s%13s%13s%13s\n", + "total", + "used", + "free", + "shared", "buffers"); + printf("Mem: "); + printf("%13llu%13llu%13llu%13llu%13llu\n", + scale(info.totalram), + scale(info.totalram - info.freeram), + scale(info.freeram), + scale(info.sharedram), + scale(info.bufferram)); + printf("-/+ buffers/cache:"); + printf("%13llu%13llu\n", + scale(info.totalram - info.freeram - info.bufferram), + scale(info.freeram + info.bufferram)); + printf("Swap:"); + printf("%13llu%13llu%13llu\n", + scale(info.totalswap), + scale(info.totalswap - info.freeswap), + scale(info.freeswap)); + return 0; +} diff --git a/ubase/freeramdisk.8 b/ubase/freeramdisk.8 @@ -0,0 +1,8 @@ +.TH FREERAMDISK 8 ubase-VERSION +.SH NAME +\fBfreeramdisk\fR - Free memory used by the loadlin ramdisk +.SH SYNOPSIS +\fBfreeramdisk\fR +.SH DESCRIPTION +\fBfreeramdisk\fR frees the memory that is used by the ramdisk. +It uses the \fI/dev/ram\fR device node. diff --git a/ubase/freeramdisk.c b/ubase/freeramdisk.c @@ -0,0 +1,39 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/ioctl.h> +#include <sys/mount.h> +#include <sys/types.h> + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *dev = "/dev/ram"; + int fd; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc != 0) + usage(); + + if ((fd = open(dev, O_RDWR)) < 0) + eprintf("open: %s:", dev); + if (ioctl(fd, BLKFLSBUF, dev) < 0) + eprintf("BLKFLSBUF %s:", dev); + close(fd); + return 0; +} diff --git a/ubase/fsfreeze.8 b/ubase/fsfreeze.8 @@ -0,0 +1,24 @@ +.TH FSFREEZE 8 ubase-VERSION +.SH NAME +\fBfsfreeze\fR - Suspend access to a filesystem +.SH SYNOPSIS +\fBfsfreeze\fR [\fB-f\fR] [\fB-u\fR] \fImountpoint\fR +.SH DESCRIPTION +\fBfsfreeze\fR suspends and resumes access to a filesystem. +\fBfsfreeze\fR is intended to be used with hardware RAID +devices that support the creation of snapshots. +The \fImountpoint\fR argument is the pathname of the directory +where the filesystem is mounted. The filesystem must be mounted +to be frozen. +.SH OPTIONS +.TP +\fB-f\fR +Freeze the filesystem mounted at \fImountpoint\fR. +.TP +\fB-u\fR +Unfreeze the filesystem mounted at \fImountpoint\fR. +.SH BUGS +.TP +Only works for ext3/4, reiserfs, jfs and xfs. +.SH SEE ALSO +mount(8) diff --git a/ubase/fsfreeze.c b/ubase/fsfreeze.c @@ -0,0 +1,54 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +#define FIFREEZE _IOWR('X', 119, int) /* Freeze */ +#define FITHAW _IOWR('X', 120, int) /* Thaw */ + +static void +usage(void) +{ + eprintf("usage: %s [-f] [-u] mountpoint\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int fflag = 0; + int uflag = 0; + long p = 1; + int fd; + + ARGBEGIN { + case 'f': + fflag = 1; + break; + case 'u': + uflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc != 1) + usage(); + + if ((fflag ^ uflag) == 0) + usage(); + + fd = open(argv[0], O_RDONLY); + if (fd < 0) + eprintf("open: %s:", argv[0]); + if (ioctl(fd, fflag == 1 ? FIFREEZE : FITHAW, &p) < 0) + eprintf("%s %s:", fflag == 1 ? "FIFREEZE" : "FITHAW", argv[0]); + close(fd); + return 0; +} diff --git a/ubase/getty.8 b/ubase/getty.8 @@ -0,0 +1,11 @@ +.TH GETTY 8 ubase-VERSION +.SH NAME +\fBgetty\fR - Suckless linux getty +.SH SYNOPSIS +\fBgetty\fR [\fItty\fR] [\fIterm\fR] [\fIcmd\fR] [\fIargs...\fR] +.SH DESCRIPTION +\fBgetty\fR opens a tty device, prompts for a login name and by default +invokes the /bin/login program. You can start another program instead of +/bin/login via \fIcmd\fR with \fIargs\fR. The hostname is printed in the +login name prompt as well. The \fItty\fR should be specified using an +absolute path. diff --git a/ubase/getty.c b/ubase/getty.c @@ -0,0 +1,140 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <utmp.h> + +#include "config.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [tty] [term] [cmd] [args...]\n", argv0); +} + +static char *tty = "/dev/tty1"; +static char *defaultterm = "linux"; + +int +main(int argc, char *argv[]) +{ + char term[128], logname[LOGIN_NAME_MAX], c; + char hostname[HOST_NAME_MAX + 1]; + struct utmp usr; + struct sigaction sa; + FILE *fp; + int fd; + unsigned int i = 0; + ssize_t n; + long pos; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + strlcpy(term, defaultterm, sizeof(term)); + if (argc > 0) { + tty = argv[0]; + if (argc > 1) + strlcpy(term, argv[1], sizeof(term)); + } + + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGHUP, &sa, NULL); + + setenv("TERM", term, 1); + + setsid(); + + fd = open(tty, O_RDWR); + if (fd < 0) + eprintf("open %s:", tty); + if (isatty(fd) == 0) + eprintf("%s is not a tty\n", tty); + + /* steal the controlling terminal if necessary */ + if (ioctl(fd, TIOCSCTTY, (void *)1) != 0) + weprintf("TIOCSCTTY: could not set controlling tty\n"); + vhangup(); + close(fd); + + fd = open(tty, O_RDWR); + if (fd < 0) + eprintf("open %s:", tty); + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + if (fchown(fd, 0, 0) < 0) + weprintf("fchown %s:", tty); + if (fchmod(fd, 0600) < 0) + weprintf("fchmod %s:", tty); + if (fd > 2) + close(fd); + + sa.sa_handler = SIG_DFL; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGHUP, &sa, NULL); + + /* Clear all utmp entries for this tty */ + fp = fopen(UTMP_PATH, "r+"); + if (fp) { + do { + pos = ftell(fp); + if (fread(&usr, sizeof(usr), 1, fp) != 1) + break; + if (usr.ut_line[0] == '\0') + continue; + if (strcmp(usr.ut_line, tty) != 0) + continue; + memset(&usr, 0, sizeof(usr)); + fseek(fp, pos, SEEK_SET); + if (fwrite(&usr, sizeof(usr), 1, fp) != 1) + break; + } while (1); + if (ferror(fp)) + weprintf("%s: I/O error:", UTMP_PATH); + fclose(fp); + } + + if (argc > 2) + return execvp(argv[2], argv + 2); + + if (gethostname(hostname, sizeof(hostname)) == 0) + printf("%s ", hostname); + printf("login: "); + fflush(stdout); + + /* Flush pending input */ + ioctl(0, TCFLSH, (void *)0); + memset(logname, 0, sizeof(logname)); + while (1) { + n = read(0, &c, 1); + if (n < 0) + eprintf("read:"); + if (n == 0) + return 1; + if (i >= sizeof(logname) - 1) + eprintf("login name too long\n"); + if (c == '\n' || c == '\r') + break; + logname[i++] = c; + } + if (logname[0] == '-') + eprintf("login name cannot start with '-'\n"); + if (logname[0] == '\0') + return 1; + return execlp("/bin/login", "login", "-p", logname, NULL); +} diff --git a/ubase/halt.8 b/ubase/halt.8 @@ -0,0 +1,16 @@ +.TH HALT 8 ubase-VERSION +.SH NAME +\fBhalt\fR - Power-off or reboot the machine +.SH SYNOPSIS +\fBhalt\fR [\fB-pr\fR] +.SH DESCRIPTION +\fBhalt\fR can be used to power-off or reboot the machine. +This is a low-level tool and should not be used directly or data-loss +can happen if the filesystems are not properly unmounted first. +.SH OPTIONS +.TP +\fB-p\fR +Power-off the machine. +.TP +\fB-r\fR +Reboot the machine. diff --git a/ubase/halt.c b/ubase/halt.c @@ -0,0 +1,51 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/syscall.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "reboot.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-pr]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int pflag = 0, rflag = 0; + int cmd = LINUX_REBOOT_CMD_HALT; + + ARGBEGIN { + case 'p': + pflag = 1; + break; + case 'r': + rflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc > 0) + usage(); + + sync(); + + if (pflag && rflag) + usage(); + + if (pflag) + cmd = LINUX_REBOOT_CMD_POWER_OFF; + if (rflag) + cmd = LINUX_REBOOT_CMD_RESTART; + + if (syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, + LINUX_REBOOT_MAGIC2, cmd, NULL) < 0) + eprintf("reboot:"); + return 0; +} diff --git a/ubase/hwclock.8 b/ubase/hwclock.8 @@ -0,0 +1,25 @@ +.TH HWCLOCK 8 ubase-VERSION +.SH NAME +\fBhwclock\fR - Query or set the hardware clock +.SH SYNOPSIS +\fBhwclock\fR [\fB-rsw\fR] [\fB-u\fR] [\fIdev\fR] +.SH DESCRIPTION +\fBhwclock\fR is a tool for accessing the hardware clock. You can display +the current time, set the hardware clock from the System Time, or +set the System Time from the hardware clock. It currently only works with UTC. +You can use \fIdev\fR to specify the RTC device node absolute path. By default +it will use \fI/dev/rtc\fR. +.SH FUNCTIONS +.TP +\fB-r\fR +Read the hardware clock and print the time on stdout. +.TP +\fB-s\fR +Set the system time from the hardware clock. +.TP +\fB-w\fR +Set the hardware clock to the system time. +.SH OPTIONS +.TP +\fB-u\fR +Use UTC. This is the default and only option. diff --git a/ubase/hwclock.c b/ubase/hwclock.c @@ -0,0 +1,165 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "rtc.h" +#include "util.h" + +static void readrtctm(struct tm *, int); +static void writertctm(struct tm *, int); +static void show(char *); +static void hctosys(char *); +static void systohc(char *); + +static void +usage(void) +{ + eprintf("usage: %s [-rsw] [-u] [dev]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *dev = "/dev/rtc"; + int rflag = 0; + int sflag = 0; + int wflag = 0; + + ARGBEGIN { + case 'r': + rflag = 1; + break; + case 's': + sflag = 1; + break; + case 'w': + wflag = 1; + break; + case 'u': + break; + default: + usage(); + } ARGEND; + + if (argc > 1) + usage(); + else if (argc == 1) + dev = argv[0]; + + if ((rflag ^ sflag ^ wflag) == 0) + eprintf("missing or incompatible function\n"); + + /* Only UTC support at the moment */ + setenv("TZ", "UTC0", 1); + tzset(); + + if (rflag == 1) + show(dev); + else if (sflag == 1) + hctosys(dev); + else if (wflag == 1) + systohc(dev); + + return 0; +} + +static void +readrtctm(struct tm *tm, int fd) +{ + struct rtc_time rt; + + memset(&rt, 0, sizeof(rt)); + if (ioctl(fd, RTC_RD_TIME, &rt) < 0) + eprintf("RTC_RD_TIME:"); + tm->tm_sec = rt.tm_sec; + tm->tm_min = rt.tm_min; + tm->tm_hour = rt.tm_hour; + tm->tm_mday = rt.tm_mday; + tm->tm_mon = rt.tm_mon; + tm->tm_year = rt.tm_year; + tm->tm_wday = rt.tm_wday; + tm->tm_yday = rt.tm_yday; + tm->tm_isdst = rt.tm_isdst; +} + +static void +writertctm(struct tm *tm, int fd) +{ + struct rtc_time rt; + + rt.tm_sec = tm->tm_sec; + rt.tm_min = tm->tm_min; + rt.tm_hour = tm->tm_hour; + rt.tm_mday = tm->tm_mday; + rt.tm_mon = tm->tm_mon; + rt.tm_year = tm->tm_year; + rt.tm_wday = tm->tm_wday; + rt.tm_yday = tm->tm_yday; + rt.tm_isdst = tm->tm_isdst; + if (ioctl(fd, RTC_SET_TIME, &rt) < 0) + eprintf("RTC_SET_TIME:"); +} + +static void +show(char *dev) +{ + struct tm tm; + time_t t; + int fd; + + fd = open(dev, O_RDONLY); + if (fd < 0) + eprintf("open %s:", dev); + readrtctm(&tm, fd); + t = mktime(&tm); + printf("%s", asctime(localtime(&t))); + close(fd); +} + +static void +hctosys(char *dev) +{ + struct timeval tv; + struct tm tm; + int r; + int fd; + + fd = open(dev, O_RDONLY); + if (fd < 0) + eprintf("open %s:", dev); + readrtctm(&tm, fd); + tv.tv_sec = mktime(&tm); + tv.tv_usec = 0; + r = settimeofday(&tv, NULL); + if (r < 0) + eprintf("settimeofday:"); + close(fd); +} + +static void +systohc(char *dev) +{ + struct timeval tv; + struct tm *tm; + time_t t; + int fd; + + fd = open(dev, O_WRONLY); + if (fd < 0) + eprintf("open %s:", dev); + gettimeofday(&tv, NULL); + t = tv.tv_sec; + tm = gmtime(&t); + weprintf("warning: assuming UTC for systohc\n"); + writertctm(tm, fd); + close(fd); +} diff --git a/ubase/id.1 b/ubase/id.1 @@ -0,0 +1,21 @@ +.TH ID 1 ubase-VERSION +.SH NAME +\fBid\fR - Print real and effective user and group IDs +.SH SYNOPSIS +\fBid\fR [\fB-g\fR] [\fB-u\fR] [\fB-G\fR] \fR[\fIuser\fR|\fIuid\fR] +.SH DESCRIPTION +\fBid\fR prints user and group information of the calling process to standard output. +If a login name or uid is specified, the user and group information of that +user is displayed. +.SH OPTIONS +.TP +\fB-g\fR +Print only the effective group ID. +.TP +\fB-u\fR +Print only the effective user ID. +.TP +\fB-G\fR +Display group information as whitespace separated numbers, in no particular order. +.SH SEE ALSO +who(1) diff --git a/ubase/id.c b/ubase/id.c @@ -0,0 +1,143 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/types.h> + +#include <ctype.h> +#include <errno.h> +#include <grp.h> +#include <limits.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void groupid(struct passwd *pw); +static void user(struct passwd *pw); +static void userid(uid_t id); +static void usernam(const char *nam); + +static void +usage(void) +{ + eprintf("usage: %s [-g] [-u] [-G] [user | uid]\n", argv0); +} + +static int Gflag = 0; + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + case 'g': + printf("%d\n", getegid()); + return 0; + case 'u': + printf("%d\n", geteuid()); + return 0; + case 'G': + Gflag = 1; + break; + default: + usage(); + } ARGEND; + + switch (argc) { + case 0: + userid(getuid()); + break; + case 1: + /* user names can't begin [0-9] */ + if (isdigit(argv[0][0])) + userid(estrtol(argv[0], 0)); + else + usernam(argv[0]); + break; + default: + usage(); + } + + return 0; +} + +static void +groupid(struct passwd *pw) +{ + gid_t gid, groups[NGROUPS_MAX]; + int ngroups; + int i; + + ngroups = NGROUPS_MAX; + getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups); + for (i = 0; i < ngroups; i++) { + gid = groups[i]; + printf("%u", gid); + if (i < ngroups - 1) + putchar(' '); + } + putchar('\n'); +} + +static void +usernam(const char *nam) +{ + struct passwd *pw; + + errno = 0; + pw = getpwnam(nam); + if (!pw) { + if (errno) + eprintf("getpwnam %s:", nam); + else + eprintf("getpwnam %s: no such user\n", nam); + } + if (Gflag) + groupid(pw); + else + user(pw); +} + +static void +userid(uid_t id) +{ + struct passwd *pw; + + errno = 0; + pw = getpwuid(id); + if (!pw) { + if (errno) + eprintf("getpwuid %d:", id); + else + eprintf("getpwuid %d: no such user\n", id); + } + if (Gflag) + groupid(pw); + else + user(pw); +} + +static void +user(struct passwd *pw) +{ + struct group *gr; + gid_t gid, groups[NGROUPS_MAX]; + int ngroups; + int i; + + printf("uid=%u(%s)", pw->pw_uid, pw->pw_name); + printf(" gid=%u", pw->pw_gid); + if (!(gr = getgrgid(pw->pw_gid))) + eprintf("getgrgid:"); + printf("(%s)", gr->gr_name); + + ngroups = NGROUPS_MAX; + getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups); + for (i = 0; i < ngroups; i++) { + gid = groups[i]; + printf("%s%u", !i ? " groups=" : ",", gid); + if (!(gr = getgrgid(gid))) + eprintf("getgrgid:"); + printf("(%s)", gr->gr_name); + } + putchar('\n'); +} diff --git a/ubase/insmod.8 b/ubase/insmod.8 @@ -0,0 +1,10 @@ +.TH INSMOD 8 ubase-VERSION +.SH NAME +\fBinsmod\fR - Insert a module into the Linux kernel +.SH SYNOPSIS +\fBinsmod\fR \fIfilename\fR [\fIargs...\fR] +.SH DESCRIPTION +\fBinsmod\fR inserts the module specified by \fIfilename\fR +into the kernel. It does not handle module dependencies. +.SH SEE ALSO +rmmod(8), lsmod(8) diff --git a/ubase/insmod.c b/ubase/insmod.c @@ -0,0 +1,69 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> +#include <sys/syscall.h> + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s filename [args...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *buf = NULL, *opts = NULL; + size_t blen, plen = 0; + int i, fd; + ssize_t n; + struct stat sb; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + fd = open(argv[0], O_RDONLY); + if (fd < 0) + eprintf("open %s:", argv[0]); + if (fstat(fd, &sb) < 0) + eprintf("stat %s:", argv[0]); + blen = sb.st_size; + buf = emalloc(blen); + + n = read(fd, buf, blen); + if (n < 0 || (size_t)n != blen) + eprintf("read:"); + + argc--; + argv++; + + for (i = 0; i < argc; i++) + plen += strlen(argv[i]); + if (plen > 0) { + plen += argc; + opts = ecalloc(1, plen); + for (i = 0; i < argc; i++) { + strcat(opts, argv[i]); + if (i + 1 < argc) + strcat(opts, " "); + } + } + + if (syscall(__NR_init_module, buf, blen, !opts ? "" : opts) < 0) + eprintf("init_module:"); + + free(opts); + free(buf); + return 0; +} diff --git a/ubase/killall5.8 b/ubase/killall5.8 @@ -0,0 +1,18 @@ +.TH KILLALL5 8 ubase-VERSION +.SH NAME +\fBkillall\fR - Send a signal to all processes +.SH SYNOPSIS +\fBkillall5\fR [\fB-o\fI pid1,pid2,...,pidN\fR] [\fB-s\fI signal\fR] +.SH DESCRIPTION +\fBkillall5\fR is an implementation of the SystemV killall command. +It sends a signal to all processes except kernel threads and the processes +in its own session. It is primarily used by the system's init scripts. +.SH OPTIONS +.TP +\fB-o\fR +Tell killall5 to omit processes with that process id. +.TP +\fB-s\fR +Send \fIsignal\fR instead of the default SIGTERM. +.SH SEE ALSO +halt(8), reboot(8) diff --git a/ubase/killall5.c b/ubase/killall5.c @@ -0,0 +1,115 @@ +/* See LICENSE file for copyright and license details. */ +#include <dirent.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "proc.h" +#include "queue.h" +#include "util.h" + +struct { + const char *name; + int sig; +} sigs[] = { +#define SIG(n) { #n, SIG##n } + SIG(ABRT), SIG(ALRM), SIG(BUS), SIG(CHLD), SIG(CONT), SIG(FPE), SIG(HUP), + SIG(ILL), SIG(INT), SIG(KILL), SIG(PIPE), SIG(QUIT), SIG(SEGV), SIG(STOP), + SIG(TERM), SIG(TSTP), SIG(TTIN), SIG(TTOU), SIG(USR1), SIG(USR2), SIG(URG), +#undef SIG +}; + +static void +usage(void) +{ + eprintf("usage: %s [-o pid1,pid2,..,pidN] [-s signal]\n", argv0); +} + +struct pidentry { + pid_t pid; + TAILQ_ENTRY(pidentry) entry; +}; + +static TAILQ_HEAD(omitpid_head, pidentry) omitpid_head; + +int +main(int argc, char *argv[]) +{ + struct pidentry *pe, *tmp; + int oflag = 0; + char *p, *arg = NULL; + DIR *dp; + struct dirent *entry; + char *end, *v; + int sig = SIGTERM; + pid_t pid; + size_t i; + + ARGBEGIN { + case 's': + v = EARGF(usage()); + sig = strtol(v, &end, 0); + if (*end == '\0') + break; + for (i = 0; i < LEN(sigs); i++) { + if (strcasecmp(v, sigs[i].name) == 0) { + sig = sigs[i].sig; + break; + } + } + if (i == LEN(sigs)) + eprintf("%s: unknown signal\n", v); + break; + case 'o': + oflag = 1; + arg = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + + TAILQ_INIT(&omitpid_head); + + for (p = strtok(arg, ","); p; p = strtok(NULL, ",")) { + pe = emalloc(sizeof(*pe)); + pe->pid = estrtol(p, 10); + TAILQ_INSERT_TAIL(&omitpid_head, pe, entry); + } + + if (sig != SIGSTOP && sig != SIGCONT) + kill(-1, SIGSTOP); + + if (!(dp = opendir("/proc"))) + eprintf("opendir /proc:"); + while ((entry = readdir(dp))) { + if (pidfile(entry->d_name) == 0) + continue; + pid = estrtol(entry->d_name, 10); + if (pid == 1 || pid == getpid() || + getsid(pid) == getsid(0) || getsid(pid) == 0) + continue; + if (oflag == 1) { + TAILQ_FOREACH(pe, &omitpid_head, entry) + if (pe->pid == pid) + break; + if (pe) + continue; + } + kill(pid, sig); + } + closedir(dp); + + if (sig != SIGSTOP && sig != SIGCONT) + kill(-1, SIGCONT); + + for (pe = TAILQ_FIRST(&omitpid_head); pe; pe = tmp) { + tmp = TAILQ_NEXT(pe, entry); + TAILQ_REMOVE(&omitpid_head, pe, entry); + free(pe); + } + + return 0; +} diff --git a/ubase/last.c b/ubase/last.c @@ -0,0 +1,64 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <libgen.h> +#include <paths.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <utmp.h> +#include <unistd.h> + +#include "config.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [user]\n", argv0); +} + +int +main(int argc, char **argv) +{ + FILE *fp; + struct utmp ut; + char *user, *file, *prog; + time_t t; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + switch (argc) { + case 0: + user = NULL; + break; + case 1: + user = argv[0]; + break; + default: + usage(); + } + + prog = basename(argv0); + file = (!strcmp(prog, "last")) ? WTMP_PATH : BTMP_PATH; + if ((fp = fopen(file, "r")) == NULL) + eprintf("fopen %s:", file); + + while (fread(&ut, sizeof(ut), 1, fp) == 1) { + if (ut.ut_type != USER_PROCESS || + (user && strcmp(user, ut.ut_name))) { + continue; + } + + t = ut.ut_time; + printf("%-8.8s %-8.8s %-16.16s %s", + ut.ut_user, ut.ut_line, ut.ut_host, ctime(&t)); + } + if (fclose(fp)) + eprintf("fclose %s:", file); + return 0; +} diff --git a/ubase/lastlog.8 b/ubase/lastlog.8 @@ -0,0 +1,11 @@ +.TH LASTLOG 8 ubase-VERSION +.SH NAME +\fBlastlog\fR - Show last login of users +.SH SYPNOSIS +\fBlastlog\fI [user ...] +.SH DESCRIPTION +\fBlastlog\fR Show time, tty, and host (if it was a remote +connection) of last login of users. If some user names are passed +as parameter then information about last login of these users is +shown, otherwise is shown for all the users in /etc/passwd in the +order they appear in it. diff --git a/ubase/lastlog.c b/ubase/lastlog.c @@ -0,0 +1,78 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <paths.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <utmp.h> + +#include "text.h" +#include "util.h" + +#define PASSWD "/etc/passwd" + +static FILE *last; + +static void +lastlog(char *user) +{ + struct passwd *pwd; + struct lastlog ll; + time_t lltime; + + errno = 0; + if ((pwd = getpwnam(user)) == NULL) { + if (errno) + weprintf("getpwnam %s:", user); + else + weprintf("unknown user: %s\n", user); + return; + } + + fseek(last, pwd->pw_uid * sizeof(struct lastlog), 0); + fread(&ll, sizeof(struct lastlog), 1, last); + + if (ferror(last)) + eprintf("%s: read error:", _PATH_LASTLOG); + + /* on glibc `ll_time' can be an int32_t with compat32 + * avoid compiler warning when calling ctime() */ + lltime = ll.ll_time; + printf("%-8.8s %-8.8s %-16.16s %s", + user, ll.ll_line, ll.ll_host, ctime(&lltime)); +} + +int +main(int argc, char **argv) +{ + FILE *fp; + char *line = NULL, *p; + size_t sz = 0; + + if ((last = fopen(_PATH_LASTLOG, "r")) == NULL) + eprintf("fopen %s:", _PATH_LASTLOG); + + if (argc > 1) { + while (*++argv) + lastlog(*argv); + } else { + if ((fp = fopen(PASSWD, "r")) == NULL) + eprintf("fopen %s:", PASSWD); + while (agetline(&line, &sz, fp) != -1) { + if ((p = strchr(line, ':')) == NULL) + eprintf("invalid passwd entry\n"); + *p = '\0'; + lastlog(line); + } + if (fclose(fp)) + eprintf("fclose %s:", PASSWD); + free(line); + } + + if (fclose(last)) + eprintf("fclose %s:", _PATH_LASTLOG); + + return 0; +} diff --git a/ubase/libutil/agetcwd.c b/ubase/libutil/agetcwd.c @@ -0,0 +1,18 @@ +/* See LICENSE file for copyright and license details. */ +#include <unistd.h> + +#include "../util.h" + +char * +agetcwd(void) +{ + char *buf; + long size; + + apathmax(&buf, &size); + if (!getcwd(buf, size)) + eprintf("getcwd:"); + + return buf; +} + diff --git a/ubase/libutil/agetline.c b/ubase/libutil/agetline.c @@ -0,0 +1,13 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../text.h" +#include "../util.h" + +ssize_t +agetline(char **p, size_t *size, FILE *fp) +{ + return getline(p, size, fp); +} diff --git a/ubase/libutil/apathmax.c b/ubase/libutil/apathmax.c @@ -0,0 +1,22 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "../util.h" + +void +apathmax(char **p, long *size) +{ + errno = 0; + + if ((*size = pathconf("/", _PC_PATH_MAX)) == -1) { + if (errno == 0) { + *size = BUFSIZ; + } else { + eprintf("pathconf:"); + } + } + *p = emalloc(*size); +} diff --git a/ubase/libutil/concat.c b/ubase/libutil/concat.c @@ -0,0 +1,20 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <unistd.h> + +#include "../text.h" +#include "../util.h" + +void +concat(FILE *fp1, const char *s1, FILE *fp2, const char *s2) +{ + char buf[BUFSIZ]; + ssize_t n; + + while ((n = read(fileno(fp1), buf, sizeof buf)) > 0) { + if (write(fileno(fp2), buf, n) != n) + eprintf("%s: write error:", s2); + } + if (n < 0) + eprintf("%s: read error:", s1); +} diff --git a/ubase/libutil/ealloc.c b/ubase/libutil/ealloc.c @@ -0,0 +1,47 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdlib.h> +#include <string.h> + +#include "../util.h" + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + p = calloc(nmemb, size); + if (!p) + eprintf("calloc: out of memory\n"); + return p; +} + +void * +emalloc(size_t size) +{ + void *p; + + p = malloc(size); + if (!p) + eprintf("malloc: out of memory\n"); + return p; +} + +void * +erealloc(void *p, size_t size) +{ + p = realloc(p, size); + if (!p) + eprintf("realloc: out of memory\n"); + return p; +} + +char * +estrdup(const char *s) +{ + char *p; + + p = strdup(s); + if (!p) + eprintf("strdup: out of memory\n"); + return p; +} diff --git a/ubase/libutil/eprintf.c b/ubase/libutil/eprintf.c @@ -0,0 +1,67 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../util.h" + +char *argv0; + +static void venprintf(int, const char *, va_list); + +void +eprintf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + venprintf(1, fmt, ap); + va_end(ap); +} + +void +enprintf(int status, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + venprintf(status, fmt, ap); + va_end(ap); +} + +void +venprintf(int status, const char *fmt, va_list ap) +{ +#ifdef DEBUG + fprintf(stderr, "%s: ", argv0); +#endif + + vfprintf(stderr, fmt, ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } + + exit(status); +} + +void +weprintf(const char *fmt, ...) +{ + va_list ap; + +#ifdef DEBUG + fprintf(stderr, "%s: ", argv0); +#endif + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } +} diff --git a/ubase/libutil/estrtol.c b/ubase/libutil/estrtol.c @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../util.h" + +long +estrtol(const char *s, int base) +{ + char *end; + long n; + + errno = 0; + n = strtol(s, &end, base); + if (*end != '\0') { + if (base == 0) + eprintf("%s: not an integer\n", s); + else + eprintf("%s: not a base %d integer\n", s, base); + } + if (errno != 0) + eprintf("%s:", s); + + return n; +} + diff --git a/ubase/libutil/estrtoul.c b/ubase/libutil/estrtoul.c @@ -0,0 +1,26 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../util.h" + +unsigned long +estrtoul(const char *s, int base) +{ + char *end; + unsigned long n; + + errno = 0; + n = strtoul(s, &end, base); + if (*end != '\0') { + if (base == 0) + eprintf("%s: not an integer\n", s); + else + eprintf("%s: not a base %d integer\n", s, base); + } + if (errno != 0) + eprintf("%s:", s); + + return n; +} diff --git a/ubase/libutil/explicit_bzero.c b/ubase/libutil/explicit_bzero.c @@ -0,0 +1,12 @@ +/* See LICENSE file for copyright and license details. */ +#include <string.h> + +#include "../util.h" + +static void *(*volatile explicit_memset)(void *, int, size_t) = memset; + +void +explicit_bzero(void *b, size_t len) +{ + (*explicit_memset)(b, 0, len); +} diff --git a/ubase/libutil/passwd.c b/ubase/libutil/passwd.c @@ -0,0 +1,77 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/resource.h> +#include <sys/time.h> + +#include <errno.h> +#include <pwd.h> +#include <shadow.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../passwd.h" +#include "../text.h" +#include "../util.h" + +/* Returns -1 on error, 0 for incorrect password + * and 1 if all went OK */ +int +pw_check(const struct passwd *pw, const char *pass) +{ + char *cryptpass, *p; + struct spwd *spw; + + p = pw->pw_passwd; + if (p[0] == '!' || p[0] == '*') { + weprintf("denied\n"); + return -1; + } + + if (pw->pw_passwd[0] == '\0') { + if (pass[0] == '\0') + return 1; + weprintf("incorrect password\n"); + return 0; + } + + if (pw->pw_passwd[0] == 'x' && pw->pw_passwd[1] == '\0') { + errno = 0; + spw = getspnam(pw->pw_name); + if (!spw) { + if (errno) + weprintf("getspnam: %s:", pw->pw_name); + else + weprintf("who are you?\n"); + return -1; + } + p = spw->sp_pwdp; + if (p[0] == '!' || p[0] == '*') { + weprintf("denied\n"); + return -1; + } + } + + cryptpass = crypt(pass, p); + if (!cryptpass) { + weprintf("crypt:"); + return -1; + } + if (strcmp(cryptpass, p) != 0) { + weprintf("incorrect password\n"); + return 0; + } + return 1; +} + +int +pw_init(void) +{ + struct rlimit rlim; + + rlim.rlim_cur = 0; + rlim.rlim_max = 0; + if (setrlimit(RLIMIT_CORE, &rlim) < 0) + eprintf("setrlimit:"); + return 0; +} diff --git a/ubase/libutil/proc.c b/ubase/libutil/proc.c @@ -0,0 +1,117 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../proc.h" +#include "../util.h" + +int +parsecmdline(pid_t pid, char *buf, size_t siz) +{ + int fd; + char path[PATH_MAX]; + ssize_t n, i; + + snprintf(path, sizeof(path), "/proc/%ld/cmdline", (long)pid); + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + n = read(fd, buf, siz > 0 ? siz - 1 : 0); + if (n < 0) { + weprintf("read %s:", path); + close(fd); + return -1; + } + if (!n) { + close(fd); + return -1; + } + buf[n] = '\0'; + for (i = 0; i < n; i++) + if (buf[i] == '\0') + buf[i] = ' '; + close(fd); + return 0; +} + +int +parsestat(pid_t pid, struct procstat *ps) +{ + char path[PATH_MAX]; + FILE *fp; + size_t len; + + snprintf(path, sizeof(path), "/proc/%d/stat", pid); + if (!(fp = fopen(path, "r"))) + return -1; + fscanf(fp, "%d %s %c %d %d %d %d %d %u %lu %lu %lu %lu %lu %lu", + &ps->pid, ps->comm, + &ps->state, &ps->ppid, &ps->pgrp, + &ps->sid, &ps->tty_nr, &ps->tpgid, &ps->flags, + &ps->minflt, &ps->cminflt, &ps->majflt, &ps->cmajflt, + &ps->utime, &ps->stime); + fscanf(fp, "%ld %ld %ld %ld %ld %ld %llu %lu %ld %ld", + &ps->cutime, &ps->cstime, &ps->priority, &ps->nice, + &ps->num_threads, &ps->itrealvalue, &ps->starttime, + &ps->vsize, &ps->rss, &ps->rsslim); + /* Filter out '(' and ')' from comm */ + if ((len = strlen(ps->comm)) > 0) + len--; + ps->comm[len] = '\0'; + memmove(ps->comm, ps->comm + 1, len); + fclose(fp); + return 0; +} + +int +parsestatus(pid_t pid, struct procstatus *pstatus) +{ + char path[PATH_MAX]; + char buf[BUFSIZ], *off; + int fd; + ssize_t n; + + snprintf(path, sizeof(path), "/proc/%d/status", pid); + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + n = read(fd, buf, sizeof(buf) - 1); + if (n < 0) + eprintf("%s: read error:", path); + if (!n) { + close(fd); + return -1; + } + buf[n] = '\0'; + close(fd); + off = strstr(buf, "Uid:"); + if (!off) + return -1; + sscanf(off, "Uid: %u %u", &pstatus->uid, &pstatus->euid); + off = strstr(buf, "Gid:"); + if (!off) + return -1; + sscanf(off, "Gid: %u %u", &pstatus->gid, &pstatus->egid); + return 0; +} + +int +pidfile(const char *file) +{ + char *end; + + errno = 0; + strtol(file, &end, 10); + if (*end != '\0') + return 0; + if (errno != 0) + return 0; + return 1; +} diff --git a/ubase/libutil/putword.c b/ubase/libutil/putword.c @@ -0,0 +1,16 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> + +#include "../util.h" + +void +putword(const char *s) +{ + static int first = 1; + + if (!first) + putchar(' '); + + fputs(s, stdout); + first = 0; +} diff --git a/ubase/libutil/recurse.c b/ubase/libutil/recurse.c @@ -0,0 +1,42 @@ +/* See LICENSE file for copyright and license details. */ +#include <dirent.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "../util.h" + +void +recurse(const char *path, void (*fn)(const char *)) +{ + char buf[PATH_MAX]; + struct dirent *d; + struct stat st; + DIR *dp; + + if (lstat(path, &st) == -1 || S_ISDIR(st.st_mode) == 0) + return; + + if (!(dp = opendir(path))) + eprintf("opendir %s:", path); + + while ((d = readdir(dp))) { + if (strcmp(d->d_name, ".") == 0 || + strcmp(d->d_name, "..") == 0) + continue; + if (strlcpy(buf, path, sizeof(buf)) >= sizeof(buf)) + eprintf("path too long\n"); + if (buf[strlen(buf) - 1] != '/') + if (strlcat(buf, "/", sizeof(buf)) >= sizeof(buf)) + eprintf("path too long\n"); + if (strlcat(buf, d->d_name, sizeof(buf)) >= sizeof(buf)) + eprintf("path too long\n"); + fn(buf); + } + + closedir(dp); +} diff --git a/ubase/libutil/strlcat.c b/ubase/libutil/strlcat.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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 <string.h> +#include <sys/types.h> + +#include "../util.h" + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t +strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + return(dlen + (s - src)); /* count does not include NUL */ +} diff --git a/ubase/libutil/strlcpy.c b/ubase/libutil/strlcpy.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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 <string.h> +#include <sys/types.h> + +#include "../util.h" + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + return(s - src - 1); /* count does not include NUL */ +} diff --git a/ubase/libutil/tty.c b/ubase/libutil/tty.c @@ -0,0 +1,39 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../util.h" + +void +devtotty(int dev, int *tty_maj, int *tty_min) +{ + *tty_maj = (dev >> 8) & 0xfff; + *tty_min = (dev & 0xff) | ((dev >> 12) & 0xfff00); +} + +char * +ttytostr(int tty_maj, int tty_min) +{ + const char *pts = "pts/"; + const char *tty = "tty"; + char *ttystr; + size_t len; + + /* Up to 10k ttys */ + len = strlen(pts) + 4 + 1; + ttystr = emalloc(len); + switch (tty_maj) { + case 136: + snprintf(ttystr, len, "%s%d", pts, tty_min); + break; + case 4: + snprintf(ttystr, len, "%s%d", tty, tty_min); + break; + default: + ttystr[0] = '?'; + ttystr[1] = '\0'; + break; + } + return ttystr; +} diff --git a/ubase/login.1 b/ubase/login.1 @@ -0,0 +1,13 @@ +.TH LOGIN 1 ubase-VERSION +.SH NAME +\fBlogin\fR - Log into the system +.SH SYNOPSIS +\fBlogin\fR [\fB-p\fR] \fIusername\fR +.SH DESCRIPTION +\fBlogin\fR logs the \fIusername\fR into the system. It sets \fBHOME\fR, +\fBSHELL\fR, \fBUSER\fR, \fBLOGNAME\fR and the \fBPATH\fR environment +variables and invokes the login shell as specified in \fI/etc/passwd\fR. +.SH OPTIONS +.TP +\fB-p\fR +Preserve the environment. diff --git a/ubase/login.c b/ubase/login.c @@ -0,0 +1,132 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/ioctl.h> +#include <sys/types.h> + +#include <errno.h> +#include <grp.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <utmp.h> + +#include "config.h" +#include "passwd.h" +#include "util.h" + +static int dologin(struct passwd *, int); + +static void +usage(void) +{ + eprintf("usage: %s [-p] username\n", argv0); +} + +/* Write utmp entry */ +static void +writeutmp(const char *user, const char *tty) +{ + struct utmp usr; + FILE *fp; + + memset(&usr, 0, sizeof(usr)); + + usr.ut_type = USER_PROCESS; + usr.ut_pid = getpid(); + strlcpy(usr.ut_user, user, sizeof(usr.ut_user)); + strlcpy(usr.ut_line, tty, sizeof(usr.ut_line)); + usr.ut_tv.tv_sec = time(NULL); + + fp = fopen(UTMP_PATH, "a"); + if (fp) { + if (fwrite(&usr, sizeof(usr), 1, fp) != 1) + if (ferror(fp)) + weprintf("%s: write error:", UTMP_PATH); + fclose(fp); + } else { + weprintf("fopen %s:", UTMP_PATH); + } +} + +int +main(int argc, char *argv[]) +{ + struct passwd *pw; + char *pass, *user; + char *tty; + uid_t uid; + gid_t gid; + int pflag = 0; + + ARGBEGIN { + case 'p': + pflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + if (isatty(0) == 0 || isatty(1) == 0 || isatty(2) == 0) + eprintf("no tty"); + + user = argv[0]; + errno = 0; + pw = getpwnam(user); + if (!pw) { + if (errno) + eprintf("getpwnam %s:", user); + else + eprintf("who are you?\n"); + } + + uid = pw->pw_uid; + gid = pw->pw_gid; + + /* Flush pending input */ + ioctl(0, TCFLSH, (void *)0); + + pass = getpass("Password: "); + if (!pass) + eprintf("getpass:"); + if (pw_check(pw, pass) <= 0) + exit(1); + + tty = ttyname(0); + if (!tty) + eprintf("ttyname:"); + + writeutmp(user, tty); + + if (initgroups(user, gid) < 0) + eprintf("initgroups:"); + if (setgid(gid) < 0) + eprintf("setgid:"); + if (setuid(uid) < 0) + eprintf("setuid:"); + + return dologin(pw, pflag); +} + +static int +dologin(struct passwd *pw, int preserve) +{ + char *shell = pw->pw_shell[0] == '\0' ? "/bin/sh" : pw->pw_shell; + + if (preserve == 0) + clearenv(); + setenv("HOME", pw->pw_dir, 1); + setenv("SHELL", shell, 1); + setenv("USER", pw->pw_name, 1); + setenv("LOGNAME", pw->pw_name, 1); + setenv("PATH", ENV_PATH, 1); + if (chdir(pw->pw_dir) < 0) + eprintf("chdir %s:", pw->pw_dir); + execlp(shell, shell, "-l", NULL); + weprintf("execlp %s:", shell); + return (errno == ENOENT) ? 127 : 126; +} diff --git a/ubase/lsmod.8 b/ubase/lsmod.8 @@ -0,0 +1,6 @@ +.TH lsmod 8 ubase-VERSION +.SH NAME +\fBlsmod\fR - List loaded kernel modules +.SH DESCRIPTION +\fBlsmod\fR parses `/proc/modules' and shows the loadable kernel modules that are +currently loaded. diff --git a/ubase/lsmod.c b/ubase/lsmod.c @@ -0,0 +1,67 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "text.h" +#include "util.h" + +static void parse_modline(char *buf, char **name, char **size, + char **refcount, char **users); + +static void +usage(void) +{ + eprintf("usage: %s\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + const char *modfile = "/proc/modules"; + FILE *fp; + char *buf = NULL; + char *name, *size, *refcount, *users; + size_t bufsize = 0; + size_t len; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc > 0) + usage(); + + fp = fopen(modfile, "r"); + if (!fp) + eprintf("fopen %s:", modfile); + + printf("%-23s Size Used by\n", "Module"); + + while (agetline(&buf, &bufsize, fp) != -1) { + parse_modline(buf, &name, &size, &refcount, &users); + if (!name || !size || !refcount || !users) + eprintf("invalid format: %s\n", modfile); + len = strlen(users) - 1; + if (users[len] == ',' || users[len] == '-') + users[len] = '\0'; + printf("%-20s%8s%3s %s\n", name, size, refcount, + users); + } + if (ferror(fp)) + eprintf("%s: read error:", modfile); + free(buf); + fclose(fp); + return 0; +} + +static void +parse_modline(char *buf, char **name, char **size, + char **refcount, char **users) +{ + *name = strtok(buf, " "); + *size = strtok(NULL, " "); + *refcount = strtok(NULL, " "); + *users = strtok(NULL, " "); +} diff --git a/ubase/lsusb.8 b/ubase/lsusb.8 @@ -0,0 +1,6 @@ +.TH LSUSB 8 ubase-VERSION +.SH NAME +\fBlsusb\fR - List USB devices +.SH DESCRIPTION +\fBlsusb\fR searches in `/sys/bus/usb/devices' for usb buses and connected devices and prints +them one by one. diff --git a/ubase/lsusb.c b/ubase/lsusb.c @@ -0,0 +1,60 @@ +/* See LICENSE file for copyright and license details. */ +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> + +#include "text.h" +#include "util.h" + +static void lsusb(const char *file); + +static void +usage(void) +{ + eprintf("usage: %s\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + default: + usage(); + } ARGEND; + + recurse("/sys/bus/usb/devices", lsusb); + return 0; +} + +static void +lsusb(const char *file) +{ + FILE *fp; + char path[PATH_MAX]; + char *buf = NULL; + size_t size = 0; + unsigned int i = 0, busnum = 0, devnum = 0, pid = 0, vid = 0; + + if (strlcpy(path, file, sizeof(path)) >= sizeof(path)) + eprintf("path too long\n"); + if (strlcat(path, "/uevent", sizeof(path)) >= sizeof(path)) + eprintf("path too long\n"); + + if (!(fp = fopen(path, "r"))) + return; + while (agetline(&buf, &size, fp) != -1) { + if (sscanf(buf, "BUSNUM=%u\n", &busnum) || + sscanf(buf, "DEVNUM=%u\n", &devnum) || + sscanf(buf, "PRODUCT=%x/%x/", &pid, &vid)) + i++; + if (i == 3) { + printf("Bus %03d Device %03d: ID %04x:%04x\n", busnum, devnum, + pid, vid); + break; + } + } + if (ferror(fp)) + eprintf("%s: read error:", path); + free(buf); + fclose(fp); +} diff --git a/ubase/mesg.1 b/ubase/mesg.1 @@ -0,0 +1,19 @@ +.TH MESG 1 ubase-VERSION +.SH NAME +\fBmesg\fR - Display (do not display) messages from other users +.SH SYNOPSIS +\fBmesg\fR [\fBn\fR|\fBy\fR] +.SH DESCRIPTION +\fBmesg\fR controls write access others have to the terminal device associated +with standard error output. If write access is allowed, then programs such as \fBtalk\fR(1) +and \fBwrite\fR(1) may display messages on the terminal. +.SH OPTIONS +.TP +\fBn\fR +Disallow messages. +.TP +\fBy\fR +Allow messages. +.SH SEE ALSO +.TP +write(1) diff --git a/ubase/mesg.c b/ubase/mesg.c @@ -0,0 +1,53 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> +#include <sys/types.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [n|y]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct stat sb; + mode_t mode; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc > 1) + usage(); + + if (isatty(2) == 0) + eprintf("stderr: not a tty\n"); + + if (fstat(2, &sb) < 0) + eprintf("fstat stderr:"); + + if (argc == 0) { + puts(sb.st_mode & (S_IWGRP | S_IWOTH) ? "is y" : "is n"); + return 0; + } + + if (argv[0][0] == 'y' && argv[0][1] == '\0') + mode = sb.st_mode | S_IWGRP | S_IWOTH; + else if (argv[0][0] == 'n' && argv[0][1] == '\0') + mode = sb.st_mode & ~(S_IWGRP | S_IWOTH); + else + usage(); + + if (fchmod(2, mode) < 0) + eprintf("fchmod stderr:"); + + return 0; +} diff --git a/ubase/mknod.1 b/ubase/mknod.1 @@ -0,0 +1,23 @@ +.TH MKNOD 1 ubase-VERSION +.SH NAME +\fBmknod\fR - Create a special device file +.SH SYNOPSIS +\fBmknod\fR [\fB-m \fImode\fR] \fIname type major minor +.SH DESCRIPTION +\fBmknod\fR reates a special device file named \fIname\fR +with major number \fImajor\fR, and minor number \fIminor\fR. +\fItype\fR specifies what kind of special file will be created +and must be one of: +.TP +\fBu|c\fR +A character device. +.TP +\fBb\fR +A block device. +.SH OPTIONS +.TP +\fB-m\fR +Set the mode of the new file based on the octal value of +\fImode\fR. +.SH SEE ALSO +mknod (2) diff --git a/ubase/mknod.c b/ubase/mknod.c @@ -0,0 +1,45 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> +#include <sys/types.h> + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: mknod [-m mode] name type major minor\n"); +} + +int +main(int argc, char *argv[]) +{ + mode_t type, mode = 0644; + dev_t dev; + + ARGBEGIN { + case 'm': + mode = estrtol(EARGF(usage()), 8); + break; + default: + usage(); + } ARGEND; + + if (argc != 4) + usage(); + + if (strlen(argv[1]) != 1 || !strchr("ucb", argv[1][0])) + eprintf("mknod: '%s': invalid type\n", argv[1]); + type = (argv[1][0] == 'b') ? S_IFBLK : S_IFCHR; + + dev = makedev(estrtol(argv[2], 0), estrtol(argv[3], 0)); + + if (mknod(argv[0], type|mode, dev) == -1) + eprintf("mknod: '%s':", argv[0]); + return 0; +} diff --git a/ubase/mkswap.8 b/ubase/mkswap.8 @@ -0,0 +1,12 @@ +.TH MKSWAP 8 ubase-VERSION +.SH NAME +\fBmkswap\fR - Set up a Linux swap area +.SH SYNOPSIS +\fBmkswap\fR \fIdevice\fR +.SH DESCRIPTION +\fBmkswap\fR sets up a Linux swap area on a device or in a file. The +\fIdevice\fR argument will usually be a disk-partition but it can also be a +file. After creating the swap area you will typically need to use the +\fBswapon\fR command to start using it. +.SH SEE ALSO +swapon(8) diff --git a/ubase/mkswap.c b/ubase/mkswap.c @@ -0,0 +1,89 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +#define SWAP_UUID_LENGTH 16 +#define SWAP_LABEL_LENGTH 16 +#define SWAP_MIN_PAGES 10 + +struct swap_hdr { + char bootbits[1024]; + unsigned int version; + unsigned int last_page; + unsigned int nr_badpages; + unsigned char uuid[SWAP_UUID_LENGTH]; + char volume_name[SWAP_LABEL_LENGTH]; + unsigned int padding[117]; + unsigned int badpages[1]; +}; + +static void +usage(void) +{ + eprintf("usage: %s device\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int fd; + unsigned int pages; + long pagesize; + struct stat sb; + char *buf; + struct swap_hdr *hdr; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + pagesize = sysconf(_SC_PAGESIZE); + if (pagesize <= 0) { + pagesize = sysconf(_SC_PAGE_SIZE); + if (pagesize <= 0) + eprintf("can't determine pagesize\n"); + } + + fd = open(argv[0], O_RDWR); + if (fd < 0) + eprintf("open %s:", argv[0]); + if (fstat(fd, &sb) < 0) + eprintf("stat %s:", argv[0]); + + buf = ecalloc(1, pagesize); + + pages = sb.st_size / pagesize; + if (pages < SWAP_MIN_PAGES) + eprintf("swap space needs to be at least %ldKiB\n", + SWAP_MIN_PAGES * pagesize / 1024); + + /* Fill up the swap header */ + hdr = (struct swap_hdr *)buf; + hdr->version = 1; + hdr->last_page = pages - 1; + strncpy(buf + pagesize - 10, "SWAPSPACE2", 10); + + printf("Setting up swapspace version 1, size = %luKiB\n", + (pages - 1) * pagesize / 1024); + + /* Write out the signature page */ + if (write(fd, buf, pagesize) != pagesize) + eprintf("unable to write signature page\n"); + + fsync(fd); + close(fd); + free(buf); + + return 0; +} diff --git a/ubase/mount.8 b/ubase/mount.8 @@ -0,0 +1,33 @@ +.TH MOUNT 8 ubase-VERSION +.SH NAME +\fBmount\fR - Mount a filesystem +.SH SYNOPSIS +\fBmount\fR [\fB-BMRan\fR] [\fB-t\fI fstype\fR] [\fB-o\fI options\fR] [\fIsource\fR] [\fItarget\fR] +.SH DESCRIPTION +\fBmount\fR attaches the filesystem specified to the filesystem hierarchy. The \fBumount(8)\fR command will detach it again. +.SH OPTIONS +.TP +\fB-B\fR +Remount a subtree somewhere else (so that its contents are +visible in both places). +.TP +\fB-M\fR +Move a subtree to some other place. +.TP +\fB-R\fR +Remount a subtree and all possible submounts somewhere else (so +that its contents are available in both places). +.TP +\fB-a\fR +Mount all filesystems mentioned in /etc/fstab. +.TP +\fB-n\fR +Mount without writing in /etc/mtab. This is the default action. +.TP +\fB-t\fR +Set the filesystem type. +.TP +\fB-o\fR +Specify a comma separated string of filesystem specific options. +.SH SEE ALSO +mount(2), umount(2), umount(8), swapon(8) diff --git a/ubase/mount.c b/ubase/mount.c @@ -0,0 +1,239 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <limits.h> +#include <mntent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +struct { + const char *opt; + const char *notopt; + unsigned long v; +} optnames[] = { + { "defaults", NULL, 0 }, + { "remount", NULL, MS_REMOUNT }, + { "ro", "rw", MS_RDONLY }, + { "sync", "async", MS_SYNCHRONOUS }, + { "dirsync", NULL, MS_DIRSYNC }, + { "nodev", "dev", MS_NODEV }, + { "noatime", "atime", MS_NOATIME }, + { "nodiratime", "diratime", MS_NODIRATIME }, + { "noexec", "exec", MS_NOEXEC }, + { "nosuid", "suid", MS_NOSUID }, + { "mand", "nomand", MS_MANDLOCK }, + { "relatime", "norelatime", MS_RELATIME }, + { "bind", NULL, MS_BIND }, + { NULL, NULL, 0 } +}; + +static void +parseopts(char *popts, unsigned long *flags, char *data, size_t datasiz) +{ + unsigned int i, validopt; + size_t optlen, dlen = 0; + char *name; + + data[0] = '\0'; + for (name = strtok(popts, ","); name; name = strtok(NULL, ",")) { + validopt = 0; + for (i = 0; optnames[i].opt; i++) { + if (optnames[i].opt && strcmp(name, optnames[i].opt) == 0) { + *flags |= optnames[i].v; + validopt = 1; + break; + } + if (optnames[i].notopt && strcmp(name, optnames[i].notopt) == 0) { + *flags &= ~optnames[i].v; + validopt = 1; + break; + } + } + if (!validopt) { + /* unknown option, pass as data option to mount() */ + if ((optlen = strlen(name))) { + if (dlen + optlen + 2 >= datasiz) + return; /* prevent overflow */ + if (dlen) + data[dlen++] = ','; + memcpy(&data[dlen], name, optlen); + dlen += optlen; + data[dlen] = '\0'; + } + } + } +} + +static int +mounted(const char *dir) +{ + FILE *fp; + struct mntent *me; + struct stat st1, st2; + + if (stat(dir, &st1) < 0) { + weprintf("stat %s:", dir); + return 0; + } + fp = setmntent("/proc/mounts", "r"); + if (!fp) + eprintf("setmntent %s:", "/proc/mounts"); + while ((me = getmntent(fp)) != NULL) { + if (stat(me->mnt_dir, &st2) < 0) { + weprintf("stat %s:", me->mnt_dir); + continue; + } + if (st1.st_dev == st2.st_dev && + st1.st_ino == st2.st_ino) + return 1; + } + endmntent(fp); + return 0; +} + +static void +usage(void) +{ + eprintf("usage: %s [-BMRan] [-t fstype] [-o options] [source] [target]\n", + argv0); +} + +static int +catfile(FILE *in, FILE *out) +{ + char buf[BUFSIZ]; + size_t bytesread; + + while (!feof(in)) { + bytesread = fread(buf, 1, sizeof(buf), in); + if (ferror(in)) + return 0; + fwrite(buf, 1, bytesread, out); + } + return 1; +} + +int +main(int argc, char *argv[]) +{ + int aflag = 0, oflag = 0, status = 0, i; + unsigned long flags = 0; + char *types = NULL, data[512] = "", *resolvpath = NULL; + char *files[] = { "/proc/mounts", "/etc/fstab", NULL }; + size_t datasiz = sizeof(data); + const char *source, *target; + struct mntent *me = NULL; + FILE *fp; + + ARGBEGIN { + case 'B': + flags |= MS_BIND; + break; + case 'M': + flags |= MS_MOVE; + break; + case 'R': + flags |= MS_REC; + break; + case 'a': + aflag = 1; + break; + case 'o': + oflag = 1; + parseopts(EARGF(usage()), &flags, data, datasiz); + break; + case 't': + types = EARGF(usage()); + break; + case 'n': + break; + default: + usage(); + } ARGEND; + + if (argc < 1 && aflag == 0) { + if (!(fp = fopen(files[0], "r"))) + eprintf("fopen %s:", files[0]); + if (catfile(fp, stdout) != 1) { + weprintf("error while reading %s:", files[0]); + status = 1; + } + fclose(fp); + return status; + } + + if (aflag == 1) + goto mountall; + + source = argv[0]; + target = argv[1]; + + if (!target) { + target = argv[0]; + source = NULL; + if (!(resolvpath = realpath(target, NULL))) + eprintf("realpath %s:", target); + target = resolvpath; + } + + for (i = 0; files[i]; i++) { + if (!(fp = setmntent(files[i], "r"))) { + if (strcmp(files[i], "/proc/mounts") != 0) + weprintf("setmntent %s:", files[i]); + continue; + } + while ((me = getmntent(fp))) { + if (strcmp(me->mnt_dir, target) == 0 || + strcmp(me->mnt_fsname, target) == 0 || + (source && strcmp(me->mnt_dir, source) == 0) || + (source && strcmp(me->mnt_fsname, source) == 0)) { + if (!source) { + target = me->mnt_dir; + source = me->mnt_fsname; + } + if (!oflag) + parseopts(me->mnt_opts, &flags, data, datasiz); + if (!types) + types = me->mnt_type; + goto mountsingle; + } + } + endmntent(fp); + fp = NULL; + } + if (!source) + eprintf("can't find %s in /etc/fstab\n", target); + +mountsingle: + if (mount(source, target, types, flags, data) < 0) { + weprintf("mount: %s:", source); + status = 1; + } + if (fp) + endmntent(fp); + free(resolvpath); + return status; + +mountall: + if (!(fp = setmntent("/etc/fstab", "r"))) + eprintf("setmntent %s:", "/etc/fstab"); + while ((me = getmntent(fp))) { + flags = 0; + parseopts(me->mnt_opts, &flags, data, datasiz); + if (mount(me->mnt_fsname, me->mnt_dir, me->mnt_type, flags, data) < 0) { + if (mounted(me->mnt_dir) == 0) { + weprintf("mount: %s:", me->mnt_fsname); + status = 1; + } + } + } + endmntent(fp); + + return status; +} diff --git a/ubase/mountpoint.1 b/ubase/mountpoint.1 @@ -0,0 +1,22 @@ +.TH MOUNTPOINT 1 ubase-VERSION +.SH NAME +\fBmountpoint\fR - Check if a directory is a mountpoint +.SH SYNOPSIS +\fBmountpoint\fR [\fB-dq\fR] \fIdirectory\fR +.TP +\fBmountpoint\fR \fB-x\fR \fIdevice\fR +.SH DESCRIPTION +\fBmountpoint\fR checks if the \fIdirectory\fR is mentioned in the +/proc/mounts file. +.SH OPTIONS +.TP +\fB-d\fR +Print the major/minor device number of the filesystem on stdout. +.TP +\fB-q\fR +Be quiet, don't print anything. +.TP +\fB-x\fR +Print the major/minor device number of the \fIdevice\fR on stdout. +.SH SEE ALSO +mount(8) diff --git a/ubase/mountpoint.c b/ubase/mountpoint.c @@ -0,0 +1,86 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> +#include <sys/types.h> + +#include <mntent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-dqx] target\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int qflag = 0, dflag = 0, xflag = 0; + struct mntent *me = NULL; + FILE *fp; + int ret = 0; + struct stat st1, st2; + + ARGBEGIN { + case 'q': + qflag = 1; + break; + case 'd': + dflag = 1; + break; + case 'x': + xflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + if (stat(argv[0], &st1) < 0) + eprintf("stat %s:", argv[0]); + + if (xflag) { + if (!S_ISBLK(st1.st_mode)) + eprintf("stat: %s: not a block device\n", + argv[0]); + printf("%u:%u\n", major(st1.st_rdev), + minor(st1.st_rdev)); + return 0; + } + + if (!S_ISDIR(st1.st_mode)) + eprintf("stat %s: not a directory\n", argv[0]); + + if (dflag) { + printf("%u:%u\n", major(st1.st_dev), + minor(st1.st_dev)); + return 0; + } + + fp = setmntent("/proc/mounts", "r"); + if (!fp) + eprintf("setmntent %s:", "/proc/mounts"); + while ((me = getmntent(fp)) != NULL) { + if (stat(me->mnt_dir, &st2) < 0) + eprintf("stat %s:", me->mnt_dir); + if (st1.st_dev == st2.st_dev && + st1.st_ino == st2.st_ino) + break; + } + endmntent(fp); + + if (me == NULL) + ret = 1; + + if (!qflag) + printf("%s %s a mountpoint\n", argv[0], + !ret ? "is" : "is not"); + + return ret; +} diff --git a/ubase/pagesize.1 b/ubase/pagesize.1 @@ -0,0 +1,8 @@ +.TH PAGESIZE 1 ubase-VERSION +.SH NAME +\fBpagesize\fR - Print system page size +.SH SYNOPSIS +\fBpagesize\fR +.SH DESCRIPTION +\fBpagesize\fR prints the size of a page of memory in bytes. This program is +useful in constructing portable shell scripts. diff --git a/ubase/pagesize.c b/ubase/pagesize.c @@ -0,0 +1,32 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + long pagesz; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + pagesz = sysconf(_SC_PAGESIZE); + if (pagesz <= 0) { + pagesz = sysconf(_SC_PAGE_SIZE); + if (pagesz <= 0) + eprintf("can't determine pagesize\n"); + } + printf("%ld\n", pagesz); + return 0; +} diff --git a/ubase/passwd.1 b/ubase/passwd.1 @@ -0,0 +1,11 @@ +.TH PASSWD 1 ubase-VERSION +.SH NAME +\fBpasswd\fR - Change a user's password +.SH SYNOPSIS +\fBpasswd\fR [\fIusername\fR] +.SH DESCRIPTION +\fBpasswd\fR changes the user's password. The user is prompted +for their current password. If the current password is correctly typed, +a new password is requested. The new password must be entered twice to +avoid typing errors. The superuser is not required to provide a user's +current password. diff --git a/ubase/passwd.c b/ubase/passwd.c @@ -0,0 +1,257 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <pwd.h> +#include <shadow.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "passwd.h" +#include "text.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [username]\n", argv0); +} + +static FILE * +spw_get_file(const char *user) +{ + FILE *fp = NULL; + char file[PATH_MAX]; + int r; + + r = snprintf(file, sizeof(file), "/etc/tcb/%s/shadow", user); + if (r < 0 || (size_t)r >= sizeof(file)) + eprintf("snprintf:"); + fp = fopen(file, "r+"); + if (!fp) + fp = fopen("/etc/shadow", "r+"); + return fp; +} + +static int +spw_write_file(FILE *fp, const struct spwd *spw, char *pwhash) +{ + struct spwd *spwent; + int r = -1, w = 0; + FILE *tfp = NULL; + + /* write to temporary file. */ + tfp = tmpfile(); + if (!tfp) { + weprintf("tmpfile:"); + goto cleanup; + } + while ((spwent = fgetspent(fp))) { + /* update entry on name match */ + if (strcmp(spwent->sp_namp, spw->sp_namp) == 0) { + spwent->sp_pwdp = pwhash; + w++; + } + errno = 0; + if (putspent(spwent, tfp) == -1) { + weprintf("putspent:"); + goto cleanup; + } + } + if (!w) { + weprintf("shadow: no matching entry to write to\n"); + goto cleanup; + } + fflush(tfp); + + if (fseek(fp, 0, SEEK_SET) == -1 || fseek(tfp, 0, SEEK_SET) == -1) { + weprintf("fseek:"); + goto cleanup; + } + + /* write temporary file to (tcb) shadow file */ + concat(tfp, "tmpfile", fp, "shadow"); + ftruncate(fileno(fp), ftell(tfp)); + + r = 0; /* success */ +cleanup: + if (tfp) + fclose(tfp); + return r; +} + +static +int pw_write_file(FILE *fp, const struct passwd *pw, char *pwhash) { + struct passwd *pwent; + int r = -1, w = 0; + FILE *tfp = NULL; + + /* write to temporary file. */ + tfp = tmpfile(); + if (!tfp) { + weprintf("tmpfile:"); + goto cleanup; + } + while ((pwent = fgetpwent(fp))) { + /* update entry on name match */ + if (strcmp(pwent->pw_name, pw->pw_name) == 0) { + pwent->pw_passwd = pwhash; + w++; + } + errno = 0; + if (putpwent(pwent, tfp) == -1) { + weprintf("putpwent:"); + goto cleanup; + } + } + if (!w) { + weprintf("passwd: no matching entry to write to\n"); + goto cleanup; + } + fflush(tfp); + + if (fseek(fp, 0, SEEK_SET) == -1 || fseek(tfp, 0, SEEK_SET) == -1) { + weprintf("fseek:"); + goto cleanup; + } + + /* write to passwd file. */ + concat(tfp, "tmpfile", fp, "passwd"); + ftruncate(fileno(fp), ftell(tfp)); + + r = 0; /* success */ +cleanup: + if (tfp) + fclose(tfp); + return r; +} + +int +main(int argc, char *argv[]) +{ + char *cryptpass1 = NULL, *cryptpass2 = NULL, *cryptpass3 = NULL; + char *inpass, *p, *salt = PW_CIPHER, *prevhash = NULL; + struct passwd *pw; + struct spwd *spw = NULL; + FILE *fp = NULL; + int r = -1, status = 1; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + pw_init(); + umask(077); + + errno = 0; + if (argc == 0) + pw = getpwuid(getuid()); + else + pw = getpwnam(argv[0]); + if (!pw) { + if (errno) + eprintf("getpwnam: %s:", argv[0]); + else + eprintf("who are you?\n"); + } + + /* is using shadow entry ? */ + if (pw->pw_passwd[0] == 'x' && pw->pw_passwd[1] == '\0') { + errno = 0; + spw = getspnam(pw->pw_name); + if (!spw) { + if (errno) + eprintf("getspnam: %s:", pw->pw_name); + else + eprintf("who are you?\n"); + } + } + + /* Flush pending input */ + ioctl(0, TCFLSH, (void *)0); + + if (getuid() == 0) { + goto newpass; + } else { + if (pw->pw_passwd[0] == '!' || + pw->pw_passwd[0] == '*') + eprintf("denied\n"); + if (pw->pw_passwd[0] == '\0') { + goto newpass; + } + if (pw->pw_passwd[0] == 'x') + prevhash = salt = spw->sp_pwdp; + else + prevhash = salt = pw->pw_passwd; + } + + printf("Changing password for %s\n", pw->pw_name); + inpass = getpass("Old password: "); + if (!inpass) + eprintf("getpass:"); + if (inpass[0] == '\0') + eprintf("no password supplied\n"); + p = crypt(inpass, salt); + if (!p) + eprintf("crypt:"); + cryptpass1 = estrdup(p); + if (strcmp(cryptpass1, prevhash) != 0) + eprintf("incorrect password\n"); + +newpass: + inpass = getpass("Enter new password: "); + if (!inpass) + eprintf("getpass:"); + if (inpass[0] == '\0') + eprintf("no password supplied\n"); + p = crypt(inpass, salt); + if (!p) + eprintf("crypt:"); + cryptpass2 = estrdup(p); + if (cryptpass1 && strcmp(cryptpass1, cryptpass2) == 0) + eprintf("password left unchanged\n"); + + /* Flush pending input */ + ioctl(0, TCFLSH, (void *)0); + + inpass = getpass("Retype new password: "); + if (!inpass) + eprintf("getpass:"); + if (inpass[0] == '\0') + eprintf("no password supplied\n"); + p = crypt(inpass, salt); + if (!p) + eprintf("crypt:"); + cryptpass3 = estrdup(p); + if (strcmp(cryptpass2, cryptpass3) != 0) + eprintf("passwords don't match\n"); + + fp = spw_get_file(pw->pw_name); + if (fp) { + r = spw_write_file(fp, spw, cryptpass3); + } else { + fp = fopen("/etc/passwd", "r+"); + if (fp) + r = pw_write_file(fp, pw, cryptpass3); + else + weprintf("fopen:"); + } + if (!r) + status = 0; + + if (fp) + fclose(fp); + free(cryptpass3); + free(cryptpass2); + free(cryptpass1); + + return status; +} diff --git a/ubase/passwd.h b/ubase/passwd.h @@ -0,0 +1,4 @@ +/* See LICENSE file for copyright and license details. */ +/* passwd.c */ +int pw_check(const struct passwd *, const char *); +int pw_init(void); diff --git a/ubase/pidof.1 b/ubase/pidof.1 @@ -0,0 +1,18 @@ +.TH PIDOF 1 ubase-VERSION +.SH NAME +\fBpidof\fR - Find the process ID of a running program +.SH SYNOPSIS +\fBpidof\fR [\fB-o\fI pid1,pid2,...,pidN\fR] [\fB-s\fR] [\fIprogram...\fR] +.SH DESCRIPTION +\fBpidof\fR finds the process id's of the named programs and prints them on +stdout. +.SH OPTIONS +.TP +\fB-o\fR +Tell pidof to omit processes with that process id. The special pid +%PPID can be used to name the parent process of the pidof program. +.TP +\fB-s\fR +Single shot - this instructs the program to only return one process id. +.SH SEE ALSO +killall5(8) diff --git a/ubase/pidof.c b/ubase/pidof.c @@ -0,0 +1,120 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/types.h> + +#include <dirent.h> +#include <libgen.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "proc.h" +#include "queue.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-o pid1,pid2,...pidN] [-s] [program...]\n", argv0); +} + +struct pidentry { + pid_t pid; + TAILQ_ENTRY(pidentry) entry; +}; + +static TAILQ_HEAD(omitpid_head, pidentry) omitpid_head; + +int +main(int argc, char *argv[]) +{ + DIR *dp; + struct dirent *entry; + pid_t pid; + struct procstat ps; + char cmdline[BUFSIZ], *cmd, *cmdbase = NULL, *p, *arg = NULL; + int i, found = 0; + int sflag = 0, oflag = 0; + struct pidentry *pe, *tmp; + + ARGBEGIN { + case 's': + sflag = 1; + break; + case 'o': + oflag = 1; + arg = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + + if (!argc) + return 1; + + TAILQ_INIT(&omitpid_head); + + for (p = strtok(arg, ","); p; p = strtok(NULL, ",")) { + pe = emalloc(sizeof(*pe)); + if (strcmp(p, "%PPID") == 0) + pe->pid = getppid(); + else + pe->pid = estrtol(p, 10); + TAILQ_INSERT_TAIL(&omitpid_head, pe, entry); + } + + if (!(dp = opendir("/proc"))) + eprintf("opendir /proc:"); + + while ((entry = readdir(dp))) { + if (!pidfile(entry->d_name)) + continue; + pid = estrtol(entry->d_name, 10); + if (oflag) { + TAILQ_FOREACH(pe, &omitpid_head, entry) + if (pe->pid == pid) + break; + if (pe) + continue; + } + if (parsestat(pid, &ps) < 0) + continue; + if (parsecmdline(ps.pid, cmdline, + sizeof(cmdline)) < 0) { + cmd = ps.comm; + cmdbase = cmd; + } else { + if ((p = strchr(cmdline, ' '))) + *p = '\0'; + cmd = cmdline; + cmdbase = basename(cmdline); + } + /* Workaround for login shells */ + if (cmd[0] == '-') + cmd++; + for (i = 0; i < argc; i++) { + if (strcmp(cmd, argv[i]) == 0 || + strcmp(cmdbase, argv[i]) == 0) { + putword(entry->d_name); + found++; + if (sflag) + goto out; + } + } + } + +out: + if (found) + putchar('\n'); + + closedir(dp); + + for (pe = TAILQ_FIRST(&omitpid_head); pe; pe = tmp) { + tmp = TAILQ_NEXT(pe, entry); + TAILQ_REMOVE(&omitpid_head, pe, entry); + free(pe); + } + + return 0; +} diff --git a/ubase/pivot_root.8 b/ubase/pivot_root.8 @@ -0,0 +1,8 @@ +.TH PIVOT_ROOT 8 ubase-VERSION +.SH NAME +\fBpivot_root\fR - change the root filesystem +.SH SYNOPSIS +\fBpivot_root\fI new_root put_old\fR +.SH DESCRIPTION +\fBpivot_root\fR moves the root file system of the current process to the +directory \fIput_old\fR and makes \fInew_root\fR the new root file system. diff --git a/ubase/pivot_root.c b/ubase/pivot_root.c @@ -0,0 +1,31 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/syscall.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s new-root put-old\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc < 2) + usage(); + + if (syscall(SYS_pivot_root, argv[0], argv[1]) < 0) + eprintf("pivot_root:"); + + return 0; +} diff --git a/ubase/proc.h b/ubase/proc.h @@ -0,0 +1,42 @@ +/* See LICENSE file for copyright and license details. */ +struct procstat { + int pid; + char comm[PATH_MAX + 2]; /* + 2 for '(' and ')' */ + unsigned char state; + int ppid; + int pgrp; + int sid; + int tty_nr; + int tpgid; + unsigned flags; + unsigned long minflt; + unsigned long cminflt; + unsigned long majflt; + unsigned long cmajflt; + unsigned long utime; + unsigned long stime; + long cutime; + long cstime; + long priority; + long nice; + long num_threads; + long itrealvalue; + unsigned long long starttime; + unsigned long vsize; + long rss; + long rsslim; +}; + +struct procstatus { + uid_t uid; + uid_t euid; + gid_t gid; + gid_t egid; +}; + +int parsecmdline(pid_t pid, char *buf, size_t siz); +int parsestat(pid_t pid, struct procstat *ps); +int parsestatus(pid_t pid, struct procstatus *pstatus); +int proceuid(pid_t pid, uid_t *euid); +int procuid(pid_t pid, uid_t *euid); +int pidfile(const char *file); diff --git a/ubase/ps.1 b/ubase/ps.1 @@ -0,0 +1,26 @@ +.TH PS 1 ubase-VERSION +.SH NAME +\fBps\fR - Display process status +.SH SYNOPSIS +\fBps\fR [\fB-aAdef\fR] +.SH DESCRIPTION +\fBps\fR displays information about active processes. When given no options, +\fBps\fR prints information about processes of the current user that has a +controlling terminal. +.SH OPTIONS +.TP +\fB-a\fR +Select all processes except both session leaders and processes not +associated with a terminal. +.TP +\fB-A\fR +Select all processes. Identical to \fB-e\fR. +.TP +\fB-d\fR +Select all processes except session leaders. +.TP +\fB-e\fR +Select all processes. Identical to \fB-A\fR. +.TP +\fB-f\fR +Do full-format listing. diff --git a/ubase/ps.c b/ubase/ps.c @@ -0,0 +1,188 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/ioctl.h> +#include <sys/sysinfo.h> + +#include <errno.h> +#include <libgen.h> +#include <limits.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "proc.h" +#include "util.h" + +static void psout(struct procstat *ps); +static void psr(const char *file); + +enum { + PS_aflag = 1 << 0, + PS_Aflag = 1 << 1, + PS_dflag = 1 << 2, + PS_fflag = 1 << 3 +}; + +static void +usage(void) +{ + eprintf("usage: [-aAdef] %s\n", argv0); +} + +static int flags; + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + case 'a': + flags |= PS_aflag; + break; + case 'A': + flags |= PS_Aflag; + break; + case 'd': + flags |= PS_dflag; + break; + case 'e': + flags |= PS_Aflag; + break; + case 'f': + flags |= PS_fflag; + break; + default: + usage(); + } ARGEND; + + if (!(flags & PS_fflag)) + printf(" PID TTY TIME CMD\n"); + else + printf("UID PID PPID C STIME TTY TIME CMD\n"); + recurse("/proc", psr); + return 0; +} + +static void +psout(struct procstat *ps) +{ + struct procstatus pstatus; + char cmdline[BUFSIZ], *cmd; + char buf[BUFSIZ]; + char *ttystr, *myttystr; + int tty_maj, tty_min; + uid_t myeuid; + unsigned sutime; + time_t start; + char stimestr[sizeof("%H:%M")]; + struct sysinfo info; + struct passwd *pw; + struct winsize w; + + /* Ignore session leaders */ + if (flags & PS_dflag) + if (ps->pid == ps->sid) + return; + + devtotty(ps->tty_nr, &tty_maj, &tty_min); + ttystr = ttytostr(tty_maj, tty_min); + + /* Only print processes that are associated with + * a terminal and they are not session leaders */ + if (flags & PS_aflag) { + if (ps->pid == ps->sid || ttystr[0] == '?') { + free(ttystr); + return; + } + } + + if (parsestatus(ps->pid, &pstatus) < 0) + return; + + /* This is the default case, only print processes that have + * the same controlling terminal as the invoker and the same + * euid as the current user */ + if (!(flags & (PS_aflag | PS_Aflag | PS_dflag))) { + myttystr = ttyname(0); + if (myttystr) { + if (strcmp(myttystr + strlen("/dev/"), ttystr)) { + free(ttystr); + return; + } + } else { + /* The invoker has no controlling terminal - just + * go ahead and print the processes anyway */ + ttystr[0] = '?'; + ttystr[1] = '\0'; + } + myeuid = geteuid(); + if (myeuid != pstatus.euid) { + free(ttystr); + return; + } + } + + sutime = (ps->stime + ps->utime) / sysconf(_SC_CLK_TCK); + + ioctl(1, TIOCGWINSZ, &w); + if (!(flags & PS_fflag)) { + snprintf(buf, sizeof(buf), "%5d %-6s %02u:%02u:%02u %s", ps->pid, ttystr, + sutime / 3600, (sutime % 3600) / 60, sutime % 60, + ps->comm); + if (w.ws_col) + printf("%.*s\n", w.ws_col, buf); + else + printf("%s\n", buf); + } else { + errno = 0; + pw = getpwuid(pstatus.uid); + if (!pw) + eprintf("getpwuid %d:", pstatus.uid); + + if (sysinfo(&info) < 0) + eprintf("sysinfo:"); + + start = time(NULL) - info.uptime; + start += (ps->starttime / sysconf(_SC_CLK_TCK)); + strftime(stimestr, sizeof(stimestr), + "%H:%M", localtime(&start)); + + /* For kthreads/zombies /proc/<pid>/cmdline will be + * empty so use ps->comm in that case */ + if (parsecmdline(ps->pid, cmdline, sizeof(cmdline)) < 0) + cmd = ps->comm; + else + cmd = cmdline; + + snprintf(buf, sizeof(buf), "%-8s %5d %5d ? %5s %-5s %02u:%02u:%02u %s%s%s", + pw->pw_name, ps->pid, + ps->ppid, stimestr, ttystr, + sutime / 3600, (sutime % 3600) / 60, sutime % 60, + (cmd == ps->comm) ? "[" : "", cmd, + (cmd == ps->comm) ? "]" : ""); + if (w.ws_col) + printf("%.*s\n", w.ws_col, buf); + else + printf("%s\n", buf); + } + free(ttystr); +} + +static void +psr(const char *file) +{ + char path[PATH_MAX], *p; + struct procstat ps; + pid_t pid; + + if (strlcpy(path, file, sizeof(path)) >= sizeof(path)) + eprintf("path too long\n"); + p = basename(path); + if (pidfile(p) == 0) + return; + pid = estrtol(p, 10); + if (parsestat(pid, &ps) < 0) + return; + psout(&ps); +} diff --git a/ubase/queue.h b/ubase/queue.h @@ -0,0 +1,648 @@ +/* $OpenBSD: queue.h,v 1.38 2013/07/03 15:05:21 fgsch Exp $ */ +/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft 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. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues, and circular queues. + * + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC)) +#define _Q_INVALIDATE(a) (a) = ((void *)-1) +#else +#define _Q_INVALIDATE(a) +#endif + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List access methods. + */ +#define SLIST_FIRST(head) ((head)->slh_first) +#define SLIST_END(head) NULL +#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_FOREACH(var, head, field) \ + for((var) = SLIST_FIRST(head); \ + (var) != SLIST_END(head); \ + (var) = SLIST_NEXT(var, field)) + +#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SLIST_FIRST(head); \ + (var) && ((tvar) = SLIST_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * Singly-linked List functions. + */ +#define SLIST_INIT(head) { \ + SLIST_FIRST(head) = SLIST_END(head); \ +} + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (0) + +#define SLIST_REMOVE_AFTER(elm, field) do { \ + (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->slh_first; \ + \ + while (curelm->field.sle_next != (elm)) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + _Q_INVALIDATE((elm)->field.sle_next); \ + } \ +} while (0) + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List access methods + */ +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_END(head) NULL +#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_FOREACH(var, head, field) \ + for((var) = LIST_FIRST(head); \ + (var)!= LIST_END(head); \ + (var) = LIST_NEXT(var, field)) + +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST(head); \ + (var) && ((tvar) = LIST_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * List functions. + */ +#define LIST_INIT(head) do { \ + LIST_FIRST(head) = LIST_END(head); \ +} while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (0) + +#define LIST_REMOVE(elm, field) do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +#define LIST_REPLACE(elm, elm2, field) do { \ + if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ + (elm2)->field.le_next->field.le_prev = \ + &(elm2)->field.le_next; \ + (elm2)->field.le_prev = (elm)->field.le_prev; \ + *(elm2)->field.le_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue access methods. + */ +#define SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define SIMPLEQ_END(head) NULL +#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) +#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + +#define SIMPLEQ_FOREACH(var, head, field) \ + for((var) = SIMPLEQ_FIRST(head); \ + (var) != SIMPLEQ_END(head); \ + (var) = SIMPLEQ_NEXT(var, field)) + +#define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SIMPLEQ_FIRST(head); \ + (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * Simple queue functions. + */ +#define SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (0) + +#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ + if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \ + == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +/* + * XOR Simple queue definitions. + */ +#define XSIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqx_first; /* first element */ \ + struct type **sqx_last; /* addr of last next element */ \ + unsigned long sqx_cookie; \ +} + +#define XSIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqx_next; /* next element */ \ +} + +/* + * XOR Simple queue access methods. + */ +#define XSIMPLEQ_XOR(head, ptr) ((__typeof(ptr))((head)->sqx_cookie ^ \ + (unsigned long)(ptr))) +#define XSIMPLEQ_FIRST(head) XSIMPLEQ_XOR(head, ((head)->sqx_first)) +#define XSIMPLEQ_END(head) NULL +#define XSIMPLEQ_EMPTY(head) (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head)) +#define XSIMPLEQ_NEXT(head, elm, field) XSIMPLEQ_XOR(head, ((elm)->field.sqx_next)) + + +#define XSIMPLEQ_FOREACH(var, head, field) \ + for ((var) = XSIMPLEQ_FIRST(head); \ + (var) != XSIMPLEQ_END(head); \ + (var) = XSIMPLEQ_NEXT(head, var, field)) + +#define XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = XSIMPLEQ_FIRST(head); \ + (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1); \ + (var) = (tvar)) + +/* + * XOR Simple queue functions. + */ +#define XSIMPLEQ_INIT(head) do { \ + arc4random_buf(&(head)->sqx_cookie, sizeof((head)->sqx_cookie)); \ + (head)->sqx_first = XSIMPLEQ_XOR(head, NULL); \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ +} while (0) + +#define XSIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqx_next = (head)->sqx_first) == \ + XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ + (head)->sqx_first = XSIMPLEQ_XOR(head, (elm)); \ +} while (0) + +#define XSIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL); \ + *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = XSIMPLEQ_XOR(head, (elm)); \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ +} while (0) + +#define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqx_next = (listelm)->field.sqx_next) == \ + XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ + (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm)); \ +} while (0) + +#define XSIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqx_first = XSIMPLEQ_XOR(head, \ + (head)->sqx_first)->field.sqx_next) == XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ +} while (0) + +#define XSIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ + if (((elm)->field.sqx_next = XSIMPLEQ_XOR(head, \ + (elm)->field.sqx_next)->field.sqx_next) \ + == XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = \ + XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ +} while (0) + + +/* + * Tail queue definitions. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ +} + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first } + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/* + * tail queue access methods + */ +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_END(head) NULL +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +/* XXX */ +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define TAILQ_EMPTY(head) \ + (TAILQ_FIRST(head) == TAILQ_END(head)) + +#define TAILQ_FOREACH(var, head, field) \ + for((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_NEXT(var, field)) + +#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head) && \ + ((tvar) = TAILQ_NEXT(var, field), 1); \ + (var) = (tvar)) + + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_PREV(var, headname, field)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head) && \ + ((tvar) = TAILQ_PREV(var, headname, field), 1); \ + (var) = (tvar)) + +/* + * Tail queue functions. + */ +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +#define TAILQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +/* + * Circular queue definitions. + */ +#define CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define CIRCLEQ_HEAD_INITIALIZER(head) \ + { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } + +#define CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue access methods + */ +#define CIRCLEQ_FIRST(head) ((head)->cqh_first) +#define CIRCLEQ_LAST(head) ((head)->cqh_last) +#define CIRCLEQ_END(head) ((void *)(head)) +#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) +#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) +#define CIRCLEQ_EMPTY(head) \ + (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head)) + +#define CIRCLEQ_FOREACH(var, head, field) \ + for((var) = CIRCLEQ_FIRST(head); \ + (var) != CIRCLEQ_END(head); \ + (var) = CIRCLEQ_NEXT(var, field)) + +#define CIRCLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = CIRCLEQ_FIRST(head); \ + (var) != CIRCLEQ_END(head) && \ + ((tvar) = CIRCLEQ_NEXT(var, field), 1); \ + (var) = (tvar)) + +#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ + for((var) = CIRCLEQ_LAST(head); \ + (var) != CIRCLEQ_END(head); \ + (var) = CIRCLEQ_PREV(var, field)) + +#define CIRCLEQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = CIRCLEQ_LAST(head, headname); \ + (var) != CIRCLEQ_END(head) && \ + ((tvar) = CIRCLEQ_PREV(var, headname, field), 1); \ + (var) = (tvar)) + +/* + * Circular queue functions. + */ +#define CIRCLEQ_INIT(head) do { \ + (head)->cqh_first = CIRCLEQ_END(head); \ + (head)->cqh_last = CIRCLEQ_END(head); \ +} while (0) + +#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = CIRCLEQ_END(head); \ + if ((head)->cqh_last == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.cqe_next = CIRCLEQ_END(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} while (0) + +#define CIRCLEQ_REMOVE(head, elm, field) do { \ + if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ + _Q_INVALIDATE((elm)->field.cqe_prev); \ + _Q_INVALIDATE((elm)->field.cqe_next); \ +} while (0) + +#define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \ + CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm2); \ + else \ + (elm2)->field.cqe_next->field.cqe_prev = (elm2); \ + if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \ + CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm2); \ + else \ + (elm2)->field.cqe_prev->field.cqe_next = (elm2); \ + _Q_INVALIDATE((elm)->field.cqe_prev); \ + _Q_INVALIDATE((elm)->field.cqe_next); \ +} while (0) + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/ubase/readahead.8 b/ubase/readahead.8 @@ -0,0 +1,11 @@ +.TH READAHEAD 8 ubase-VERSION +.SH NAME +\fBreadahead\fR - Preload files into disk cache +.SH SYNOPSIS +\fBreadahead\fR \fIfile...\fR +.SH DESCRIPTION +\fBreadahead\fR preloads files into the kernel's disk cache. The +number of pages preloaded depends on the kernel but it is usually around +2MB. +.SH SEE ALSO +readahead(2) diff --git a/ubase/readahead.c b/ubase/readahead.c @@ -0,0 +1,38 @@ +/* See LICENSE file for copyright and license details. */ +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s file...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc == 0) + usage(); + + for (; argc > 0; argc--, argv++) { + if (!(fp = fopen(argv[0], "r"))) { + weprintf("fopen %s:", argv[0]); + continue; + } + if (readahead(fileno(fp), 0, -1) < 0) + weprintf("readahead %s:", argv[0]); + fclose(fp); + } + return 0; +} diff --git a/ubase/reboot.h b/ubase/reboot.h @@ -0,0 +1,32 @@ +/* + * Magic values required to use _reboot() system call. + */ + +#define LINUX_REBOOT_MAGIC1 0xfee1dead +#define LINUX_REBOOT_MAGIC2 672274793 +#define LINUX_REBOOT_MAGIC2A 85072278 +#define LINUX_REBOOT_MAGIC2B 369367448 +#define LINUX_REBOOT_MAGIC2C 537993216 + + +/* + * Commands accepted by the _reboot() system call. + * + * RESTART Restart system using default command and mode. + * HALT Stop OS and give system control to ROM monitor, if any. + * CAD_ON Ctrl-Alt-Del sequence causes RESTART command. + * CAD_OFF Ctrl-Alt-Del sequence sends SIGINT to init task. + * POWER_OFF Stop OS and remove all power from system, if possible. + * RESTART2 Restart system using given command string. + * SW_SUSPEND Suspend system using software suspend if compiled in. + * KEXEC Restart system using a previously loaded Linux kernel + */ + +#define LINUX_REBOOT_CMD_RESTART 0x01234567 +#define LINUX_REBOOT_CMD_HALT 0xCDEF0123 +#define LINUX_REBOOT_CMD_CAD_ON 0x89ABCDEF +#define LINUX_REBOOT_CMD_CAD_OFF 0x00000000 +#define LINUX_REBOOT_CMD_POWER_OFF 0x4321FEDC +#define LINUX_REBOOT_CMD_RESTART2 0xA1B2C3D4 +#define LINUX_REBOOT_CMD_SW_SUSPEND 0xD000FCE2 +#define LINUX_REBOOT_CMD_KEXEC 0x45584543 diff --git a/ubase/respawn.1 b/ubase/respawn.1 @@ -0,0 +1,18 @@ +.TH RESPAWN 1 ubase-VERSION +.SH NAME +\fBrespawn\fR - Spawn the given command repeatedly +.SH SYNOPSIS +\fBrespawn\fR [\fB-l\fI fifo\fR] [\fB-d\fI N\fR] \fIcmd\fR [\fIargs...\fR] +.SH DESCRIPTION +\fBrespawn\fR spawns the given \fIcmd\fR in a new session +repeatedly. +.SH OPTIONS +.TP +\fB-d\fR +Set the delay between invocations of \fIcmd\fR. It defaults to 0. +.TP +\fB-l\fR +Listen on the specified \fIfifo\fR for writes. For each write +spawn a new instance of \fIcmd\fR. This can be used in conjunction +with a process supervisor to restart a particular program. The \fB-l\fR +and \fB-d\fR options are incompatible. All writes are discarded. diff --git a/ubase/respawn.c b/ubase/respawn.c @@ -0,0 +1,106 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/select.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +sigterm(int sig) +{ + if (sig == SIGTERM) { + kill(0, SIGTERM); + _exit(0); + } +} + +static void +usage(void) +{ + eprintf("usage: respawn [-l fifo] [-d N] cmd [args...]\n"); +} + +int +main(int argc, char *argv[]) +{ + char *fifo = NULL; + unsigned int delay = 0; + pid_t pid; + char buf[BUFSIZ]; + int savederrno; + int fd; + ssize_t n; + fd_set rdfd; + + ARGBEGIN { + case 'd': + delay = estrtol(EARGF(usage()), 0); + break; + case 'l': + fifo = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + if (fifo && delay > 0) + usage(); + + setsid(); + + signal(SIGTERM, sigterm); + + if (fifo) { + /* TODO: we should use O_RDONLY and re-open the fd on EOF */ + fd = open(fifo, O_RDWR | O_NONBLOCK); + if (fd < 0) + eprintf("open %s:", fifo); + } + + while (1) { + if (fifo) { + FD_ZERO(&rdfd); + FD_SET(fd, &rdfd); + n = select(fd + 1, &rdfd, NULL, NULL, NULL); + if (n < 0) + eprintf("select:"); + if (n == 0 || FD_ISSET(fd, &rdfd) == 0) + continue; + while ((n = read(fd, buf, sizeof(buf))) > 0) + ; + if (n < 0) + if (errno != EAGAIN) + eprintf("read %s:", fifo); + } + pid = fork(); + if (pid < 0) + eprintf("fork:"); + switch (pid) { + case 0: + execvp(argv[0], argv); + savederrno = errno; + weprintf("execvp %s:", argv[0]); + _exit(savederrno == ENOENT ? 127 : 126); + break; + default: + waitpid(pid, NULL, 0); + break; + } + if (!fifo) + sleep(delay); + } + /* not reachable */ + return 0; +} diff --git a/ubase/rmmod.8 b/ubase/rmmod.8 @@ -0,0 +1,23 @@ +.TH RMMOD 8 ubase-VERSION +.SH NAME +\fBrmmod\fR - Remove a module from the Linux kernel +.SH SYNOPSIS +\fBrmmod\fR [\fB-fw\fR] \fImodule...\fR +.SH DESCRIPTION +\fBrmmod\fR removes one or more modules from the kernel. +.SH OPTIONS +.TP +\fB-f\fR +This option can be extremely dangerous: it has no effect unless +CONFIG_MODULE_FORCE_UNLOAD was set when the kernel was compiled. +With this option, you can remove modules which are being used, or +which are not designed to be removed, or have been marked as unsafe. +.TP +\fB-w\fR +Normally, \fBrmmod\fR will refuse to unload modules which are in +use. With this option, \fBrmmod\fR will isolate the module, and +wait until the module is no longer used. Noone new will be +able to use the module, but s up to you to make sure the +current users eventually finish with it. +.SH SEE ALSO +insmod(8), lsmod(8) diff --git a/ubase/rmmod.c b/ubase/rmmod.c @@ -0,0 +1,50 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/syscall.h> + +#include <fcntl.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-fw] module...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *mod, *p; + int i; + int flags = O_NONBLOCK; + + ARGBEGIN { + case 'f': + flags |= O_TRUNC; + break; + case 'w': + flags &= ~O_NONBLOCK; + break; + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + for (i = 0; i < argc; i++) { + mod = argv[i]; + p = strrchr(mod, '.'); + if (p && !strcmp(p, ".ko")) + *p = '\0'; + if (syscall(__NR_delete_module, mod, flags) < 0) + eprintf("delete_module:"); + } + + return 0; +} diff --git a/ubase/rtc.h b/ubase/rtc.h @@ -0,0 +1,20 @@ +/* + * The struct used to pass data via the following ioctl. Similar to the + * struct tm in <time.h>, but it needs to be here so that the kernel + * source is self contained, allowing cross-compiles, etc. etc. + */ + +struct rtc_time { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; +}; + +#define RTC_RD_TIME _IOR('p', 0x09, struct rtc_time) /* Read RTC time */ +#define RTC_SET_TIME _IOW('p', 0x0a, struct rtc_time) /* Set RTC time */ diff --git a/ubase/stat.1 b/ubase/stat.1 @@ -0,0 +1,14 @@ +.TH STAT 1 ubase-VERSION +.SH NAME +\fBstat\fR - Display file status +.SH SYNOPSIS +\fBstat\fR [\fB-L\fR] [\fIfile...\fR] +.SH DESCRIPTION +\fBstat\fR displays information about the given files or stdin if no files +are specified. +.SH OPTIONS +.TP +\fB-L\fR +Follow links. +.SH SEE ALSO +stat (2) diff --git a/ubase/stat.c b/ubase/stat.c @@ -0,0 +1,92 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> +#include <sys/types.h> + +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +#include "util.h" + +static void show_stat(const char *file, struct stat *st); +static void show_stat_terse(const char *file, struct stat *st); + +static void +usage(void) +{ + eprintf("usage: %s [-L] [-t] [file...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct stat st; + int i, ret = 0; + int (*fn)(const char *, struct stat *) = lstat; + char *fnname = "lstat"; + void (*showstat)(const char *, struct stat *) = show_stat; + + ARGBEGIN { + case 'L': + fn = stat; + fnname = "stat"; + break; + case 't': + showstat = show_stat_terse; + break; + default: + usage(); + } ARGEND; + + if (argc == 0) { + if (fstat(0, &st) < 0) + eprintf("stat <stdin>:"); + show_stat("<stdin>", &st); + } + + for (i = 0; i < argc; i++) { + if (fn(argv[i], &st) == -1) { + weprintf("%s %s:", fnname, argv[i]); + ret = 1; + continue; + } + showstat(argv[i], &st); + } + + return ret; +} + +static void +show_stat_terse(const char *file, struct stat *st) +{ + printf("%s ", file); + printf("%lu %lu ", (unsigned long)st->st_size, + (unsigned long)st->st_blocks); + printf("%04o %u %u ", st->st_mode & 0777, st->st_uid, st->st_gid); + printf("%llx ", (unsigned long long)st->st_dev); + printf("%lu %lu ", (unsigned long)st->st_ino, (unsigned long)st->st_nlink); + printf("%d %d ", major(st->st_rdev), minor(st->st_rdev)); + printf("%ld %ld %ld ", st->st_atime, st->st_mtime, st->st_ctime); + printf("%lu\n", (unsigned long)st->st_blksize); +} + +static void +show_stat(const char *file, struct stat *st) +{ + char buf[100]; + + printf(" File: ‘%s’\n", file); + printf(" Size: %lu\tBlocks: %lu\tIO Block: %lu\n", (unsigned long)st->st_size, + (unsigned long)st->st_blocks, (unsigned long)st->st_blksize); + printf("Device: %xh/%ud\tInode: %lu\tLinks %lu\n", major(st->st_dev), + minor(st->st_dev), (unsigned long)st->st_ino, (unsigned long)st->st_nlink); + printf("Access: %04o\tUid: %u\tGid: %u\n", st->st_mode & 0777, st->st_uid, st->st_gid); + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&st->st_atime)); + printf("Access: %s\n", buf); + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&st->st_mtime)); + printf("Modify: %s\n", buf); + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&st->st_ctime)); + printf("Change: %s\n", buf); +} diff --git a/ubase/su.1 b/ubase/su.1 @@ -0,0 +1,20 @@ +.TH SU 1 ubase-VERSION +.SH NAME +\fBsu\fR - Run a command with a substitute user and group ID +.SH SYNOPSIS +\fBsu\fR [\fB-lp\fR] [\fIusername\fR] +.SH DESCRIPTION +\fBsu\fR allows to run commands with a substitute user and group ID. +When called without arguments, su defaults to running an interactive shell +as root. For backward compatibility su defaults to not change the current +directory and to only set the environment variables \fBHOME\fR and \fBSHELL\fR +(plus \fBUSER\fR and \fBLOGNAME\fR if the target \fIusername\fR is not root). +.SH OPTIONS +.TP +\fB-l\fR +Starts the shell as login shell with an environment similar to a real +login. +.TP +\fB-p\fR +Preserves the whole environment. This option is ignored if the \fB-l\fR +option is specified. diff --git a/ubase/su.c b/ubase/su.c @@ -0,0 +1,127 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/types.h> + +#include <errno.h> +#include <grp.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "passwd.h" +#include "util.h" + +extern char **environ; + +static int dologin(struct passwd *); + +static void +usage(void) +{ + eprintf("usage: %s [-lp] [username]\n", argv0); +} + +static int lflag = 0; +static int pflag = 0; + +int +main(int argc, char *argv[]) +{ + char *usr = "root", *pass; + char *shell; + struct passwd *pw; + char *newargv[2]; + uid_t uid; + + ARGBEGIN { + case 'l': + lflag = 1; + break; + case 'p': + pflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc < 1) + ; + else if (argc == 1) + usr = argv[0]; + else + usage(); + + errno = 0; + pw = getpwnam(usr); + if (!pw) { + if (errno) + eprintf("getpwnam: %s:", usr); + else + eprintf("who are you?\n"); + } + + uid = getuid(); + if (uid) { + pass = getpass("Password: "); + if (!pass) + eprintf("getpass:"); + if (pw_check(pw, pass) <= 0) + exit(1); + } + + if (initgroups(usr, pw->pw_gid) < 0) + eprintf("initgroups:"); + if (setgid(pw->pw_gid) < 0) + eprintf("setgid:"); + if (setuid(pw->pw_uid) < 0) + eprintf("setuid:"); + + if (lflag) { + return dologin(pw); + } else { + shell = pw->pw_shell[0] == '\0' ? "/bin/sh" : pw->pw_shell; + newargv[0] = shell; + newargv[1] = NULL; + if (!pflag) { + setenv("HOME", pw->pw_dir, 1); + setenv("SHELL", shell, 1); + if (strcmp(pw->pw_name, "root") != 0) { + setenv("USER", pw->pw_name, 1); + setenv("LOGNAME", pw->pw_name, 1); + } + } + if (strcmp(pw->pw_name, "root") == 0) + setenv("PATH", ENV_SUPATH, 1); + else + setenv("PATH", ENV_PATH, 1); + execve(pflag ? getenv("SHELL") : shell, + newargv, environ); + weprintf("execve %s:", shell); + return (errno == ENOENT) ? 127 : 126; + } + return 0; +} + +static int +dologin(struct passwd *pw) +{ + char *shell = pw->pw_shell[0] == '\0' ? "/bin/sh" : pw->pw_shell; + char *term = getenv("TERM"); + clearenv(); + setenv("HOME", pw->pw_dir, 1); + setenv("SHELL", shell, 1); + setenv("USER", pw->pw_name, 1); + setenv("LOGNAME", pw->pw_name, 1); + setenv("TERM", term ? term : "linux", 1); + if (strcmp(pw->pw_name, "root") == 0) + setenv("PATH", ENV_SUPATH, 1); + else + setenv("PATH", ENV_PATH, 1); + if (chdir(pw->pw_dir) < 0) + eprintf("chdir %s:", pw->pw_dir); + execlp(shell, shell, "-l", NULL); + weprintf("execlp %s:", shell); + return (errno == ENOENT) ? 127 : 126; +} diff --git a/ubase/swaplabel.8 b/ubase/swaplabel.8 @@ -0,0 +1,11 @@ +.TH SWAPLABEL 8 ubase-VERSION +.SH NAME +\fBswaplabel\fR - set the label of a swap filesystem +.SH SYNOPSIS +\fBswaplabel\fR [\fB-L\fI label\fR] \fIdevice\fR +.SH DESCRIPTION +\fBswaplabel\fR is used to change the label of a swap device or file. +.SH OPTIONS +.TP +\fB-L\fR +Change the label. diff --git a/ubase/swaplabel.c b/ubase/swaplabel.c @@ -0,0 +1,80 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/types.h> + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +#define SWAP_MAGIC1 "SWAPSPACE2" +#define SWAP_MAGIC2 "SWAP-SPACE" +#define SWAP_MAGIC_LENGTH (10) +#define SWAP_MAGIC_OFFSET (sysconf(_SC_PAGESIZE) - SWAP_MAGIC_LENGTH) +#define SWAP_LABEL_LENGTH (16) +#define SWAP_LABEL_OFFSET (1024 + 4 + 4 + 4 + 16) + +static void +usage(void) +{ + eprintf("usage: %s [-L label] device\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int setlabel = 0; + int fd; + char magic[SWAP_MAGIC_LENGTH]; + char *label; + char *device; + int i; + + ARGBEGIN { + case 'L': + setlabel = 1; + label = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + device = argv[0]; + + fd = open(device, O_RDWR); + if (fd < 0) + eprintf("open %s:", device); + + if (lseek(fd, SWAP_MAGIC_OFFSET, SEEK_SET) != SWAP_MAGIC_OFFSET) + eprintf("failed seeking to magic position:"); + if (read(fd, magic, SWAP_MAGIC_LENGTH) != SWAP_MAGIC_LENGTH) + eprintf("reading magic failed:"); + if (memcmp(magic, SWAP_MAGIC1, 10) && memcmp(magic, SWAP_MAGIC2, 10)) + eprintf("%s: is not a swap partition\n", device); + if (lseek(fd, SWAP_LABEL_OFFSET, SEEK_SET) != SWAP_LABEL_OFFSET) + eprintf("failed seeking to label position:"); + + if (!setlabel) { + label = emalloc(SWAP_LABEL_LENGTH); + if (read(fd, label, SWAP_LABEL_LENGTH) != SWAP_LABEL_LENGTH) + eprintf("reading label failed:"); + for (i = 0; i < SWAP_LABEL_LENGTH && label[i] != '\0'; i++) + if (i == (SWAP_LABEL_LENGTH - 1) && label[i] != '\0') + eprintf("invalid label\n"); + printf("label: %s\n", label); + free(label); + } else { + if (strlen(label) + 1 > SWAP_LABEL_LENGTH) + eprintf("label too long\n"); + if (write(fd, label, strlen(label) + 1) != (ssize_t)strlen(label) + 1) + eprintf("writing label failed:"); + } + + fsync(fd); + close(fd); + return 0; +} diff --git a/ubase/swapoff.8 b/ubase/swapoff.8 @@ -0,0 +1,11 @@ +.TH SWAPOFF 8 ubase-VERSION +.SH NAME +\fBswapoff\fR - disable devices and files for paging and swapping +.SH SYNOPSIS +\fBswapoff\fR [\fB-a\fI device\fR] +.SH DESCRIPTION +\fBswapoff\fR disables swapping on the specified devices and files. +.SH OPTIONS +.TP +\fB-a\fR +Disable swapping on all known swap devices and files as found in /etc/fstab. diff --git a/ubase/swapoff.c b/ubase/swapoff.c @@ -0,0 +1,60 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/swap.h> + +#include <mntent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-a] device\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int i; + int ret = 0; + int all = 0; + + ARGBEGIN { + case 'a': + all = 1; + break; + default: + usage(); + } ARGEND; + + if (!all && argc < 1) + usage(); + + if (all) { + struct mntent *me = NULL; + FILE *fp; + + fp = setmntent("/etc/fstab", "r"); + if (!fp) + eprintf("setmntent %s:", "/etc/fstab"); + while ((me = getmntent(fp)) != NULL) { + if (strcmp(me->mnt_type, MNTTYPE_SWAP) == 0) { + if (swapoff(me->mnt_fsname) < 0) { + weprintf("swapoff %s:", me->mnt_fsname); + ret = 1; + } + } + } + endmntent(fp); + } else { + for (i = 0; i < argc; i++) { + if (swapoff(argv[i]) < 0) { + weprintf("swapoff %s:", argv[i]); + ret = 1; + } + } + } + return ret; +} diff --git a/ubase/swapon.8 b/ubase/swapon.8 @@ -0,0 +1,19 @@ +.TH SWAPON 8 ubase-VERSION +.SH NAME +\fBswapon\fR - enable devices and files for paging and swapping +.SH SYNOPSIS +\fBswapon\fR [\fB-dp\fR] [\fB-a\fI device\fR] +.SH DESCRIPTION +\fBswapon\fR is used to specify devices on which paging and +swapping are to take place. +.SH OPTIONS +.TP +\fB-d\fR +Discard freed swap pages before they are reused. +.TP +\fB-p\fR +Set higher priority than the default to the new swap area. +.TP +\fB-a\fR +Make all devices marked as ``swap'' in /etc/fstab are made +available, except for those with the ``noauto'' option. diff --git a/ubase/swapon.c b/ubase/swapon.c @@ -0,0 +1,68 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/swap.h> + +#include <mntent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-dp] [-a] device\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int i; + int ret = 0; + int flags = 0; + int all = 0; + + ARGBEGIN { + case 'a': + all = 1; + break; + case 'd': + flags |= SWAP_FLAG_DISCARD; + break; + case 'p': + flags |= SWAP_FLAG_PREFER; + break; + default: + usage(); + } ARGEND; + + if (!all && argc < 1) + usage(); + + if (all) { + struct mntent *me = NULL; + FILE *fp; + + fp = setmntent("/etc/fstab", "r"); + if (!fp) + eprintf("setmntent %s:", "/etc/fstab"); + while ((me = getmntent(fp)) != NULL) { + if (strcmp(me->mnt_type, MNTTYPE_SWAP) == 0 + && (hasmntopt(me, MNTOPT_NOAUTO) == NULL)) { + if (swapon(me->mnt_fsname, flags) < 0) { + weprintf("swapon %s:", me->mnt_fsname); + ret = 1; + } + } + } + endmntent(fp); + } else { + for (i = 0; i < argc; i++) { + if (swapon(argv[i], flags) < 0) { + weprintf("swapon %s:", argv[i]); + ret = 1; + } + } + } + return ret; +} diff --git a/ubase/switch_root.8 b/ubase/switch_root.8 @@ -0,0 +1,15 @@ +.TH SWITCH_ROOT 8 ubase-VERSION +.SH NAME +\fBswitch_root\fR - Switch to another filesystem as the root of the mount tree +.SH SYNOPSIS +\fBswitch_root\fR [\fB-c \fIconsole\fR] \fInewroot init +.SH DESCRIPTION +\fBswitch_root\fR removes all files and directories on the current root filesystem and overmounts it with \fInewroot\fR. +If a \fIconsole\fR is specified, redirect stdio and stderr to it. +After the switch, execute \fIinit\fR. +.TP +\fBswitch_root\fR can only be run as PID 1 in an initramfs or tmpfs with a regular and executable /init. +.SH OPTIONS +.TP +\fB-c\fR +Redirect stdio and stderr to \fIconsole\fR after switching to \fInewroot\fR. diff --git a/ubase/switch_root.c b/ubase/switch_root.c @@ -0,0 +1,131 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/vfs.h> + +#include <dirent.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +#define RAMFS_MAGIC 0x858458f6 /* some random number */ +#define TMPFS_MAGIC 0x01021994 + +static void +delete_content(const char *dir, dev_t curdevice) +{ + char path[PATH_MAX]; + DIR *d; + struct stat st; + struct dirent *dent; + + /* don't dive into other filesystems */ + if (lstat(dir, &st) < 0 || st.st_dev != curdevice) + return; + if (!(d = opendir(dir))) + return; + while ((dent = readdir(d))) { + if (strcmp(dent->d_name, ".") == 0 || + strcmp(dent->d_name, "..") == 0) + continue; + + /* build path and dive deeper */ + if (strlcpy(path, dir, sizeof(path)) >= sizeof(path)) + eprintf("path too long\n"); + if (path[strlen(path) - 1] != '/') + if (strlcat(path, "/", sizeof(path)) >= sizeof(path)) + eprintf("path too long\n"); + if (strlcat(path, dent->d_name, sizeof(path)) >= sizeof(path)) + eprintf("path too long\n"); + + if (lstat(path, &st) < 0) + weprintf("lstat %s:", path); + + if (S_ISDIR(st.st_mode)) { + delete_content(path, curdevice); + if (rmdir(path) < 0) + weprintf("rmdir %s:", path); + } else { + if (unlink(path) < 0) + weprintf("unlink %s:", path); + } + } + closedir(d); +} + +static void +usage(void) +{ + eprintf("usage: %s [-c console] [newroot] [init] (PID 1)\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *console = NULL; + dev_t curdev; + struct stat st; + struct statfs stfs; + + ARGBEGIN { + case 'c': + console = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + + /* check number of args and if we are PID 1 */ + if (argc != 2 || getpid() != 1) + usage(); + + /* chdir to newroot and make sure it's a different fs */ + if (chdir(argv[0])) + eprintf("chdir %s:", argv[0]); + + if (stat("/", &st)) + eprintf("stat %s:", "/"); + + curdev = st.st_dev; + if (stat(".", &st)) + eprintf("stat %s:", "."); + if (st.st_dev == curdev) + usage(); + + /* avoids trouble with real filesystems */ + if (stat("/init", &st) || !S_ISREG(st.st_mode)) + eprintf("/init is not a regular file\n"); + + statfs("/", &stfs); + if ((unsigned)stfs.f_type != RAMFS_MAGIC && (unsigned)stfs.f_type != TMPFS_MAGIC) + eprintf("current filesystem is not a RAMFS or TMPFS\n"); + + /* wipe / */ + delete_content("/", curdev); + + /* overmount / with newroot and chroot into it */ + if (mount(".", "/", NULL, MS_MOVE, NULL)) + eprintf("mount %s:", "."); + + if (chroot(".")) + eprintf("chroot failed\n"); + + /* if -c is set, redirect stdin/stdout/stderr to console */ + if (console) { + close(0); + if (open(console, O_RDWR) == -1) + eprintf("open %s:", console); + dup2(0, 1); + dup2(0, 2); + } + + /* execute init */ + execv(argv[1], argv); + eprintf("can't execute '%s'\n", argv[1]); + return 1; +} diff --git a/ubase/sysctl.8 b/ubase/sysctl.8 @@ -0,0 +1,13 @@ +.TH SYSCTL 8 ubase-VERSION +.SH NAME +\fBsysctl\fR - Configure kernel parameters at runtime +.SH SYNOPSIS +\fBsysctl\fR [\fB-p\fR \fIfile\fR] \fIvariable\fR[=\fIvalue\fR]... +.SH DESCRIPTION +\fBsysctl\fR modifies kernel parameters at runtime. The parameters available +are those listed under \fI/proc/sys/\fR. Procfs is required for sysctl support +in Linux. You can use \fBsysctl\fR to both read and write sysctl data. +.SH OPTIONS +.TP +\fB-p\fR +Load the sysctl key:value pairs from \fIfile\fR. diff --git a/ubase/sysctl.c b/ubase/sysctl.c @@ -0,0 +1,213 @@ +/* See LICENSE file for copyright and license details. */ +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "text.h" +#include "util.h" + +static void +replacestr(char *s, int a, int b) +{ + for (; *s; s++) + if (*s == a) + *s = b; +} + +static int +getsysctl(char *variable, char **value) +{ + char path[PATH_MAX]; + char *p; + char *buf, *tmp, c; + int fd; + ssize_t n; + size_t sz, i; + + replacestr(variable, '.', '/'); + + strlcpy(path, "/proc/sys/", sizeof(path)); + if (strlcat(path, variable, sizeof(path)) >= sizeof(path)) { + replacestr(variable, '/', '.'); + return -1; + } + + replacestr(variable, '/', '.'); + + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + + i = 0; + sz = 1; + buf = NULL; + while (1) { + n = read(fd, &c, 1); + if (n < 0) { + close(fd); + free(buf); + return -1; + } + if (n == 0) + break; + if (i == sz - 1) { + sz *= 2; + tmp = realloc(buf, sz); + if (!tmp) { + close(fd); + free(buf); + return -1; + } + buf = tmp; + } + buf[i++] = c; + } + buf[i] = '\0'; + + p = strrchr(buf, '\n'); + if (p) + *p = '\0'; + + *value = buf; + + close(fd); + + return 0; +} + +static int +setsysctl(char *variable, char *value) +{ + char path[PATH_MAX]; + int fd; + ssize_t n; + + replacestr(variable, '.', '/'); + + strlcpy(path, "/proc/sys/", sizeof(path)); + if (strlcat(path, variable, sizeof(path)) >= sizeof(path)) { + replacestr(variable, '/', '.'); + return -1; + } + + replacestr(variable, '/', '.'); + + fd = open(path, O_WRONLY); + if (fd < 0) + return -1; + + n = write(fd, value, strlen(value)); + if ((size_t)n != strlen(value)) { + close(fd); + return -1; + } + + close(fd); + + return 0; +} + +static int +parsepair(char *pair) +{ + char *p; + char *variable; + char *value; + + for (p = pair; *p; p++) { + if (p[0] == '.' && p[1] == '.') { + weprintf("malformed input: %s\n", pair); + return -1; + } + } + p = strchr(pair, '='); + if (p) { + if (p[1] == '\0') { + weprintf("malformed input: %s\n", pair); + return -1; + } + *p = '\0'; + value = &p[1]; + } else { + value = NULL; + } + variable = pair; + if (value) { + if (setsysctl(variable, value) < 0) { + weprintf("failed to set sysctl for %s\n", variable); + return -1; + } + } else { + if (getsysctl(variable, &value) < 0) { + weprintf("failed to get sysctl for %s\n", variable); + return -1; + } + printf("%s = %s\n", variable, value); + free(value); + } + + return 0; +} + +static void +usage(void) +{ + eprintf("usage: %s [-p file] variable[=value]...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + char *buf = NULL, *p; + char *file = NULL; + size_t size = 0; + int i; + int r = 0; + + ARGBEGIN { + case 'p': + file = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + + if (!file && argc < 1) + usage(); + + if (!file) { + for (i = 0; i < argc; i++) + if (parsepair(argv[i]) < 0) + r = 1; + } else { + fp = fopen(file, "r"); + if (!fp) + eprintf("fopen %s:", file); + while (agetline(&buf, &size, fp) != -1) { + p = buf; + for (p = buf; *p == ' ' || *p == '\t'; p++) + ; + if (*p == '#' || *p == '\n') + continue; + for (p = buf; *p; p++) { + if (*p == '\n') { + *p = '\0'; + break; + } + } + p = buf; + if (parsepair(p) < 0) + r = 1; + } + if (ferror(fp)) + eprintf("%s: read error:", file); + free(buf); + fclose(fp); + } + + return r; +} diff --git a/ubase/text.h b/ubase/text.h @@ -0,0 +1,13 @@ +/* See LICENSE file for copyright and license details. */ + +struct linebuf { + char **lines; + long nlines; + long capacity; +}; +#define EMPTY_LINEBUF {NULL, 0, 0,} +void getlines(FILE *, struct linebuf *); + +ssize_t agetline(char **, size_t *, FILE *); + +void concat(FILE *, const char *, FILE *, const char *); diff --git a/ubase/truncate.1 b/ubase/truncate.1 @@ -0,0 +1,20 @@ +.TH TRUNCATE 1 ubase-VERSION +.SH NAME +\fBtruncate\fR - Shrink or extend the size of a file to the specified size +.SH SYNOPSIS +\fBtruncate\fR [\fB-c\fR] \fB-s\fI size file...\fR +.SH DESCRIPTION +\fBtruncate\fR shrinks or extends the size of each \fIfile\fR to the +specified size. A \fIfile\fR argument that does not exist is created. +If a \fIfile\fR is larger than the specified size, the extra data is lost. +If a \fIfile\fR is shorter, it is extended and the extended part (hole) reads +as zero bytes. +.SH OPTIONS +.TP +\fB-c\fR +Do not create any files. +.TP +\fB-s\fR +Set or adjust the file size by \fIsize\fR bytes. +.SH SEE ALSO +truncate(2), ftruncate(2) diff --git a/ubase/truncate.c b/ubase/truncate.c @@ -0,0 +1,53 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-c] -s size file...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int cflag = 0, sflag = 0; + int fd, i, ret = 0; + long size = 0; + + ARGBEGIN { + case 's': + sflag = 1; + size = estrtol(EARGF(usage()), 10); + break; + case 'c': + cflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc < 1 || sflag == 0) + usage(); + + for (i = 0; i < argc; i++) { + fd = open(argv[i], O_WRONLY | (cflag ? 0 : O_CREAT), 0644); + if (fd < 0) { + weprintf("open: cannot open `%s' for writing:", argv[i]); + ret = 1; + continue; + } + if (ftruncate(fd, size) < 0) { + weprintf("ftruncate: cannot open `%s' for writing:", argv[i]); + ret = 1; + } + close(fd); + } + return ret; +} diff --git a/ubase/umount.8 b/ubase/umount.8 @@ -0,0 +1,30 @@ +.TH UMOUNT 8 ubase-VERSION +.SH NAME +\fBumount\fR - Unmount file systems +.SH SYNOPSIS +\fBumount\fR [\fB-lfn\fR] \fItarget...\fR +.TP +\fBumount\fR \fB-a\fR [\fB-lfn\fR] +.SH DESCRIPTION +\fBumount\fR detaches the \fItarget\fR filesystem(s). +A file system is specified by giving the directory where it +has been mounted. Giving the special device on which the file system +lives may also work, but is obsolete, mainly because it will fail in +case this device was mounted on more than one directory. +.SH OPTIONS +.TP +\fB-l\fR +Lazy unmount. Detach the filesystem from the fs hierarchy now, and cleanup +all references to the filesystem as soon as it is not busy anymore. +.TP +\fB-f\fR +Force unmount (in case of an unreachable NFS server). +.TP +\fB-n\fR +Unmount without writing in /etc/mtab. This is the default action. +.TP +\fB-a\fR +All of the file systems described in /proc/mounts are unmounted. The +proc filesystem is not unmounted. +.SH SEE ALSO +umount(2), mount(8) diff --git a/ubase/umount.c b/ubase/umount.c @@ -0,0 +1,88 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/mount.h> + +#include <mntent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +static int umountall(int); + +static void +usage(void) +{ + weprintf("usage: %s [-lfn] target...\n", argv0); + weprintf("usage: %s -a [-lfn]\n", argv0); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int i; + int aflag = 0; + int flags = 0; + int ret = 0; + + ARGBEGIN { + case 'a': + aflag = 1; + break; + case 'f': + flags |= MNT_FORCE; + break; + case 'l': + flags |= MNT_DETACH; + break; + case 'n': + break; + default: + usage(); + } ARGEND; + + if (argc < 1 && aflag == 0) + usage(); + + if (aflag == 1) + return umountall(flags); + + for (i = 0; i < argc; i++) { + if (umount2(argv[i], flags) < 0) { + weprintf("umount2 %s:", argv[i]); + ret = 1; + } + } + return ret; +} + +static int +umountall(int flags) +{ + FILE *fp; + struct mntent *me; + int ret; + char **mntdirs = NULL; + int len = 0; + + fp = setmntent("/proc/mounts", "r"); + if (!fp) + eprintf("setmntent %s:", "/proc/mounts"); + while ((me = getmntent(fp))) { + if (strcmp(me->mnt_type, "proc") == 0) + continue; + mntdirs = erealloc(mntdirs, ++len * sizeof(*mntdirs)); + mntdirs[len - 1] = estrdup(me->mnt_dir); + } + endmntent(fp); + while (--len >= 0) { + if (umount2(mntdirs[len], flags) < 0) { + weprintf("umount2 %s:", mntdirs[len]); + ret = 1; + } + free(mntdirs[len]); + } + free(mntdirs); + return ret; +} diff --git a/ubase/unshare.1 b/ubase/unshare.1 @@ -0,0 +1,43 @@ +.TH UNSHARE 1 ubase-VERSION +.SH NAME +\fBunshare\fR - run program with some namespaces unshared from parent +.SH SYNOPSIS +\fBunshare\fR [\fB-muinpU\fR] cmd [\fIargs...\fR] +.SH DESCRIPTION +\fBunshare\fR +Unshares the indicated namespaces from the parent process +and then executes the specified program. The namespaces to be unshared are +indicated via options. +.SH OPTIONS +.TP +\fB-m\fR +Unshare the mount namespace, so that the calling process has a private +copy of its namespace which is not shared with any other process. +This flag has the same effect as the \fBclone(2) CLONE_NEWNS\fR flag. +.TP +\fB-u\fR +Unshare the UTS IPC namespace, so that the calling process has a +private copy of the UTS namespace which is not shared with any other +process. This flag has the same effect as the \fBclone(2) CLONE_NEWUTS\fR +flag. +.TP +\fB-i\fR +Unshare the System V IPC namespace, so that the calling process has a +private copy of the System V IPC namespace which is not shared with +any other process. This flag has the same effect as the \fBclone(2) +CLONE_NEWIPC\fR flag +.TP +\fB-n\fR +Unshare the network namespace, so that the calling process is moved +into a new network namespace which is not shared with any previously +existing process. This flag has the same effect as the \fBclone(2) +CLONE_NEWNET\fR flag. +.TP +\fB-p\fR +Create the process in a new PID namespace. This flag has the same +effect as the \fBclone(2) CLONE_NEWPID\fR flag. +.TP +\fB-U\fR +The process will have a distinct set of UIDs, GIDs and capabilities. +.SH SEE ALSO +clone(2), unshare(2) diff --git a/ubase/unshare.c b/ubase/unshare.c @@ -0,0 +1,54 @@ +/* See LICENSE file for copyright and license details. */ +#include <sched.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-muinpU] cmd [args...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int flags = 0; + + ARGBEGIN { + case 'm': + flags |= CLONE_NEWNS; + break; + case 'u': + flags |= CLONE_NEWUTS; + break; + case 'i': + flags |= CLONE_NEWIPC; + break; + case 'n': + flags |= CLONE_NEWNET; + break; + case 'p': + flags |= CLONE_NEWPID; + break; + case 'U': + flags |= CLONE_NEWUSER; + break; + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + if (unshare(flags) < 0) + eprintf("unshare:"); + + if (execvp(argv[0], argv) < 0) + eprintf("execvp:"); + + return 0; +} diff --git a/ubase/uptime.1 b/ubase/uptime.1 @@ -0,0 +1,12 @@ +.TH UPTIME 1 ubase-VERSION +.SH NAME +\fBuptime\fR - Tell how long the system has been running +.SH SYNOPSIS +\fBuptime\fR +.SH DESCRIPTION +\fBuptime\fR gives a one line display of the following information. +The current time, how long the system has been running, how many users are +currently logged on and the system load averages for the past 1, 5 and +15 minutes. +.SH SEE ALSO +ps(1), utmp(5) diff --git a/ubase/uptime.c b/ubase/uptime.c @@ -0,0 +1,73 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/sysinfo.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <utmpx.h> + +#include "config.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct utmpx utx; + FILE *ufp; + struct sysinfo info; + time_t tmptime; + struct tm *now; + unsigned int days, hours, minutes; + int nusers = 0; + size_t n; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (sysinfo(&info) < 0) + eprintf("sysinfo:"); + time(&tmptime); + now = localtime(&tmptime); + printf(" %02d:%02d:%02d up ", now->tm_hour, now->tm_min, now->tm_sec); + info.uptime /= 60; + minutes = info.uptime % 60; + info.uptime /= 60; + hours = info.uptime % 24; + days = info.uptime / 24; + if (days) + printf("%d day%s, ", days, days > 1 ? "s" : ""); + if (hours) + printf("%2d:%02d, ", hours, minutes); + else + printf("%d min, ", minutes); + + if ((ufp = fopen(UTMP_PATH, "r"))) { + while ((n = fread(&utx, sizeof(utx), 1, ufp)) > 0) { + if (!utx.ut_user[0]) + continue; + if (utx.ut_type != USER_PROCESS) + continue; + nusers++; + } + if (ferror(ufp)) + eprintf("/var/run/utmp: read error:"); + fclose(ufp); + printf(" %d user%s, ", nusers, nusers > 1 ? "s" : ""); + } + + printf(" load average: %.02f, %.02f, %.02f\n", + info.loads[0] / 65536.0f, + info.loads[1] / 65536.0f, + info.loads[2] / 65536.0f); + + return 0; +} diff --git a/ubase/util.h b/ubase/util.h @@ -0,0 +1,53 @@ +/* See LICENSE file for copyright and license details. */ +#include "arg.h" + +#define UTF8_POINT(c) (((c) & 0xc0) != 0x80) +#define LEN(x) (sizeof (x) / sizeof *(x)) + +/* eprintf.c */ +extern char *argv0; + +/* agetcwd.c */ +char *agetcwd(void); + +/* apathmax.c */ +void apathmax(char **, long *); + +/* ealloc.c */ +void *ecalloc(size_t, size_t); +void *emalloc(size_t size); +void *erealloc(void *, size_t); +char *estrdup(const char *); + +/* eprintf.c */ +void enprintf(int, const char *, ...); +void eprintf(const char *, ...); +void weprintf(const char *, ...); + +/* estrtol.c */ +long estrtol(const char *, int); + +/* estrtoul.c */ +unsigned long estrtoul(const char *, int); + +/* explicit_bzero.c */ +#undef explicit_bzero +void explicit_bzero(void *, size_t); + +/* putword.c */ +void putword(const char *); + +/* recurse.c */ +void recurse(const char *, void (*)(const char *)); + +/* strlcpy.c */ +#undef strlcat +size_t strlcat(char *, const char *, size_t); + +/* strlcat.c */ +#undef strlcpy +size_t strlcpy(char *, const char *, size_t); + +/* tty.c */ +void devtotty(int, int *, int *); +char *ttytostr(int, int); diff --git a/ubase/vtallow.1 b/ubase/vtallow.1 @@ -0,0 +1,22 @@ +.Dd December 5, 2014 +.Dt VTALLOW 1 ubase\-VERSION +.Os +.Sh NAME +.Nm vtallow +.Nd enable or disable the VT switch +.Sh SYNOPSIS +.Nm vtallow +.Ar n | Ar y +.Sh DESCRIPTION +.Nm +controls the VT switch lock. +.Pp +.Sh OPTIONS +.Bl -tag -width Ds +.It Ar n +Disable VT switching. +.It Ar y +Enable VT switching. +.El +.Sh SEE ALSO +.Xr chvt 1 diff --git a/ubase/vtallow.c b/ubase/vtallow.c @@ -0,0 +1,52 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +#define CONSOLE "/dev/console" + +#define VT_LOCKSWITCH 0x560B /* disallow vt switching */ +#define VT_UNLOCKSWITCH 0x560C /* allow vt switching */ + +static void +usage(void) +{ + eprintf("usage: vtallow n | y\n"); +} + +int +main(int argc, char *argv[]) +{ + int fd; + int allow; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc != 1) + usage(); + + if (!strcmp(argv[0], "y")) + allow = 1; + else if (!strcmp(argv[0], "n")) + allow = 0; + else + usage(); + + if ((fd = open(CONSOLE, O_WRONLY)) < 0) + eprintf("open %s:", CONSOLE); + if (ioctl(fd, allow ? VT_UNLOCKSWITCH : VT_LOCKSWITCH) < 0) + eprintf("cannot %s VT switch:", + allow ? "unlock" : "lock"); + close(fd); + return 0; +} diff --git a/ubase/watch.1 b/ubase/watch.1 @@ -0,0 +1,19 @@ +.TH WATCH 1 ubase-VERSION +.SH NAME +\fBwatch\fR - Execute a program periodically, showing output fullscreen +.SH SYNOPSIS +\fBwatch\fR [\fB-t\fR] [\fB-n\fI interval\fR] \fIcommand\fR +.SH DESCRIPTION +\fBwatch\fR runs \fIcommand\fR repeatedly, displaying its output one +screenfull at a time. This allows you to watch the program output change +over time. By default the program is run every 2 seconds and will run +until terminated. +.SH OPTIONS +.TP +\fB-t\fR +Turn off the header showing the interval, command and current +time at the top of the display, as well as the following blank line. This +is the default action. +.TP +\fB-n\fR +Specify the update interval. diff --git a/ubase/watch.c b/ubase/watch.c @@ -0,0 +1,58 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-t] [-n interval] command\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char cmd[BUFSIZ]; + char *end; + useconds_t interval = 2 * 1E6; + float period; + int i; + + ARGBEGIN { + case 't': + break; + case 'n': + period = strtof(EARGF(usage()), &end); + if (*end != '\0' || errno != 0) + eprintf("invalid interval\n"); + if (period < 0) + period = 0.1f; + interval = period * 1E6; + break; + default: + usage(); + } ARGEND; + + if (argc < 1) + usage(); + + if (strlcpy(cmd, argv[0], sizeof(cmd)) >= sizeof(cmd)) + eprintf("command too long\n"); + for (i = 1; i < argc; i++) { + if (strlcat(cmd, " ", sizeof(cmd)) >= sizeof(cmd)) + eprintf("command too long\n"); + if (strlcat(cmd, argv[i], sizeof(cmd)) >= sizeof(cmd)) + eprintf("command too long\n"); + } + + for (;;) { + printf("\x1b[2J\x1b[H"); /* clear */ + fflush(NULL); + system(cmd); + usleep(interval); + } + return 0; +} diff --git a/ubase/who.1 b/ubase/who.1 @@ -0,0 +1,21 @@ +.TH WHO 1 ubase-VERSION +.SH NAME +\fBwho\fR - Print who has logged on +.SH SYNOPSIS +\fBwho\fR [\fB-ml\fR] +.SH DESCRIPTION +\fBwho\fR prints a list of who has logged on, their controlling tty, and the +time at which they logged on. +.SH OPTIONS +.TP +\fB-m\fR +Only show users on current tty. +.TP +\fB-l\fR +Print LOGIN processes as well. +.SH BUGS +\fBwho\fR relies on the utmp file to be updated responsibly. This +doesn't always happen, which can cause who to print completely +bogus data. +.SH SEE ALSO +utmp(5) diff --git a/ubase/who.c b/ubase/who.c @@ -0,0 +1,64 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <utmp.h> + +#include "config.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: who [-ml]\n"); +} + +int +main(int argc, char *argv[]) +{ + struct utmp usr; + FILE *ufp; + char timebuf[sizeof "yyyy-mm-dd hh:mm"]; + char *tty, *ttmp; + int mflag = 0, lflag = 0; + time_t t; + + ARGBEGIN { + case 'm': + mflag = 1; + tty = ttyname(0); + if (!tty) + eprintf("ttyname: stdin:"); + if ((ttmp = strrchr(tty, '/'))) + tty = ttmp+1; + break; + case 'l': + lflag = 1; + break; + default: + usage(); + } ARGEND; + + if (argc > 0) + usage(); + + if (!(ufp = fopen(UTMP_PATH, "r"))) + eprintf("fopen: %s:", UTMP_PATH); + + while (fread(&usr, sizeof(usr), 1, ufp) == 1) { + if (!*usr.ut_name || !*usr.ut_line || + usr.ut_line[0] == '~') + continue; + if (mflag != 0 && strcmp(usr.ut_line, tty) != 0) + continue; + if (!!strcmp(usr.ut_name, "LOGIN") == lflag) + continue; + t = usr.ut_time; + strftime(timebuf, sizeof timebuf, "%Y-%m-%d %H:%M", localtime(&t)); + printf("%-8s %-12s %-16s\n", usr.ut_name, usr.ut_line, timebuf); + } + fclose(ufp); + return 0; +}