From 7f3275e191ecae8d29d945bfb7c87a72ac590a3b Mon Sep 17 00:00:00 2001 From: Mike Lowis Date: Tue, 3 May 2016 11:10:18 -0400 Subject: [PATCH] Added sbase rules and source code --- Makefile | 5 +- source/sbase/LICENSE | 63 + source/sbase/Makefile | 276 +++++ source/sbase/README | 139 +++ source/sbase/Rules.mk | 662 ++++++++++ source/sbase/TODO | 22 + source/sbase/arg.h | 65 + source/sbase/basename.1 | 26 + source/sbase/basename.c | 34 + source/sbase/cal.1 | 70 ++ source/sbase/cal.c | 223 ++++ source/sbase/cat.1 | 30 + source/sbase/cat.c | 61 + source/sbase/chgrp.1 | 50 + source/sbase/chgrp.c | 82 ++ source/sbase/chmod.1 | 84 ++ source/sbase/chmod.c | 82 ++ source/sbase/chown.1 | 60 + source/sbase/chown.c | 110 ++ source/sbase/chroot.1 | 25 + source/sbase/chroot.c | 46 + source/sbase/cksum.1 | 28 + source/sbase/cksum.c | 120 ++ source/sbase/cmp.1 | 52 + source/sbase/cmp.c | 82 ++ source/sbase/cols.1 | 56 + source/sbase/cols.c | 98 ++ source/sbase/comm.1 | 44 + source/sbase/comm.c | 97 ++ source/sbase/compat.h | 6 + source/sbase/config.mk | 16 + source/sbase/confstr_l.h | 51 + source/sbase/cp.1 | 71 ++ source/sbase/cp.c | 57 + source/sbase/cron.1 | 23 + source/sbase/cron.c | 566 +++++++++ source/sbase/crypt.h | 12 + source/sbase/cut.1 | 71 ++ source/sbase/cut.c | 215 ++++ source/sbase/date.1 | 39 + source/sbase/date.c | 49 + source/sbase/dirname.1 | 23 + source/sbase/dirname.c | 24 + source/sbase/du.1 | 59 + source/sbase/du.c | 115 ++ source/sbase/echo.1 | 31 + source/sbase/echo.c | 24 + source/sbase/ed.1 | 9 + source/sbase/ed.c | 1424 ++++++++++++++++++++++ source/sbase/env.1 | 51 + source/sbase/env.c | 49 + source/sbase/expand.1 | 51 + source/sbase/expand.c | 131 ++ source/sbase/expr.1 | 103 ++ source/sbase/expr.c | 268 +++++ source/sbase/false.1 | 17 + source/sbase/false.c | 6 + source/sbase/find.1 | 135 +++ source/sbase/find.c | 1044 ++++++++++++++++ source/sbase/flock.1 | 32 + source/sbase/flock.c | 82 ++ source/sbase/fold.1 | 42 + source/sbase/fold.c | 119 ++ source/sbase/fs.h | 43 + source/sbase/getconf.1 | 60 + source/sbase/getconf.c | 120 ++ source/sbase/getconf.sh | 220 ++++ source/sbase/grep.1 | 93 ++ source/sbase/grep.c | 288 +++++ source/sbase/head.1 | 43 + source/sbase/head.c | 77 ++ source/sbase/hostname.1 | 18 + source/sbase/hostname.c | 33 + source/sbase/join.1 | 105 ++ source/sbase/join.c | 529 ++++++++ source/sbase/kill.1 | 47 + source/sbase/kill.c | 129 ++ source/sbase/libutf/Makefile | 6 + source/sbase/libutf/fgetrune.c | 36 + source/sbase/libutf/fputrune.c | 27 + source/sbase/libutf/isalnumrune.c | 9 + source/sbase/libutf/isalpharune.c | 679 +++++++++++ source/sbase/libutf/isblankrune.c | 9 + source/sbase/libutf/iscntrlrune.c | 18 + source/sbase/libutf/isdigitrune.c | 66 + source/sbase/libutf/isgraphrune.c | 9 + source/sbase/libutf/isprintrune.c | 10 + source/sbase/libutf/ispunctrune.c | 9 + source/sbase/libutf/isspacerune.c | 31 + source/sbase/libutf/istitlerune.c | 31 + source/sbase/libutf/isxdigitrune.c | 9 + source/sbase/libutf/lowerrune.c | 317 +++++ source/sbase/libutf/mkrunetype.awk | 240 ++++ source/sbase/libutf/rune.c | 148 +++ source/sbase/libutf/runetype.c | 41 + source/sbase/libutf/runetype.h | 26 + source/sbase/libutf/upperrune.c | 242 ++++ source/sbase/libutf/utf.c | 129 ++ source/sbase/libutf/utftorunestr.c | 13 + source/sbase/libutil/concat.c | 19 + source/sbase/libutil/cp.c | 176 +++ source/sbase/libutil/crypt.c | 179 +++ source/sbase/libutil/ealloc.c | 88 ++ source/sbase/libutil/enmasse.c | 38 + source/sbase/libutil/eprintf.c | 59 + source/sbase/libutil/eregcomp.c | 27 + source/sbase/libutil/estrtod.c | 18 + source/sbase/libutil/fnck.c | 21 + source/sbase/libutil/fshut.c | 43 + source/sbase/libutil/getlines.c | 32 + source/sbase/libutil/human.c | 25 + source/sbase/libutil/linecmp.c | 24 + source/sbase/libutil/md5.c | 148 +++ source/sbase/libutil/memmem.c | 66 + source/sbase/libutil/mkdirp.c | 30 + source/sbase/libutil/mode.c | 163 +++ source/sbase/libutil/parseoffset.c | 61 + source/sbase/libutil/putword.c | 16 + source/sbase/libutil/reallocarray.c | 50 + source/sbase/libutil/recurse.c | 107 ++ source/sbase/libutil/rm.c | 31 + source/sbase/libutil/sha1.c | 144 +++ source/sbase/libutil/sha224.c | 26 + source/sbase/libutil/sha256.c | 154 +++ source/sbase/libutil/sha384.c | 26 + source/sbase/libutil/sha512-224.c | 26 + source/sbase/libutil/sha512-256.c | 26 + source/sbase/libutil/sha512.c | 175 +++ source/sbase/libutil/strcasestr.c | 38 + source/sbase/libutil/strlcat.c | 63 + source/sbase/libutil/strlcpy.c | 59 + source/sbase/libutil/strsep.c | 37 + source/sbase/libutil/strtonum.c | 85 ++ source/sbase/libutil/unescape.c | 74 ++ source/sbase/limits_l.h | 129 ++ source/sbase/link.1 | 20 + source/sbase/link.c | 24 + source/sbase/ln.1 | 65 + source/sbase/ln.c | 101 ++ source/sbase/logger.1 | 55 + source/sbase/logger.c | 91 ++ source/sbase/logname.1 | 17 + source/sbase/logname.c | 29 + source/sbase/ls.1 | 98 ++ source/sbase/ls.c | 483 ++++++++ source/sbase/md5.h | 18 + source/sbase/md5sum.1 | 32 + source/sbase/md5sum.c | 42 + source/sbase/mkdir.1 | 37 + source/sbase/mkdir.c | 53 + source/sbase/mkfifo.1 | 31 + source/sbase/mkfifo.c | 47 + source/sbase/mktemp.1 | 46 + source/sbase/mktemp.c | 92 ++ source/sbase/mv.1 | 39 + source/sbase/mv.c | 62 + source/sbase/nice.1 | 40 + source/sbase/nice.c | 56 + source/sbase/nl.1 | 97 ++ source/sbase/nl.c | 212 ++++ source/sbase/nohup.1 | 44 + source/sbase/nohup.c | 45 + source/sbase/od.1 | 65 + source/sbase/od.c | 293 +++++ source/sbase/paste.1 | 51 + source/sbase/paste.c | 141 +++ source/sbase/pathchk.1 | 35 + source/sbase/pathchk.c | 114 ++ source/sbase/pathconf_l.h | 63 + source/sbase/printenv.1 | 20 + source/sbase/printenv.c | 30 + source/sbase/printf.1 | 34 + source/sbase/printf.c | 182 +++ source/sbase/pwd.1 | 32 + source/sbase/pwd.c | 50 + source/sbase/queue.h | 648 ++++++++++ source/sbase/readlink.1 | 32 + source/sbase/readlink.c | 54 + source/sbase/renice.1 | 42 + source/sbase/renice.c | 93 ++ source/sbase/rm.1 | 43 + source/sbase/rm.c | 40 + source/sbase/rmdir.1 | 33 + source/sbase/rmdir.c | 49 + source/sbase/sed.1 | 142 +++ source/sbase/sed.c | 1731 +++++++++++++++++++++++++++ source/sbase/seq.1 | 39 + source/sbase/seq.c | 147 +++ source/sbase/setsid.1 | 17 + source/sbase/setsid.c | 40 + source/sbase/sha1.h | 18 + source/sbase/sha1sum.1 | 32 + source/sbase/sha1sum.c | 41 + source/sbase/sha224.h | 16 + source/sbase/sha224sum.1 | 32 + source/sbase/sha224sum.c | 41 + source/sbase/sha256.h | 18 + source/sbase/sha256sum.1 | 32 + source/sbase/sha256sum.c | 41 + source/sbase/sha384.h | 16 + source/sbase/sha384sum.1 | 32 + source/sbase/sha384sum.c | 41 + source/sbase/sha512-224.h | 16 + source/sbase/sha512-224sum.1 | 32 + source/sbase/sha512-224sum.c | 41 + source/sbase/sha512-256.h | 16 + source/sbase/sha512-256sum.1 | 32 + source/sbase/sha512-256sum.c | 41 + source/sbase/sha512.h | 18 + source/sbase/sha512sum.1 | 32 + source/sbase/sha512sum.c | 41 + source/sbase/sleep.1 | 22 + source/sbase/sleep.c | 27 + source/sbase/sort.1 | 100 ++ source/sbase/sort.c | 434 +++++++ source/sbase/split.1 | 50 + source/sbase/split.c | 111 ++ source/sbase/sponge.1 | 19 + source/sbase/sponge.c | 36 + source/sbase/strings.1 | 52 + source/sbase/strings.c | 100 ++ source/sbase/sync.1 | 17 + source/sbase/sync.c | 22 + source/sbase/sysconf_l.h | 354 ++++++ source/sbase/tail.1 | 53 + source/sbase/tail.c | 169 +++ source/sbase/tar.1 | 69 ++ source/sbase/tar.c | 593 +++++++++ source/sbase/tee.1 | 30 + source/sbase/tee.c | 53 + source/sbase/test.1 | 124 ++ source/sbase/test.c | 243 ++++ source/sbase/text.h | 17 + source/sbase/tftp.1 | 29 + source/sbase/tftp.c | 309 +++++ source/sbase/time.1 | 49 + source/sbase/time.c | 73 ++ source/sbase/touch.1 | 65 + source/sbase/touch.c | 164 +++ source/sbase/tr.1 | 82 ++ source/sbase/tr.c | 277 +++++ source/sbase/true.1 | 17 + source/sbase/true.c | 6 + source/sbase/tsort.1 | 73 ++ source/sbase/tsort.c | 209 ++++ source/sbase/tty.1 | 28 + source/sbase/tty.c | 27 + source/sbase/uname.1 | 38 + source/sbase/uname.c | 59 + source/sbase/unexpand.1 | 45 + source/sbase/unexpand.c | 174 +++ source/sbase/uniq.1 | 47 + source/sbase/uniq.c | 144 +++ source/sbase/unlink.1 | 23 + source/sbase/unlink.c | 24 + source/sbase/utf.h | 67 ++ source/sbase/util.h | 80 ++ source/sbase/uudecode.1 | 49 + source/sbase/uudecode.c | 282 +++++ source/sbase/uuencode.1 | 38 + source/sbase/uuencode.c | 129 ++ source/sbase/wc.1 | 32 + source/sbase/wc.c | 117 ++ source/sbase/which.1 | 44 + source/sbase/which.c | 89 ++ source/sbase/whoami.1 | 9 + source/sbase/whoami.c | 37 + source/sbase/xargs.1 | 96 ++ source/sbase/xargs.c | 280 +++++ source/sbase/xinstall.1 | 89 ++ source/sbase/xinstall.c | 256 ++++ source/sbase/yes.1 | 14 + source/sbase/yes.c | 19 + source/ubase/Rules.mk | 3 - 274 files changed, 27447 insertions(+), 5 deletions(-) create mode 100644 source/sbase/LICENSE create mode 100644 source/sbase/Makefile create mode 100644 source/sbase/README create mode 100644 source/sbase/Rules.mk create mode 100644 source/sbase/TODO create mode 100644 source/sbase/arg.h create mode 100644 source/sbase/basename.1 create mode 100644 source/sbase/basename.c create mode 100644 source/sbase/cal.1 create mode 100644 source/sbase/cal.c create mode 100644 source/sbase/cat.1 create mode 100644 source/sbase/cat.c create mode 100644 source/sbase/chgrp.1 create mode 100644 source/sbase/chgrp.c create mode 100644 source/sbase/chmod.1 create mode 100644 source/sbase/chmod.c create mode 100644 source/sbase/chown.1 create mode 100644 source/sbase/chown.c create mode 100644 source/sbase/chroot.1 create mode 100644 source/sbase/chroot.c create mode 100644 source/sbase/cksum.1 create mode 100644 source/sbase/cksum.c create mode 100644 source/sbase/cmp.1 create mode 100644 source/sbase/cmp.c create mode 100644 source/sbase/cols.1 create mode 100644 source/sbase/cols.c create mode 100644 source/sbase/comm.1 create mode 100644 source/sbase/comm.c create mode 100644 source/sbase/compat.h create mode 100644 source/sbase/config.mk create mode 100644 source/sbase/confstr_l.h create mode 100644 source/sbase/cp.1 create mode 100644 source/sbase/cp.c create mode 100644 source/sbase/cron.1 create mode 100644 source/sbase/cron.c create mode 100644 source/sbase/crypt.h create mode 100644 source/sbase/cut.1 create mode 100644 source/sbase/cut.c create mode 100644 source/sbase/date.1 create mode 100644 source/sbase/date.c create mode 100644 source/sbase/dirname.1 create mode 100644 source/sbase/dirname.c create mode 100644 source/sbase/du.1 create mode 100644 source/sbase/du.c create mode 100644 source/sbase/echo.1 create mode 100644 source/sbase/echo.c create mode 100644 source/sbase/ed.1 create mode 100644 source/sbase/ed.c create mode 100644 source/sbase/env.1 create mode 100644 source/sbase/env.c create mode 100644 source/sbase/expand.1 create mode 100644 source/sbase/expand.c create mode 100644 source/sbase/expr.1 create mode 100644 source/sbase/expr.c create mode 100644 source/sbase/false.1 create mode 100644 source/sbase/false.c create mode 100644 source/sbase/find.1 create mode 100644 source/sbase/find.c create mode 100644 source/sbase/flock.1 create mode 100644 source/sbase/flock.c create mode 100644 source/sbase/fold.1 create mode 100644 source/sbase/fold.c create mode 100644 source/sbase/fs.h create mode 100644 source/sbase/getconf.1 create mode 100644 source/sbase/getconf.c create mode 100755 source/sbase/getconf.sh create mode 100644 source/sbase/grep.1 create mode 100644 source/sbase/grep.c create mode 100644 source/sbase/head.1 create mode 100644 source/sbase/head.c create mode 100644 source/sbase/hostname.1 create mode 100644 source/sbase/hostname.c create mode 100644 source/sbase/join.1 create mode 100644 source/sbase/join.c create mode 100644 source/sbase/kill.1 create mode 100644 source/sbase/kill.c create mode 100644 source/sbase/libutf/Makefile create mode 100644 source/sbase/libutf/fgetrune.c create mode 100644 source/sbase/libutf/fputrune.c create mode 100644 source/sbase/libutf/isalnumrune.c create mode 100644 source/sbase/libutf/isalpharune.c create mode 100644 source/sbase/libutf/isblankrune.c create mode 100644 source/sbase/libutf/iscntrlrune.c create mode 100644 source/sbase/libutf/isdigitrune.c create mode 100644 source/sbase/libutf/isgraphrune.c create mode 100644 source/sbase/libutf/isprintrune.c create mode 100644 source/sbase/libutf/ispunctrune.c create mode 100644 source/sbase/libutf/isspacerune.c create mode 100644 source/sbase/libutf/istitlerune.c create mode 100644 source/sbase/libutf/isxdigitrune.c create mode 100644 source/sbase/libutf/lowerrune.c create mode 100644 source/sbase/libutf/mkrunetype.awk create mode 100644 source/sbase/libutf/rune.c create mode 100644 source/sbase/libutf/runetype.c create mode 100644 source/sbase/libutf/runetype.h create mode 100644 source/sbase/libutf/upperrune.c create mode 100644 source/sbase/libutf/utf.c create mode 100644 source/sbase/libutf/utftorunestr.c create mode 100644 source/sbase/libutil/concat.c create mode 100644 source/sbase/libutil/cp.c create mode 100644 source/sbase/libutil/crypt.c create mode 100644 source/sbase/libutil/ealloc.c create mode 100644 source/sbase/libutil/enmasse.c create mode 100644 source/sbase/libutil/eprintf.c create mode 100644 source/sbase/libutil/eregcomp.c create mode 100644 source/sbase/libutil/estrtod.c create mode 100644 source/sbase/libutil/fnck.c create mode 100644 source/sbase/libutil/fshut.c create mode 100644 source/sbase/libutil/getlines.c create mode 100644 source/sbase/libutil/human.c create mode 100644 source/sbase/libutil/linecmp.c create mode 100644 source/sbase/libutil/md5.c create mode 100644 source/sbase/libutil/memmem.c create mode 100644 source/sbase/libutil/mkdirp.c create mode 100644 source/sbase/libutil/mode.c create mode 100644 source/sbase/libutil/parseoffset.c create mode 100644 source/sbase/libutil/putword.c create mode 100644 source/sbase/libutil/reallocarray.c create mode 100644 source/sbase/libutil/recurse.c create mode 100644 source/sbase/libutil/rm.c create mode 100644 source/sbase/libutil/sha1.c create mode 100644 source/sbase/libutil/sha224.c create mode 100644 source/sbase/libutil/sha256.c create mode 100644 source/sbase/libutil/sha384.c create mode 100644 source/sbase/libutil/sha512-224.c create mode 100644 source/sbase/libutil/sha512-256.c create mode 100644 source/sbase/libutil/sha512.c create mode 100644 source/sbase/libutil/strcasestr.c create mode 100644 source/sbase/libutil/strlcat.c create mode 100644 source/sbase/libutil/strlcpy.c create mode 100644 source/sbase/libutil/strsep.c create mode 100644 source/sbase/libutil/strtonum.c create mode 100644 source/sbase/libutil/unescape.c create mode 100644 source/sbase/limits_l.h create mode 100644 source/sbase/link.1 create mode 100644 source/sbase/link.c create mode 100644 source/sbase/ln.1 create mode 100644 source/sbase/ln.c create mode 100644 source/sbase/logger.1 create mode 100644 source/sbase/logger.c create mode 100644 source/sbase/logname.1 create mode 100644 source/sbase/logname.c create mode 100644 source/sbase/ls.1 create mode 100644 source/sbase/ls.c create mode 100644 source/sbase/md5.h create mode 100644 source/sbase/md5sum.1 create mode 100644 source/sbase/md5sum.c create mode 100644 source/sbase/mkdir.1 create mode 100644 source/sbase/mkdir.c create mode 100644 source/sbase/mkfifo.1 create mode 100644 source/sbase/mkfifo.c create mode 100644 source/sbase/mktemp.1 create mode 100644 source/sbase/mktemp.c create mode 100644 source/sbase/mv.1 create mode 100644 source/sbase/mv.c create mode 100644 source/sbase/nice.1 create mode 100644 source/sbase/nice.c create mode 100644 source/sbase/nl.1 create mode 100644 source/sbase/nl.c create mode 100644 source/sbase/nohup.1 create mode 100644 source/sbase/nohup.c create mode 100644 source/sbase/od.1 create mode 100644 source/sbase/od.c create mode 100644 source/sbase/paste.1 create mode 100644 source/sbase/paste.c create mode 100644 source/sbase/pathchk.1 create mode 100644 source/sbase/pathchk.c create mode 100644 source/sbase/pathconf_l.h create mode 100644 source/sbase/printenv.1 create mode 100644 source/sbase/printenv.c create mode 100644 source/sbase/printf.1 create mode 100644 source/sbase/printf.c create mode 100644 source/sbase/pwd.1 create mode 100644 source/sbase/pwd.c create mode 100644 source/sbase/queue.h create mode 100644 source/sbase/readlink.1 create mode 100644 source/sbase/readlink.c create mode 100644 source/sbase/renice.1 create mode 100644 source/sbase/renice.c create mode 100644 source/sbase/rm.1 create mode 100644 source/sbase/rm.c create mode 100644 source/sbase/rmdir.1 create mode 100644 source/sbase/rmdir.c create mode 100644 source/sbase/sed.1 create mode 100644 source/sbase/sed.c create mode 100644 source/sbase/seq.1 create mode 100644 source/sbase/seq.c create mode 100644 source/sbase/setsid.1 create mode 100644 source/sbase/setsid.c create mode 100644 source/sbase/sha1.h create mode 100644 source/sbase/sha1sum.1 create mode 100644 source/sbase/sha1sum.c create mode 100644 source/sbase/sha224.h create mode 100644 source/sbase/sha224sum.1 create mode 100644 source/sbase/sha224sum.c create mode 100644 source/sbase/sha256.h create mode 100644 source/sbase/sha256sum.1 create mode 100644 source/sbase/sha256sum.c create mode 100644 source/sbase/sha384.h create mode 100644 source/sbase/sha384sum.1 create mode 100644 source/sbase/sha384sum.c create mode 100644 source/sbase/sha512-224.h create mode 100644 source/sbase/sha512-224sum.1 create mode 100644 source/sbase/sha512-224sum.c create mode 100644 source/sbase/sha512-256.h create mode 100644 source/sbase/sha512-256sum.1 create mode 100644 source/sbase/sha512-256sum.c create mode 100644 source/sbase/sha512.h create mode 100644 source/sbase/sha512sum.1 create mode 100644 source/sbase/sha512sum.c create mode 100644 source/sbase/sleep.1 create mode 100644 source/sbase/sleep.c create mode 100644 source/sbase/sort.1 create mode 100644 source/sbase/sort.c create mode 100644 source/sbase/split.1 create mode 100644 source/sbase/split.c create mode 100644 source/sbase/sponge.1 create mode 100644 source/sbase/sponge.c create mode 100644 source/sbase/strings.1 create mode 100644 source/sbase/strings.c create mode 100644 source/sbase/sync.1 create mode 100644 source/sbase/sync.c create mode 100644 source/sbase/sysconf_l.h create mode 100644 source/sbase/tail.1 create mode 100644 source/sbase/tail.c create mode 100644 source/sbase/tar.1 create mode 100644 source/sbase/tar.c create mode 100644 source/sbase/tee.1 create mode 100644 source/sbase/tee.c create mode 100644 source/sbase/test.1 create mode 100644 source/sbase/test.c create mode 100644 source/sbase/text.h create mode 100644 source/sbase/tftp.1 create mode 100644 source/sbase/tftp.c create mode 100644 source/sbase/time.1 create mode 100644 source/sbase/time.c create mode 100644 source/sbase/touch.1 create mode 100644 source/sbase/touch.c create mode 100644 source/sbase/tr.1 create mode 100644 source/sbase/tr.c create mode 100644 source/sbase/true.1 create mode 100644 source/sbase/true.c create mode 100644 source/sbase/tsort.1 create mode 100644 source/sbase/tsort.c create mode 100644 source/sbase/tty.1 create mode 100644 source/sbase/tty.c create mode 100644 source/sbase/uname.1 create mode 100644 source/sbase/uname.c create mode 100644 source/sbase/unexpand.1 create mode 100644 source/sbase/unexpand.c create mode 100644 source/sbase/uniq.1 create mode 100644 source/sbase/uniq.c create mode 100644 source/sbase/unlink.1 create mode 100644 source/sbase/unlink.c create mode 100644 source/sbase/utf.h create mode 100644 source/sbase/util.h create mode 100644 source/sbase/uudecode.1 create mode 100644 source/sbase/uudecode.c create mode 100644 source/sbase/uuencode.1 create mode 100644 source/sbase/uuencode.c create mode 100644 source/sbase/wc.1 create mode 100644 source/sbase/wc.c create mode 100644 source/sbase/which.1 create mode 100644 source/sbase/which.c create mode 100644 source/sbase/whoami.1 create mode 100644 source/sbase/whoami.c create mode 100644 source/sbase/xargs.1 create mode 100644 source/sbase/xargs.c create mode 100644 source/sbase/xinstall.1 create mode 100644 source/sbase/xinstall.c create mode 100644 source/sbase/yes.1 create mode 100644 source/sbase/yes.c diff --git a/Makefile b/Makefile index 9c6904ce..d0de9ea8 100644 --- a/Makefile +++ b/Makefile @@ -35,14 +35,15 @@ DIRS = $(BUILDDIR) $(BINDIR) $(OBJDIR) include source/Rules.mk include source/sh/Rules.mk include source/ubase/Rules.mk -#include source/sbase/Rules.mk +include source/sbase/Rules.mk .PHONY: all $(BINS) all: $(BINS) clean: - $(RM) $(ECLEAN) + @echo cleaning + @$(RM) $(BUILDDIR)/dummy $(ECLEAN) # load dependency files if they exist -include $(DEPS) diff --git a/source/sbase/LICENSE b/source/sbase/LICENSE new file mode 100644 index 00000000..1ff215b7 --- /dev/null +++ b/source/sbase/LICENSE @@ -0,0 +1,63 @@ +MIT/X Consortium License + +© 2011 Connor Lane Smith +© 2011-2016 Dimitris Papastamos +© 2014-2016 Laslo Hunhold + +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 +© 2011 Rob Pilling +© 2011 Hiltjo Posthuma +© 2011 pancake +© 2011 Random832 +© 2012 William Haddon +© 2012 Kurt H. Maier +© 2012 Christoph Lohmann <20h@r-36.net> +© 2012 David Galos +© 2012 Robert Ransom +© 2013 Jakob Kramer +© 2013 Anselm R Garbe +© 2013 Truls Becken +© 2013 dsp +© 2013 Markus Teich +© 2013 Jesse Ogle +© 2013 Lorenzo Cogotti +© 2013 Federico G. Benavento +© 2013 Roberto E. Vargas Caballero +© 2013 Christian Hesse +© 2013 Markus Wichmann +© 2014 Silvan Jegen +© 2014 Daniel Bainton +© 2014 Tuukka Kataja +© 2014 Jeffrey Picard +© 2014 Evan Gates +© 2014 Michael Forney +© 2014 Ari Malinen +© 2014 Brandon Mulcahy +© 2014 Adria Garriga +© 2014-2015 Greg Reagle +© 2015 Tai Chi Minh Ralph Eastwood +© 2015 Quentin Rameau +© 2015 Dionysis Grigoropoulos +© 2015 Wolfgang Corcoran-Mathe +© 2016 Mattias Andrée +© 2016 Eivind Uggedal diff --git a/source/sbase/Makefile b/source/sbase/Makefile new file mode 100644 index 00000000..6b2bfdf7 --- /dev/null +++ b/source/sbase/Makefile @@ -0,0 +1,276 @@ +include config.mk + +.SUFFIXES: +.SUFFIXES: .o .c + +HDR =\ + arg.h\ + compat.h\ + crypt.h\ + fs.h\ + md5.h\ + queue.h\ + sha1.h\ + sha224.h\ + sha256.h\ + sha384.h\ + sha512.h\ + sha512-224.h\ + sha512-256.h\ + text.h\ + utf.h\ + util.h + +LIBUTF = libutf.a +LIBUTFSRC =\ + libutf/rune.c\ + libutf/runetype.c\ + libutf/utf.c\ + libutf/utftorunestr.c\ + libutf/fgetrune.c\ + libutf/fputrune.c\ + libutf/isalnumrune.c\ + libutf/isalpharune.c\ + libutf/isblankrune.c\ + libutf/iscntrlrune.c\ + libutf/isdigitrune.c\ + libutf/isgraphrune.c\ + libutf/isprintrune.c\ + libutf/ispunctrune.c\ + libutf/isspacerune.c\ + libutf/istitlerune.c\ + libutf/isxdigitrune.c\ + libutf/lowerrune.c\ + libutf/upperrune.c + +LIBUTIL = libutil.a +LIBUTILSRC =\ + 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/fnck.c\ + libutil/fshut.c\ + libutil/getlines.c\ + libutil/human.c\ + libutil/linecmp.c\ + libutil/md5.c\ + libutil/memmem.c\ + libutil/mkdirp.c\ + libutil/mode.c\ + libutil/parseoffset.c\ + libutil/putword.c\ + libutil/reallocarray.c\ + libutil/recurse.c\ + libutil/rm.c\ + libutil/sha1.c\ + libutil/sha224.c\ + libutil/sha256.c\ + libutil/sha384.c\ + libutil/sha512.c\ + libutil/sha512-224.c\ + libutil/sha512-256.c\ + libutil/strcasestr.c\ + libutil/strlcat.c\ + libutil/strlcpy.c\ + libutil/strsep.c\ + libutil/strtonum.c\ + libutil/unescape.c + +LIB = $(LIBUTF) $(LIBUTIL) + +BIN =\ + basename\ + cal\ + cat\ + chgrp\ + chmod\ + chown\ + chroot\ + cksum\ + cmp\ + cols\ + comm\ + cp\ + cron\ + cut\ + date\ + dirname\ + du\ + echo\ + ed\ + env\ + expand\ + expr\ + false\ + find\ + flock\ + fold\ + getconf\ + grep\ + head\ + join\ + hostname\ + kill\ + link\ + ln\ + logger\ + logname\ + ls\ + md5sum\ + mkdir\ + mkfifo\ + mktemp\ + mv\ + nice\ + nl\ + nohup\ + od\ + pathchk\ + paste\ + printenv\ + printf\ + pwd\ + readlink\ + renice\ + rm\ + rmdir\ + sed\ + seq\ + setsid\ + sha1sum\ + sha224sum\ + sha256sum\ + sha384sum\ + sha512sum\ + sha512-224sum\ + sha512-256sum\ + sleep\ + sort\ + split\ + sponge\ + strings\ + sync\ + tail\ + tar\ + tee\ + test\ + tftp\ + time\ + touch\ + tr\ + true\ + tsort\ + tty\ + uname\ + unexpand\ + uniq\ + unlink\ + uudecode\ + uuencode\ + wc\ + which\ + whoami\ + xargs\ + xinstall\ + 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: + $(CC) $(LDFLAGS) -o $@ $< $(LIB) + +.c.o: + $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -c $< + +$(LIBUTF): $(LIBUTFOBJ) + $(AR) rc $@ $? + $(RANLIB) $@ + +$(LIBUTIL): $(LIBUTILOBJ) + $(AR) rc $@ $? + $(RANLIB) $@ + +getconf.c: confstr_l.h limits_l.h sysconf_l.h pathconf_l.h + +confstr_l.h limits_l.h sysconf_l.h pathconf_l.h: getconf.sh + ./getconf.sh + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin + cd $(DESTDIR)$(PREFIX)/bin && ln -f test [ && chmod 755 $(BIN) + mv -f $(DESTDIR)$(PREFIX)/bin/xinstall $(DESTDIR)$(PREFIX)/bin/install + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + for m in $(MAN); do sed "s/^\.Os sbase/.Os sbase $(VERSION)/g" < "$$m" > $(DESTDIR)$(MANPREFIX)/man1/"$$m"; done + cd $(DESTDIR)$(MANPREFIX)/man1 && chmod 644 $(MAN) + mv -f $(DESTDIR)$(MANPREFIX)/man1/xinstall.1 $(DESTDIR)$(MANPREFIX)/man1/install.1 + +uninstall: + cd $(DESTDIR)$(PREFIX)/bin && rm -f $(BIN) [ install + 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 + cp confstr_l.h limits_l.h sysconf_l.h pathconf_l.h build + for f in $(SRC); do sed "s/^main(/$$(echo "$${f%.c}" | sed s/-/_/g)_&/" < $$f > build/$$f; done + echo '#include ' > build/$@.c + echo '#include ' >> build/$@.c + echo '#include ' >> build/$@.c + echo '#include ' >> build/$@.c + echo '#include "util.h"' >> build/$@.c + for f in $(SRC); do echo "int $$(echo "$${f%.c}" | sed s/-/_/g)_main(int, char **);"; done >> build/$@.c + echo 'int main(int argc, char *argv[]) { char *s = basename(argv[0]);' >> build/$@.c + echo 'if(!strcmp(s,"sbase-box")) { argc--; argv++; s = basename(argv[0]); } if(0) ;' >> build/$@.c + echo "else if (!strcmp(s, \"install\")) return xinstall_main(argc, argv);" >> build/$@.c + echo "else if (!strcmp(s, \"[\")) return test_main(argc, argv);" >> build/$@.c + for f in $(SRC); do echo "else if(!strcmp(s, \"$${f%.c}\")) return $$(echo "$${f%.c}" | sed s/-/_/g)_main(argc, argv);"; done >> build/$@.c + echo 'else { fputs("[ ", stdout);' >> build/$@.c + for f in $(SRC); do echo "fputs(\"$${f%.c} \", stdout);"; done >> build/$@.c + echo 'putchar(0xa); }; return 0; }' >> build/$@.c + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ build/*.c $(LIB) + rm -r build + +sbase-box-install: sbase-box + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f sbase-box $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/sbase-box + for f in $(BIN); do ln -sf sbase-box $(DESTDIR)$(PREFIX)/bin/"$$f"; done + ln -sf sbase-box $(DESTDIR)$(PREFIX)/bin/[ + mv -f $(DESTDIR)$(PREFIX)/bin/xinstall $(DESTDIR)$(PREFIX)/bin/install + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + for m in $(MAN); do sed "s/^\.Os sbase/.Os sbase $(VERSION)/g" < "$$m" > $(DESTDIR)$(MANPREFIX)/man1/"$$m"; done + cd $(DESTDIR)$(MANPREFIX)/man1 && chmod 644 $(MAN) + mv -f $(DESTDIR)$(MANPREFIX)/man1/xinstall.1 $(DESTDIR)$(MANPREFIX)/man1/install.1 + +sbase-box-uninstall: uninstall + cd $(DESTDIR)$(PREFIX)/bin && rm -f sbase-box + +clean: + rm -f $(BIN) $(OBJ) $(LIB) sbase-box sbase-$(VERSION).tar.gz + rm -f confstr_l.h limits_l.h sysconf_l.h pathconf_l.h + +.PHONY: + all install uninstall dist sbase-box sbase-box-install sbase-box-uninstall clean diff --git a/source/sbase/README b/source/sbase/README new file mode 100644 index 00000000..d60d8fc3 --- /dev/null +++ b/source/sbase/README @@ -0,0 +1,139 @@ +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: + +'#' -> UTF-8 support, '=' -> Implicit UTF-8 support, '*' -> Finished, +'|' -> Audited, 'o' -> POSIX 2013 compliant, 'x' -> Non-POSIX, +'0' -> NUL handling, '()' -> Petty flag + + UTILITY MISSING + ------- ------- +0=*|o basename . +0=*|o cal . +0=*|o cat . +0=*|o chgrp . +0=*|o chmod . +0=*|o chown . +0=*|x chroot . +0=*|o cksum . +0=*|o cmp . +0#*|x cols . +0=*|o comm . +0=*|o cp (-i) +0=*|x cron . +0#*|o cut . +0=*|o date . +0=*|o dirname . +0=*|o du . +0=*|o echo . + o ed . +0=*|o env . +0#*|o expand . +0#*|o expr . +0=*|o false . +0= find . +0=* x flock . +0#*|o fold . +0=*|o getconf (-v) + =*|o grep . +0=*|o head . +0=*|x hostname . +0=*|x install . +0=* o join . +0=*|o kill . +0=*|o link . +0=*|o ln . +0=*|o logger . +0=*|o logname . +0#* o ls (-C, -k, -m, -p, -s, -x) +0=*|x md5sum . +0=*|o mkdir . +0=*|o mkfifo . +0=*|x mktemp . +0=*|o mv (-i) +0=*|o nice . +0#*|o nl . +0=*|o nohup . +0=*|o od . +0#* o pathchk . + #*|o paste . +0=*|x printenv . +0#*|o printf . +0=*|o pwd . +0=*|x readlink . +0=*|o renice . +0=*|o rm (-i) +0=*|o rmdir . + # sed . +0=*|x seq . +0=*|x setsid . +0=*|x sha1sum . +0=* x sha224sum . +0=*|x sha256sum . +0=* x sha238sum . +0=*|x sha512sum . +0=* x sha512-224sum . +0=* x sha512-256sum . +0=*|o sleep . +0#*|o sort . +0=*|o split . +0=*|x sponge . +0#*|o strings . +0=*|x sync . +0=*|o tail . +0=*|x tar . +0=*|o tee . +0=*|o test . +0=*|x tftp . +0=*|o time . +0=*|o touch . +0#*|o tr . +0=*|o true . +0=* o tsort . +0=*|o tty . +0=*|o uname . +0#*|o unexpand . +0=*|o uniq . +0=*|o unlink . +0=*|o uudecode . +0=*|o uuencode . +0#*|o wc . +0=*|x which . +0=*|x whoami . +0=*|o xargs (-p) +0=*|x yes . + +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 +-------- + +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 or run: make sbase-box-install + +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/source/sbase/Rules.mk b/source/sbase/Rules.mk new file mode 100644 index 00000000..60784f08 --- /dev/null +++ b/source/sbase/Rules.mk @@ -0,0 +1,662 @@ +BINS += $(SBASE_BINS) +DIRS += $(SBASE_OBJDIR) +ECLEAN += $(SBASE_LIBUTIL) \ + $(SBASE_LIBUTF) \ + $(SBASE_LIBUTIL_OBJS) \ + $(SBASE_LIBUTF_OBJS) \ + $(addprefix $(BINDIR)/,$(SBASE_BINS)) + +SBASE_SUBDIR = source/sbase +SBASE_OBJDIR = $(OBJDIR)/sbase +SBASE_LIBUTIL = $(SBASE_OBJDIR)/libutil.a +SBASE_LIBUTF = $(SBASE_OBJDIR)/libutf.a +SBASE_DEFS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_FILE_OFFSET_BITS=64 +SBASE_COMPILE = $(CC) $(SBASE_DEFS) $(CFLAGS) $(CPPFLAGS) -I$(SBASE_SUBDIR) -c -o $@ $< +SBASE_BUILD = $(CC) $(SBASE_DEFS) $(CFLAGS) $(CPPFLAGS) -I$(SBASE_SUBDIR) -o $@ $^ +SBASE_ARCHIVE = $(AR) $(ARFLAGS) $@ $^ + +SBASE_LIBUTIL_OBJS = \ + $(SBASE_OBJDIR)/concat.o \ + $(SBASE_OBJDIR)/cp.o \ + $(SBASE_OBJDIR)/crypt.o \ + $(SBASE_OBJDIR)/ealloc.o \ + $(SBASE_OBJDIR)/enmasse.o \ + $(SBASE_OBJDIR)/eprintf.o \ + $(SBASE_OBJDIR)/eregcomp.o \ + $(SBASE_OBJDIR)/estrtod.o \ + $(SBASE_OBJDIR)/fnck.o \ + $(SBASE_OBJDIR)/fshut.o \ + $(SBASE_OBJDIR)/getlines.o \ + $(SBASE_OBJDIR)/human.o \ + $(SBASE_OBJDIR)/linecmp.o \ + $(SBASE_OBJDIR)/md5.o \ + $(SBASE_OBJDIR)/memmem.o \ + $(SBASE_OBJDIR)/mkdirp.o \ + $(SBASE_OBJDIR)/mode.o \ + $(SBASE_OBJDIR)/parseoffset.o \ + $(SBASE_OBJDIR)/putword.o \ + $(SBASE_OBJDIR)/reallocarray.o \ + $(SBASE_OBJDIR)/recurse.o \ + $(SBASE_OBJDIR)/rm.o \ + $(SBASE_OBJDIR)/sha1.o \ + $(SBASE_OBJDIR)/sha224.o \ + $(SBASE_OBJDIR)/sha256.o \ + $(SBASE_OBJDIR)/sha384.o \ + $(SBASE_OBJDIR)/sha512-224.o \ + $(SBASE_OBJDIR)/sha512-256.o \ + $(SBASE_OBJDIR)/sha512.o \ + $(SBASE_OBJDIR)/strcasestr.o \ + $(SBASE_OBJDIR)/strlcat.o \ + $(SBASE_OBJDIR)/strlcpy.o \ + $(SBASE_OBJDIR)/strsep.o \ + $(SBASE_OBJDIR)/strtonum.o \ + $(SBASE_OBJDIR)/unescape.o + +SBASE_LIBUTF_OBJS = \ + $(SBASE_OBJDIR)/fgetrune.o \ + $(SBASE_OBJDIR)/fputrune.o \ + $(SBASE_OBJDIR)/isalnumrune.o \ + $(SBASE_OBJDIR)/isalpharune.o \ + $(SBASE_OBJDIR)/isblankrune.o \ + $(SBASE_OBJDIR)/iscntrlrune.o \ + $(SBASE_OBJDIR)/isdigitrune.o \ + $(SBASE_OBJDIR)/isgraphrune.o \ + $(SBASE_OBJDIR)/isprintrune.o \ + $(SBASE_OBJDIR)/ispunctrune.o \ + $(SBASE_OBJDIR)/isspacerune.o \ + $(SBASE_OBJDIR)/istitlerune.o \ + $(SBASE_OBJDIR)/isxdigitrune.o \ + $(SBASE_OBJDIR)/lowerrune.o \ + $(SBASE_OBJDIR)/rune.o \ + $(SBASE_OBJDIR)/runetype.o \ + $(SBASE_OBJDIR)/upperrune.o \ + $(SBASE_OBJDIR)/utf.o \ + $(SBASE_OBJDIR)/utftorunestr.o + +SBASE_BINS =\ + basename\ + cal\ + cat\ + chgrp\ + chmod\ + chown\ + chroot\ + cksum\ + cmp\ + cols\ + comm\ + cp\ + cron\ + cut\ + date\ + dirname\ + du\ + echo\ + ed\ + env\ + expand\ + expr\ + false\ + find\ + flock\ + fold\ + getconf\ + grep\ + head\ + join\ + hostname\ + kill\ + link\ + ln\ + logger\ + logname\ + ls\ + md5sum\ + mkdir\ + mkfifo\ + mktemp\ + mv\ + nice\ + nl\ + nohup\ + od\ + pathchk\ + paste\ + printenv\ + printf\ + pwd\ + readlink\ + renice\ + rm\ + rmdir\ + sed\ + seq\ + setsid\ + sha1sum\ + sha224sum\ + sha256sum\ + sha384sum\ + sha512sum\ + sha512-224sum\ + sha512-256sum\ + sleep\ + sort\ + split\ + sponge\ + strings\ + sync\ + tail\ + tar\ + tee\ + test\ + tftp\ + time\ + touch\ + tr\ + true\ + tsort\ + tty\ + uname\ + unexpand\ + uniq\ + unlink\ + uudecode\ + uuencode\ + wc\ + which\ + whoami\ + xargs\ + xinstall\ + yes + +$(SBASE_LIBUTIL): $(SBASE_LIBUTIL_OBJS) + $(SBASE_ARCHIVE) +$(SBASE_OBJDIR)/concat.o: $(SBASE_SUBDIR)/libutil/concat.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/cp.o: $(SBASE_SUBDIR)/libutil/cp.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/crypt.o: $(SBASE_SUBDIR)/libutil/crypt.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/ealloc.o: $(SBASE_SUBDIR)/libutil/ealloc.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/enmasse.o: $(SBASE_SUBDIR)/libutil/enmasse.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/eprintf.o: $(SBASE_SUBDIR)/libutil/eprintf.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/eregcomp.o: $(SBASE_SUBDIR)/libutil/eregcomp.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/estrtod.o: $(SBASE_SUBDIR)/libutil/estrtod.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/fnck.o: $(SBASE_SUBDIR)/libutil/fnck.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/fshut.o: $(SBASE_SUBDIR)/libutil/fshut.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/getlines.o: $(SBASE_SUBDIR)/libutil/getlines.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/human.o: $(SBASE_SUBDIR)/libutil/human.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/linecmp.o: $(SBASE_SUBDIR)/libutil/linecmp.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/md5.o: $(SBASE_SUBDIR)/libutil/md5.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/memmem.o: $(SBASE_SUBDIR)/libutil/memmem.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/mkdirp.o: $(SBASE_SUBDIR)/libutil/mkdirp.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/mode.o: $(SBASE_SUBDIR)/libutil/mode.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/parseoffset.o: $(SBASE_SUBDIR)/libutil/parseoffset.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/putword.o: $(SBASE_SUBDIR)/libutil/putword.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/reallocarray.o: $(SBASE_SUBDIR)/libutil/reallocarray.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/recurse.o: $(SBASE_SUBDIR)/libutil/recurse.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/rm.o: $(SBASE_SUBDIR)/libutil/rm.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/sha1.o: $(SBASE_SUBDIR)/libutil/sha1.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/sha224.o: $(SBASE_SUBDIR)/libutil/sha224.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/sha256.o: $(SBASE_SUBDIR)/libutil/sha256.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/sha384.o: $(SBASE_SUBDIR)/libutil/sha384.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/sha512-224.o: $(SBASE_SUBDIR)/libutil/sha512-224.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/sha512-256.o: $(SBASE_SUBDIR)/libutil/sha512-256.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/sha512.o: $(SBASE_SUBDIR)/libutil/sha512.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/strcasestr.o: $(SBASE_SUBDIR)/libutil/strcasestr.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/strlcat.o: $(SBASE_SUBDIR)/libutil/strlcat.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/strlcpy.o: $(SBASE_SUBDIR)/libutil/strlcpy.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/strsep.o: $(SBASE_SUBDIR)/libutil/strsep.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/strtonum.o: $(SBASE_SUBDIR)/libutil/strtonum.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/unescape.o: $(SBASE_SUBDIR)/libutil/unescape.c + $(SBASE_COMPILE) + + +$(SBASE_LIBUTF): $(SBASE_LIBUTF_OBJS) + $(SBASE_ARCHIVE) +$(SBASE_OBJDIR)/fgetrune.o: $(SBASE_SUBDIR)/libutf/fgetrune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/fputrune.o: $(SBASE_SUBDIR)/libutf/fputrune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/isalnumrune.o: $(SBASE_SUBDIR)/libutf/isalnumrune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/isalpharune.o: $(SBASE_SUBDIR)/libutf/isalpharune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/isblankrune.o: $(SBASE_SUBDIR)/libutf/isblankrune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/iscntrlrune.o: $(SBASE_SUBDIR)/libutf/iscntrlrune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/isdigitrune.o: $(SBASE_SUBDIR)/libutf/isdigitrune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/isgraphrune.o: $(SBASE_SUBDIR)/libutf/isgraphrune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/isprintrune.o: $(SBASE_SUBDIR)/libutf/isprintrune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/ispunctrune.o: $(SBASE_SUBDIR)/libutf/ispunctrune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/isspacerune.o: $(SBASE_SUBDIR)/libutf/isspacerune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/istitlerune.o: $(SBASE_SUBDIR)/libutf/istitlerune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/isxdigitrune.o: $(SBASE_SUBDIR)/libutf/isxdigitrune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/lowerrune.o: $(SBASE_SUBDIR)/libutf/lowerrune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/rune.o: $(SBASE_SUBDIR)/libutf/rune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/runetype.o: $(SBASE_SUBDIR)/libutf/runetype.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/upperrune.o: $(SBASE_SUBDIR)/libutf/upperrune.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/utf.o: $(SBASE_SUBDIR)/libutf/utf.c + $(SBASE_COMPILE) +$(SBASE_OBJDIR)/utftorunestr.o: $(SBASE_SUBDIR)/libutf/utftorunestr.c + $(SBASE_COMPILE) + +basename: $(BINDIR)/basename +$(BINDIR)/basename: $(SBASE_SUBDIR)/basename.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +cal: $(BINDIR)/cal +$(BINDIR)/cal: $(SBASE_SUBDIR)/cal.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +cat: $(BINDIR)/cat +$(BINDIR)/cat: $(SBASE_SUBDIR)/cat.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +chgrp: $(BINDIR)/chgrp +$(BINDIR)/chgrp: $(SBASE_SUBDIR)/chgrp.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +chmod: $(BINDIR)/chmod +$(BINDIR)/chmod: $(SBASE_SUBDIR)/chmod.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +chown: $(BINDIR)/chown +$(BINDIR)/chown: $(SBASE_SUBDIR)/chown.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +chroot: $(BINDIR)/chroot +$(BINDIR)/chroot: $(SBASE_SUBDIR)/chroot.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +cksum: $(BINDIR)/cksum +$(BINDIR)/cksum: $(SBASE_SUBDIR)/cksum.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +cmp: $(BINDIR)/cmp +$(BINDIR)/cmp: $(SBASE_SUBDIR)/cmp.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +cols: $(BINDIR)/cols +$(BINDIR)/cols: $(SBASE_SUBDIR)/cols.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +comm: $(BINDIR)/comm +$(BINDIR)/comm: $(SBASE_SUBDIR)/comm.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +cp: $(BINDIR)/cp +$(BINDIR)/cp: $(SBASE_SUBDIR)/cp.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +cron: $(BINDIR)/cron +$(BINDIR)/cron: $(SBASE_SUBDIR)/cron.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +cut: $(BINDIR)/cut +$(BINDIR)/cut: $(SBASE_SUBDIR)/cut.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +date: $(BINDIR)/date +$(BINDIR)/date: $(SBASE_SUBDIR)/date.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +dirname: $(BINDIR)/dirname +$(BINDIR)/dirname: $(SBASE_SUBDIR)/dirname.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +du: $(BINDIR)/du +$(BINDIR)/du: $(SBASE_SUBDIR)/du.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +echo: $(BINDIR)/echo +$(BINDIR)/echo: $(SBASE_SUBDIR)/echo.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +ed: $(BINDIR)/ed +$(BINDIR)/ed: $(SBASE_SUBDIR)/ed.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +env: $(BINDIR)/env +$(BINDIR)/env: $(SBASE_SUBDIR)/env.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +expand: $(BINDIR)/expand +$(BINDIR)/expand: $(SBASE_SUBDIR)/expand.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +expr: $(BINDIR)/expr +$(BINDIR)/expr: $(SBASE_SUBDIR)/expr.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +false: $(BINDIR)/false +$(BINDIR)/false: $(SBASE_SUBDIR)/false.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +find: $(BINDIR)/find +$(BINDIR)/find: $(SBASE_SUBDIR)/find.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +flock: $(BINDIR)/flock +$(BINDIR)/flock: $(SBASE_SUBDIR)/flock.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +fold: $(BINDIR)/fold +$(BINDIR)/fold: $(SBASE_SUBDIR)/fold.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +getconf: $(BINDIR)/getconf +$(BINDIR)/getconf: $(SBASE_SUBDIR)/getconf.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +grep: $(BINDIR)/grep +$(BINDIR)/grep: $(SBASE_SUBDIR)/grep.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +head: $(BINDIR)/head +$(BINDIR)/head: $(SBASE_SUBDIR)/head.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +join: $(BINDIR)/join +$(BINDIR)/join: $(SBASE_SUBDIR)/join.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +hostname: $(BINDIR)/hostname +$(BINDIR)/hostname: $(SBASE_SUBDIR)/hostname.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +kill: $(BINDIR)/kill +$(BINDIR)/kill: $(SBASE_SUBDIR)/kill.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +link: $(BINDIR)/link +$(BINDIR)/link: $(SBASE_SUBDIR)/link.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +ln: $(BINDIR)/ln +$(BINDIR)/ln: $(SBASE_SUBDIR)/ln.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +logger: $(BINDIR)/logger +$(BINDIR)/logger: $(SBASE_SUBDIR)/logger.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +logname: $(BINDIR)/logname +$(BINDIR)/logname: $(SBASE_SUBDIR)/logname.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +ls: $(BINDIR)/ls +$(BINDIR)/ls: $(SBASE_SUBDIR)/ls.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +md5sum: $(BINDIR)/md5sum +$(BINDIR)/md5sum: $(SBASE_SUBDIR)/md5sum.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +mkdir: $(BINDIR)/mkdir +$(BINDIR)/mkdir: $(SBASE_SUBDIR)/mkdir.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +mkfifo: $(BINDIR)/mkfifo +$(BINDIR)/mkfifo: $(SBASE_SUBDIR)/mkfifo.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +mktemp: $(BINDIR)/mktemp +$(BINDIR)/mktemp: $(SBASE_SUBDIR)/mktemp.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +mv: $(BINDIR)/mv +$(BINDIR)/mv: $(SBASE_SUBDIR)/mv.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +nice: $(BINDIR)/nice +$(BINDIR)/nice: $(SBASE_SUBDIR)/nice.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +nl: $(BINDIR)/nl +$(BINDIR)/nl: $(SBASE_SUBDIR)/nl.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +nohup: $(BINDIR)/nohup +$(BINDIR)/nohup: $(SBASE_SUBDIR)/nohup.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +od: $(BINDIR)/od +$(BINDIR)/od: $(SBASE_SUBDIR)/od.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +pathchk: $(BINDIR)/pathchk +$(BINDIR)/pathchk: $(SBASE_SUBDIR)/pathchk.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +paste: $(BINDIR)/paste +$(BINDIR)/paste: $(SBASE_SUBDIR)/paste.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +printenv: $(BINDIR)/printenv +$(BINDIR)/printenv: $(SBASE_SUBDIR)/printenv.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +printf: $(BINDIR)/printf +$(BINDIR)/printf: $(SBASE_SUBDIR)/printf.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +pwd: $(BINDIR)/pwd +$(BINDIR)/pwd: $(SBASE_SUBDIR)/pwd.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +readlink: $(BINDIR)/readlink +$(BINDIR)/readlink: $(SBASE_SUBDIR)/readlink.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +renice: $(BINDIR)/renice +$(BINDIR)/renice: $(SBASE_SUBDIR)/renice.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +rm: $(BINDIR)/rm +$(BINDIR)/rm: $(SBASE_SUBDIR)/rm.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +rmdir: $(BINDIR)/rmdir +$(BINDIR)/rmdir: $(SBASE_SUBDIR)/rmdir.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +sed: $(BINDIR)/sed +$(BINDIR)/sed: $(SBASE_SUBDIR)/sed.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +seq: $(BINDIR)/seq +$(BINDIR)/seq: $(SBASE_SUBDIR)/seq.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +setsid: $(BINDIR)/setsid +$(BINDIR)/setsid: $(SBASE_SUBDIR)/setsid.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +sha1sum: $(BINDIR)/sha1sum +$(BINDIR)/sha1sum: $(SBASE_SUBDIR)/sha1sum.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +sha224sum: $(BINDIR)/sha224sum +$(BINDIR)/sha224sum: $(SBASE_SUBDIR)/sha224sum.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +sha256sum: $(BINDIR)/sha256sum +$(BINDIR)/sha256sum: $(SBASE_SUBDIR)/sha256sum.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +sha384sum: $(BINDIR)/sha384sum +$(BINDIR)/sha384sum: $(SBASE_SUBDIR)/sha384sum.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +sha512sum: $(BINDIR)/sha512sum +$(BINDIR)/sha512sum: $(SBASE_SUBDIR)/sha512sum.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +sha512-224sum: $(BINDIR)/sha512-224sum +$(BINDIR)/sha512-224sum: $(SBASE_SUBDIR)/sha512-224sum.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +sha512-256sum: $(BINDIR)/sha512-256sum +$(BINDIR)/sha512-256sum: $(SBASE_SUBDIR)/sha512-256sum.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +sleep: $(BINDIR)/sleep +$(BINDIR)/sleep: $(SBASE_SUBDIR)/sleep.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +sort: $(BINDIR)/sort +$(BINDIR)/sort: $(SBASE_SUBDIR)/sort.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +split: $(BINDIR)/split +$(BINDIR)/split: $(SBASE_SUBDIR)/split.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +sponge: $(BINDIR)/sponge +$(BINDIR)/sponge: $(SBASE_SUBDIR)/sponge.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +strings: $(BINDIR)/strings +$(BINDIR)/strings: $(SBASE_SUBDIR)/strings.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +sync: $(BINDIR)/sync +$(BINDIR)/sync: $(SBASE_SUBDIR)/sync.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +tail: $(BINDIR)/tail +$(BINDIR)/tail: $(SBASE_SUBDIR)/tail.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +tar: $(BINDIR)/tar +$(BINDIR)/tar: $(SBASE_SUBDIR)/tar.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +tee: $(BINDIR)/tee +$(BINDIR)/tee: $(SBASE_SUBDIR)/tee.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +test: $(BINDIR)/test +$(BINDIR)/test: $(SBASE_SUBDIR)/test.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +tftp: $(BINDIR)/tftp +$(BINDIR)/tftp: $(SBASE_SUBDIR)/tftp.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +time: $(BINDIR)/time +$(BINDIR)/time: $(SBASE_SUBDIR)/time.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +touch: $(BINDIR)/touch +$(BINDIR)/touch: $(SBASE_SUBDIR)/touch.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +tr: $(BINDIR)/tr +$(BINDIR)/tr: $(SBASE_SUBDIR)/tr.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +true: $(BINDIR)/true +$(BINDIR)/true: $(SBASE_SUBDIR)/true.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +tsort: $(BINDIR)/tsort +$(BINDIR)/tsort: $(SBASE_SUBDIR)/tsort.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +tty: $(BINDIR)/tty +$(BINDIR)/tty: $(SBASE_SUBDIR)/tty.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +uname: $(BINDIR)/uname +$(BINDIR)/uname: $(SBASE_SUBDIR)/uname.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +unexpand: $(BINDIR)/unexpand +$(BINDIR)/unexpand: $(SBASE_SUBDIR)/unexpand.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +uniq: $(BINDIR)/uniq +$(BINDIR)/uniq: $(SBASE_SUBDIR)/uniq.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +unlink: $(BINDIR)/unlink +$(BINDIR)/unlink: $(SBASE_SUBDIR)/unlink.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +uudecode: $(BINDIR)/uudecode +$(BINDIR)/uudecode: $(SBASE_SUBDIR)/uudecode.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +uuencode: $(BINDIR)/uuencode +$(BINDIR)/uuencode: $(SBASE_SUBDIR)/uuencode.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +wc: $(BINDIR)/wc +$(BINDIR)/wc: $(SBASE_SUBDIR)/wc.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +which: $(BINDIR)/which +$(BINDIR)/which: $(SBASE_SUBDIR)/which.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +whoami: $(BINDIR)/whoami +$(BINDIR)/whoami: $(SBASE_SUBDIR)/whoami.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +xargs: $(BINDIR)/xargs +$(BINDIR)/xargs: $(SBASE_SUBDIR)/xargs.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +xinstall: $(BINDIR)/xinstall +$(BINDIR)/xinstall: $(SBASE_SUBDIR)/xinstall.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + +yes: $(BINDIR)/yes +$(BINDIR)/yes: $(SBASE_SUBDIR)/yes.c $(SBASE_LIBUTIL) $(SBASE_LIBUTF) + $(SBASE_BUILD) + diff --git a/source/sbase/TODO b/source/sbase/TODO new file mode 100644 index 00000000..225034ab --- /dev/null +++ b/source/sbase/TODO @@ -0,0 +1,22 @@ +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 manpage +patch +stty + +If you are looking for some work to do on sbase, another option is to +pick a utility from the list in the README which has missing flags or +features noted. + +What also needs to be implemented is the capability of the tools to +handle data with NUL-bytes in it. + +[0] http://landley.net/toybox/roadmap.html +[1] http://git.suckless.org/ubase/ diff --git a/source/sbase/arg.h b/source/sbase/arg.h new file mode 100644 index 00000000..0b23c53a --- /dev/null +++ b/source/sbase/arg.h @@ -0,0 +1,65 @@ +/* + * 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][0] == '-'\ + && argv[0][1];\ + 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() (brk_ = 1, estrtonum(argv[0], 0, INT_MAX)) + +#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]))) + +#define LNGARG() &argv[0][0] + +#endif diff --git a/source/sbase/basename.1 b/source/sbase/basename.1 new file mode 100644 index 00000000..27ee9838 --- /dev/null +++ b/source/sbase/basename.1 @@ -0,0 +1,26 @@ +.Dd 2015-10-08 +.Dt BASENAME 1 +.Os sbase +.Sh NAME +.Nm basename +.Nd strip leading directory components of a path +.Sh SYNOPSIS +.Nm +.Ar path +.Op Ar suffix +.Sh DESCRIPTION +.Nm +writes +.Ar path +without leading directory components and +.Ar suffix +to stdout. +.Sh SEE ALSO +.Xr dirname 1 , +.Xr basename 3 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/basename.c b/source/sbase/basename.c new file mode 100644 index 00000000..4a6ac2ac --- /dev/null +++ b/source/sbase/basename.c @@ -0,0 +1,34 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s path [suffix]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + ssize_t off; + char *p; + + argv0 = argv[0], argc--, argv++; + + if (argc != 1 && argc != 2) + usage(); + + p = basename(argv[0]); + if (argc == 2) { + off = strlen(p) - strlen(argv[1]); + if (off > 0 && !strcmp(p + off, argv[1])) + p[off] = '\0'; + } + puts(p); + + return fshut(stdout, ""); +} diff --git a/source/sbase/cal.1 b/source/sbase/cal.1 new file mode 100644 index 00000000..38f862da --- /dev/null +++ b/source/sbase/cal.1 @@ -0,0 +1,70 @@ +.Dd 2015-10-08 +.Dt CAL 1 +.Os sbase +.Sh NAME +.Nm cal +.Nd show calendar +.Sh SYNOPSIS +.Nm +.Op Fl 1 | Fl 3 | Fl y | Fl n Ar num +.Op Fl s | Fl m | Fl f Ar num +.Op Fl c Ar num +.Oo Oo Ar month Oc Ar year Oc +.Sh DESCRIPTION +.Nm +writes a calendar of +.Ar month +and +.Ar year +or the current month to stdout. +If +.Ar year +is given without +.Ar month , +.Nm +writes a 3-column calendar of the whole +year to stdout. +The date formatting is according to +.Xr localtime 3 . +.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 +Print current month. This is the default. +.It Fl 3 +Print previous, current and next month. +.It Fl c Ar num +Print +.Ar num +calendars in a row. The default is 3. +.It Fl f Ar num +Set +.Ar num +(0 is Sunday, 6 is Saturday) as first day of week. +.It Fl m +Set Monday as first day of week. +.It Fl n Ar num +Output +.Ar num +months starting from and including the current month. +.It Fl s +Set Sunday as first day of week. +.It Fl y +Print the entire +.Ar year +or current year. +.El +.Sh SEE ALSO +.Xr localtime 3 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The flags +.Op Fl 13cfmnsy +are an extension to that specification. diff --git a/source/sbase/cal.c b/source/sbase/cal.c new file mode 100644 index 00000000..b403d00f --- /dev/null +++ b/source/sbase/cal.c @@ -0,0 +1,223 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include + +#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 struct tm *ltime; + +static int +isleap(size_t 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(size_t 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(size_t year, int month, int dom, enum caltype cal) +{ + size_t y; + int m, a; + + a = (13 - month) / 12; + y = year - a; + m = month + 12 * a - 1; + + 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(size_t year, int month, int fday, int line) +{ + enum caltype cal; + int offset, dom, d = 0, trans; /* are we in the transition from Julian to Gregorian? */ + int today = 0; + + cal = (year < TRANS_YEAR || (year == TRANS_YEAR && month <= TRANS_MONTH)) ? JULIAN : 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; + } + if (ltime && year == ltime->tm_year + 1900 && month == ltime->tm_mon) + today = ltime->tm_mday; + for (; d < 7 && dom <= monthlength(year, month, cal); ++d, ++dom) { + if (dom == today) + printf("\x1b[7m%2d\x1b[0m ", dom); /* highlight today's date */ + else + printf("%2d ", dom); + if (trans && dom == TRANS_DAY) + dom += 11; + } + for (; d < 7; ++d) + printf(" "); +} + +static void +drawcal(size_t year, int month, size_t ncols, size_t 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", }; + size_t m, n, col, cur_year, cur_month, dow; + int line; + + 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 %zu ", smon[cur_month], cur_year); + printf(" "); + } + putchar('\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(" "); + } + putchar('\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(" "); + } + putchar('\n'); + } + } +} + +static void +usage(void) +{ + eprintf("usage: %s [-1 | -3 | -y | -n num] " + "[-s | -m | -f num] [-c num] [[month] year]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + time_t now; + size_t year, ncols, nmons; + int fday, month; + + now = time(NULL); + ltime = localtime(&now); + year = ltime->tm_year + 1900; + month = ltime->tm_mon + 1; + fday = 0; + + if (!isatty(STDOUT_FILENO)) + ltime = NULL; /* don't highlight today's date */ + + ncols = 3; + nmons = 0; + + ARGBEGIN { + case '1': + nmons = 1; + break; + case '3': + nmons = 3; + if (--month == 0) { + month = 12; + year--; + } + break; + case 'c': + ncols = estrtonum(EARGF(usage()), 0, MIN(SIZE_MAX, LLONG_MAX)); + break; + case 'f': + fday = estrtonum(EARGF(usage()), 0, 6); + break; + case 'm': /* Monday */ + fday = 1; + break; + case 'n': + nmons = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX)); + 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 = estrtonum(argv[0], 1, 12); + argv++; + case 1: /* fallthrough */ + year = estrtonum(argv[0], 0, INT_MAX); + break; + case 0: + break; + default: + usage(); + } + + drawcal(year, month - 1, ncols, nmons, fday); + + return fshut(stdout, ""); +} diff --git a/source/sbase/cat.1 b/source/sbase/cat.1 new file mode 100644 index 00000000..9b67e8fb --- /dev/null +++ b/source/sbase/cat.1 @@ -0,0 +1,30 @@ +.Dd 2015-10-08 +.Dt CAT 1 +.Os sbase +.Sh NAME +.Nm cat +.Nd concatenate files +.Sh SYNOPSIS +.Nm +.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-2013 +specification. diff --git a/source/sbase/cat.c b/source/sbase/cat.c new file mode 100644 index 00000000..e3741aae --- /dev/null +++ b/source/sbase/cat.c @@ -0,0 +1,61 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "text.h" +#include "util.h" + +static void +uconcat(FILE *fp1, const char *s1, FILE *fp2, const char *s2) +{ + int c; + + setbuf(fp2, NULL); + while ((c = getc(fp1)) != EOF) + putc(c, fp2); +} + +static void +usage(void) +{ + eprintf("usage: %s [-u] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + int ret = 0; + void (*cat)(FILE *, const char *, FILE *, const char *) = &concat; + + ARGBEGIN { + case 'u': + cat = &uconcat; + break; + default: + usage(); + } ARGEND + + if (!argc) { + cat(stdin, "", stdout, ""); + } else { + for (; *argv; argc--, argv++) { + if (!strcmp(*argv, "-")) { + *argv = ""; + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + ret = 1; + continue; + } + cat(fp, *argv, stdout, ""); + if (fp != stdin && fshut(fp, *argv)) + ret = 1; + } + } + + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/chgrp.1 b/source/sbase/chgrp.1 new file mode 100644 index 00000000..5efa2e74 --- /dev/null +++ b/source/sbase/chgrp.1 @@ -0,0 +1,50 @@ +.Dd 2015-10-08 +.Dt CHGRP 1 +.Os sbase +.Sh NAME +.Nm chgrp +.Nd change file group ownership +.Sh SYNOPSIS +.Nm +.Op Fl h +.Oo +.Fl R +.Op Fl H | L | P +.Oc +.Ar group +.Ar file ... +.Sh DESCRIPTION +.Nm +sets the group id of each +.Ar file +to the gid of +.Ar group . +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl h +Preserve +.Ar file +if it is a symbolic link. +.It Fl R +Change file group ownerships recursively. +.It Fl H +Dereference +.Ar file +if it is a symbolic link. +.It Fl L +Dereference all symbolic links. +.It Fl P +Preserve symbolic links. This is the default. +.El +.Sh SEE ALSO +.Xr chmod 1 , +.Xr chown 1 , +.Xr chmod 2 , +.Xr chown 2 , +.Xr getgrnam 3 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/chgrp.c b/source/sbase/chgrp.c new file mode 100644 index 00000000..299238bf --- /dev/null +++ b/source/sbase/chgrp.c @@ -0,0 +1,82 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include + +#include "fs.h" +#include "util.h" + +static int hflag = 0; +static gid_t gid = -1; +static int ret = 0; + +static void +chgrp(const char *path, struct stat *st, void *data, struct recursor *r) +{ + char *chownf_name; + int (*chownf)(const char *, uid_t, gid_t); + + if (r->follow == 'P' || (r->follow == 'H' && r->depth) || (hflag && !(r->depth))) { + chownf_name = "lchown"; + chownf = lchown; + } else { + chownf_name = "chown"; + chownf = chown; + } + + if (st && chownf(path, st->st_uid, gid) < 0) { + weprintf("%s %s:", chownf_name, path); + ret = 1; + } else if (st && S_ISDIR(st->st_mode)) { + recurse(path, NULL, r); + } +} + +static void +usage(void) +{ + eprintf("usage: %s [-h] [-R [-H | -L | -P]] group file ...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct group *gr; + struct recursor r = { .fn = chgrp, .hist = NULL, .depth = 0, .maxdepth = 1, + .follow = 'P', .flags = 0 }; + + ARGBEGIN { + case 'h': + hflag = 1; + break; + case 'R': + r.maxdepth = 0; + break; + case 'H': + case 'L': + case 'P': + r.follow = ARGC(); + break; + default: + usage(); + } ARGEND + + if (argc < 2) + usage(); + + errno = 0; + if (!(gr = getgrnam(argv[0]))) { + if (errno) + eprintf("getgrnam %s:", argv[0]); + else + eprintf("getgrnam %s: no such group\n", argv[0]); + } + gid = gr->gr_gid; + + for (argc--, argv++; *argv; argc--, argv++) + recurse(*argv, NULL, &r); + + return ret || recurse_status; +} diff --git a/source/sbase/chmod.1 b/source/sbase/chmod.1 new file mode 100644 index 00000000..4805ce05 --- /dev/null +++ b/source/sbase/chmod.1 @@ -0,0 +1,84 @@ +.Dd 2015-10-08 +.Dt CHMOD 1 +.Os sbase +.Sh NAME +.Nm chmod +.Nd change file modes +.Sh SYNOPSIS +.Nm +.Oo +.Fl R +.Op Fl H | L | P +.Oc +.Ar mode +.Ar file ... +.Sh DESCRIPTION +.Nm +changes the file mode of each +.Ar file +to +.Ar mode . +.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. +.It Fl H +Dereference +.Ar file +if it is a symbolic link. +.It Fl L +Dereference all symbolic links. +.It Fl P +Preserve symbolic links. This is the default. +.El +.Sh SEE ALSO +.Xr chgrp 1 , +.Xr umask 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The +.Op Fl HLP +flags are an extension to that specification. diff --git a/source/sbase/chmod.c b/source/sbase/chmod.c new file mode 100644 index 00000000..a0d96f18 --- /dev/null +++ b/source/sbase/chmod.c @@ -0,0 +1,82 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "fs.h" +#include "util.h" + +static char *modestr = ""; +static mode_t mask = 0; +static int ret = 0; + +static void +chmodr(const char *path, struct stat *st, void *data, struct recursor *r) +{ + mode_t m; + + m = parsemode(modestr, st ? st->st_mode : 0, mask); + if (chmod(path, m) < 0) { + weprintf("chmod %s:", path); + ret = 1; + } else if (st && S_ISDIR(st->st_mode)) { + recurse(path, NULL, r); + } +} + +static void +usage(void) +{ + eprintf("usage: %s [-R [-H | -L | -P]] mode file ...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct recursor r = { .fn = chmodr, .hist = NULL, .depth = 0, .maxdepth = 1, + .follow = 'P', .flags = 0 }; + size_t i; + + argv0 = argv[0], argc--, argv++; + + for (; *argv && (*argv)[0] == '-'; argc--, argv++) { + if (!(*argv)[1]) + usage(); + for (i = 1; (*argv)[i]; i++) { + switch ((*argv)[i]) { + case 'R': + r.maxdepth = 0; + break; + case 'H': + case 'L': + case 'P': + r.follow = (*argv)[i]; + break; + case 'r': case 'w': case 'x': case 's': case 't': + /* -[rwxst] are valid modes, so we're done */ + if (i == 1) + goto done; + /* fallthrough */ + case '-': + /* -- terminator */ + if (i == 1 && !(*argv)[i + 1]) { + argv++; + argc--; + goto done; + } + /* fallthrough */ + default: + usage(); + } + } + } +done: + mask = getumask(); + modestr = *argv; + + if (argc < 2) + usage(); + + for (--argc, ++argv; *argv; argc--, argv++) + recurse(*argv, NULL, &r); + + return ret || recurse_status; +} diff --git a/source/sbase/chown.1 b/source/sbase/chown.1 new file mode 100644 index 00000000..d2bebca5 --- /dev/null +++ b/source/sbase/chown.1 @@ -0,0 +1,60 @@ +.Dd 2015-10-08 +.Dt CHOWN 1 +.Os sbase +.Sh NAME +.Nm chown +.Nd change file ownership +.Sh SYNOPSIS +.Nm +.Op Fl h +.Oo +.Fl R +.Op Fl H | L | P +.Oc +.Ar owner Ns Op Pf : Op Ar group +.Op Ar file ... +.Nm +.Op Fl h +.Oo +.Fl R +.Op Fl H | L | P +.Oc +.Pf : Ar group +.Op Ar file ... +.Sh DESCRIPTION +.Nm +sets the user and/or group id of each +.Ar file +to the uid of +.Ar owner +and/or the gid of +.Ar group +respectively. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl h +Preserve +.Ar file +if it is a symbolic link. +.It Fl R +Change file ownerships recursively. +.It Fl H +Dereference +.Ar file +if it is a symbolic link. +.It Fl L +Dereference all symbolic links. +.It Fl P +Preserve symbolic links. This is the default. +.El +.Sh SEE ALSO +.Xr chmod 1 , +.Xr chown 2 , +.Xr getgrnam 3 , +.Xr getpwnam 3 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/chown.c b/source/sbase/chown.c new file mode 100644 index 00000000..2009507a --- /dev/null +++ b/source/sbase/chown.c @@ -0,0 +1,110 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include + +#include "fs.h" +#include "util.h" + +static int hflag = 0; +static uid_t uid = -1; +static gid_t gid = -1; +static int ret = 0; + +static void +chownpwgr(const char *path, struct stat *st, void *data, struct recursor *r) +{ + char *chownf_name; + int (*chownf)(const char *, uid_t, gid_t); + + if (r->follow == 'P' || (r->follow == 'H' && r->depth) || (hflag && !(r->depth))) { + chownf_name = "lchown"; + chownf = lchown; + } else { + chownf_name = "chown"; + chownf = chown; + } + + if (chownf(path, uid, gid) < 0) { + weprintf("%s %s:", chownf_name, path); + ret = 1; + } else if (st && S_ISDIR(st->st_mode)) { + recurse(path, NULL, r); + } +} + +static void +usage(void) +{ + eprintf("usage: %s [-h] [-R [-H | -L | -P]] owner[:[group]] file ...\n" + " %s [-h] [-R [-H | -L | -P]] :group file ...\n", + argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + struct group *gr; + struct passwd *pw; + struct recursor r = { .fn = chownpwgr, .hist = NULL, .depth = 0, .maxdepth = 1, + .follow = 'P', .flags = 0 }; + char *owner, *group; + + ARGBEGIN { + case 'h': + hflag = 1; + break; + case 'r': + case 'R': + r.maxdepth = 0; + break; + case 'H': + case 'L': + case 'P': + r.follow = ARGC(); + break; + default: + usage(); + } ARGEND + + if (argc < 2) + usage(); + + owner = argv[0]; + if ((group = strchr(owner, ':'))) + *group++ = '\0'; + + if (owner && *owner) { + errno = 0; + pw = getpwnam(owner); + if (pw) { + uid = pw->pw_uid; + } else { + if (errno) + eprintf("getpwnam %s:", owner); + uid = estrtonum(owner, 0, UINT_MAX); + } + } + if (group && *group) { + errno = 0; + gr = getgrnam(group); + if (gr) { + gid = gr->gr_gid; + } else { + if (errno) + eprintf("getgrnam %s:", group); + gid = estrtonum(group, 0, UINT_MAX); + } + } + if (uid == (uid_t)-1 && gid == (gid_t)-1) + usage(); + + for (argc--, argv++; *argv; argc--, argv++) + recurse(*argv, NULL, &r); + + return ret || recurse_status; +} diff --git a/source/sbase/chroot.1 b/source/sbase/chroot.1 new file mode 100644 index 00000000..de8761fa --- /dev/null +++ b/source/sbase/chroot.1 @@ -0,0 +1,25 @@ +.Dd 2015-10-08 +.Dt CHROOT 1 +.Os sbase +.Sh NAME +.Nm chroot +.Nd run a command or shell with a different root directory +.Sh SYNOPSIS +.Nm +.Ar dir +.Op Ar cmd Op Ar arg ... +.Sh DESCRIPTION +.Nm +runs +.Ar cmd +after changing the root directory to +.Ar dir +with the +.Xr chroot 2 +system call and after changing the working directory to the new root. +If +.Ar cmd +is not specified, an interactive shell is started in the new root. +.Sh SEE ALSO +.Xr chdir 2 , +.Xr chroot 2 diff --git a/source/sbase/chroot.c b/source/sbase/chroot.c new file mode 100644 index 00000000..0b797cc1 --- /dev/null +++ b/source/sbase/chroot.c @@ -0,0 +1,46 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s dir [cmd [arg ...]]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *shell[] = { "/bin/sh", "-i", NULL }, *aux, *cmd; + int savederrno; + + argv0 = argv[0], argc--, argv++; + + if (!argc) + 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) { + cmd = *shell; + execvp(cmd, shell); + } else { + cmd = argv[1]; + execvp(cmd, argv + 1); + } + + savederrno = errno; + weprintf("execvp %s:", cmd); + + _exit(126 + (savederrno == ENOENT)); +} diff --git a/source/sbase/cksum.1 b/source/sbase/cksum.1 new file mode 100644 index 00000000..5f994141 --- /dev/null +++ b/source/sbase/cksum.1 @@ -0,0 +1,28 @@ +.Dd 2015-10-08 +.Dt CKSUM 1 +.Os sbase +.Sh NAME +.Nm cksum +.Nd compute file checksum +.Sh SYNOPSIS +.Nm +.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-2013 +specification. diff --git a/source/sbase/cksum.c b/source/sbase/cksum.c new file mode 100644 index 00000000..570ca81f --- /dev/null +++ b/source/sbase/cksum.c @@ -0,0 +1,120 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "util.h" + +static int ret = 0; +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) +{ + size_t len = 0, i, n; + uint32_t ck = 0; + unsigned char buf[BUFSIZ]; + + while ((n = fread(buf, 1, sizeof(buf), fp))) { + for (i = 0; i < n; i++) + ck = (ck << 8) ^ crctab[(ck >> 24) ^ buf[i]]; + len += n; + } + if (ferror(fp)) { + weprintf("fread %s:", s ? s : ""); + ret = 1; + return; + } + + for (i = len; i; i >>= 8) + ck = (ck << 8) ^ crctab[(ck >> 24) ^ (i & 0xFF)]; + + printf("%"PRIu32" %zu", ~ck, len); + if (s) { + putchar(' '); + fputs(s, stdout); + } + putchar('\n'); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + + argv0 = argv[0], argc--, argv++; + + if (!argc) { + cksum(stdin, NULL); + } else { + for (; *argv; argc--, argv++) { + if (!strcmp(*argv, "-")) { + *argv = ""; + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + ret = 1; + continue; + } + cksum(fp, *argv); + if (fp != stdin && fshut(fp, *argv)) + ret = 1; + } + } + + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/cmp.1 b/source/sbase/cmp.1 new file mode 100644 index 00000000..f613f26d --- /dev/null +++ b/source/sbase/cmp.1 @@ -0,0 +1,52 @@ +.Dd 2015-10-08 +.Dt CMP 1 +.Os sbase +.Sh NAME +.Nm cmp +.Nd compare two files +.Sh SYNOPSIS +.Nm +.Op Fl l | Fl s +.Ar file1 file2 +.Sh DESCRIPTION +.Nm +compares +.Ar file1 +and +.Ar file2 +byte by byte. If they differ, +.Nm +writes the first differing byte- and line-number to stdout. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl l +Print byte-number and bytes (in octal) for each difference. +.It Fl s +Print nothing and only return status. +.El +.Sh EXIT STATUS +.Bl -tag -width Ds +.It 0 +.Ar file1 +and +.Ar file2 +are identical. +.It 1 +.Ar file1 +and +.Ar file2 +are different. +.It > 1 +An error occurred. +.El +.Sh SEE ALSO +.Xr comm 1 , +.Xr diff 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The "char" in the default result format has been replaced with "byte". diff --git a/source/sbase/cmp.c b/source/sbase/cmp.c new file mode 100644 index 00000000..ea8902fd --- /dev/null +++ b/source/sbase/cmp.c @@ -0,0 +1,82 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "util.h" + +static void +usage(void) +{ + enprintf(2, "usage: %s [-l | -s] file1 file2\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp[2]; + size_t line = 1, n; + int ret = 0, lflag = 0, sflag = 0, same = 1, b[2]; + + ARGBEGIN { + case 'l': + lflag = 1; + break; + case 's': + sflag = 1; + break; + default: + usage(); + } ARGEND + + if (argc != 2 || (lflag && sflag)) + usage(); + + for (n = 0; n < 2; n++) { + if (!strcmp(argv[n], "-")) { + argv[n] = ""; + fp[n] = stdin; + } else { + if (!(fp[n] = fopen(argv[n], "r"))) { + if (!sflag) + weprintf("fopen %s:", argv[n]); + return 2; + } + } + } + + for (n = 1; ; n++) { + b[0] = getc(fp[0]); + b[1] = getc(fp[1]); + + if (b[0] == b[1]) { + if (b[0] == EOF) + break; + else if (b[0] == '\n') + line++; + continue; + } else if (b[0] == EOF || b[1] == EOF) { + if (!sflag) + weprintf("cmp: EOF on %s\n", argv[(b[0] != EOF)]); + same = 0; + break; + } else if (!lflag) { + if (!sflag) + printf("%s %s differ: byte %zu, line %zu\n", + argv[0], argv[1], n, line); + same = 0; + break; + } else { + printf("%zu %o %o\n", n, b[0], b[1]); + same = 0; + } + } + + if (!ret) + ret = !same; + if (fshut(fp[0], argv[0]) | (fp[0] != fp[1] && fshut(fp[1], argv[1])) | + fshut(stdout, "")) + ret = 2; + + return ret; +} diff --git a/source/sbase/cols.1 b/source/sbase/cols.1 new file mode 100644 index 00000000..f3628653 --- /dev/null +++ b/source/sbase/cols.1 @@ -0,0 +1,56 @@ +.Dd 2015-10-08 +.Dt COLS 1 +.Os sbase +.Sh NAME +.Nm cols +.Nd columnize output +.Sh SYNOPSIS +.Nm +.Op Fl c Ar num +.Op Ar file ... +.Sh DESCRIPTION +.Nm +reads each +.Ar file +in sequence and writes them to stdout, in as many vertical +columns as will fit in +.Ar num +character columns. +If no +.Ar file +is given, +.Nm +reads from stdin. +.Pp +By default +.Nm cols +tries to figure out the width of the output +device. If that fails, it defaults to 65 chars. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c Ar num +Set maximum number of character columns to +.Ar num , +unless input lines exceed this limit. +.El +.Sh ENVIRONMENT +.Bl -tag -width Ds +.It COLUMNS +The width of the output device. +.El +.Sh HISTORY +.Nm +is similar to +.Xr mc 1 +in Plan 9. It was renamed to +.Nm +to avoid the name collision with the popular file manager +Midnight Commander. +.Sh CAVEATS +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. diff --git a/source/sbase/cols.c b/source/sbase/cols.c new file mode 100644 index 00000000..428cd79d --- /dev/null +++ b/source/sbase/cols.c @@ -0,0 +1,98 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include +#include +#include +#include + +#include "text.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-c num] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + struct winsize w; + struct linebuf b = EMPTY_LINEBUF; + size_t chars = 65, maxlen = 0, i, j, k, len, cols, rows; + int cflag = 0, ret = 0; + char *p; + + ARGBEGIN { + case 'c': + cflag = 1; + chars = estrtonum(EARGF(usage()), 1, MIN(LLONG_MAX, SIZE_MAX)); + break; + default: + usage(); + } ARGEND + + if (!cflag) { + if ((p = getenv("COLUMNS"))) + chars = estrtonum(p, 1, MIN(LLONG_MAX, SIZE_MAX)); + else if (!ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) && w.ws_col > 0) + chars = w.ws_col; + } + + if (!argc) { + getlines(stdin, &b); + } else { + for (; *argv; argc--, argv++) { + if (!strcmp(*argv, "-")) { + *argv = ""; + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + ret = 1; + continue; + } + getlines(fp, &b); + if (fp != stdin && fshut(fp, *argv)) + ret = 1; + } + } + + for (i = 0; i < b.nlines; i++) { + for (j = 0, len = 0; j < b.lines[i].len; j++) { + if (UTF8_POINT(b.lines[i].data[j])) + len++; + } + if (len && b.lines[i].data[b.lines[i].len - 1] == '\n') { + b.lines[i].data[--(b.lines[i].len)] = '\0'; + len--; + } + if (len > maxlen) + maxlen = len; + } + + for (cols = 1; (cols + 1) * maxlen + cols <= chars; cols++); + rows = b.nlines / cols + (b.nlines % cols > 0); + + for (i = 0; i < rows; i++) { + for (j = 0; j < cols && i + j * rows < b.nlines; j++) { + for (k = 0, len = 0; k < b.lines[i + j * rows].len; k++) { + if (UTF8_POINT(b.lines[i + j * rows].data[k])) + len++; + } + fwrite(b.lines[i + j * rows].data, 1, + b.lines[i + j * rows].len, stdout); + if (j < cols - 1) + for (k = len; k < maxlen + 1; k++) + putchar(' '); + } + putchar('\n'); + } + + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/comm.1 b/source/sbase/comm.1 new file mode 100644 index 00000000..3bb84a16 --- /dev/null +++ b/source/sbase/comm.1 @@ -0,0 +1,44 @@ +.Dd 2015-10-08 +.Dt COMM 1 +.Os sbase +.Sh NAME +.Nm comm +.Nd select or reject lines common to two files +.Sh SYNOPSIS +.Nm +.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-2013 +specification. diff --git a/source/sbase/comm.c b/source/sbase/comm.c new file mode 100644 index 00000000..fbd50d9b --- /dev/null +++ b/source/sbase/comm.c @@ -0,0 +1,97 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "text.h" +#include "util.h" + +static int show = 0x07; + +static void +printline(int pos, struct line *line) +{ + int i; + + if (!(show & (0x1 << pos))) + return; + + for (i = 0; i < pos; i++) { + if (show & (0x1 << i)) + putchar('\t'); + } + fwrite(line->data, 1, line->len, stdout); +} + +static void +usage(void) +{ + eprintf("usage: %s [-123] file1 file2\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp[2]; + static struct line line[2]; + size_t linecap[2] = { 0, 0 }; + ssize_t len; + int ret = 0, i, diff = 0, seenline = 0; + + ARGBEGIN { + case '1': + case '2': + case '3': + show &= 0x07 ^ (1 << (ARGC() - '1')); + break; + default: + usage(); + } ARGEND + + if (argc != 2) + usage(); + + for (i = 0; i < 2; i++) { + if (!strcmp(argv[i], "-")) { + argv[i] = ""; + fp[i] = stdin; + } else if (!(fp[i] = fopen(argv[i], "r"))) { + eprintf("fopen %s:", argv[i]); + } + } + + for (;;) { + for (i = 0; i < 2; i++) { + if (diff && i == (diff < 0)) + continue; + if ((len = getline(&(line[i].data), &linecap[i], + fp[i])) > 0) { + line[i].len = len; + seenline = 1; + continue; + } + if (ferror(fp[i])) + eprintf("getline %s:", argv[i]); + if ((diff || seenline) && line[!i].data[0]) + printline(!i, &line[!i]); + while ((len = getline(&(line[!i].data), &linecap[!i], + fp[!i])) > 0) { + line[!i].len = len; + printline(!i, &line[!i]); + } + if (ferror(fp[!i])) + eprintf("getline %s:", argv[!i]); + goto end; + } + diff = linecmp(&line[0], &line[1]); + LIMIT(diff, -1, 1); + seenline = 0; + printline((2 - diff) % 3, &line[MAX(0, diff)]); + } +end: + ret |= fshut(fp[0], argv[0]); + ret |= (fp[0] != fp[1]) && fshut(fp[1], argv[1]); + ret |= fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/compat.h b/source/sbase/compat.h new file mode 100644 index 00000000..e2154a62 --- /dev/null +++ b/source/sbase/compat.h @@ -0,0 +1,6 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX +#endif diff --git a/source/sbase/config.mk b/source/sbase/config.mk new file mode 100644 index 00000000..9fb18da1 --- /dev/null +++ b/source/sbase/config.mk @@ -0,0 +1,16 @@ +# sbase version +VERSION = 0.0 + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +CC = cc +AR = ar +RANLIB = ranlib + +# for NetBSD add -D_NETBSD_SOURCE +# -lrt might be needed on some systems +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_FILE_OFFSET_BITS=64 +CFLAGS = -std=c99 -Wall -pedantic +LDFLAGS = -s diff --git a/source/sbase/confstr_l.h b/source/sbase/confstr_l.h new file mode 100644 index 00000000..ccca3833 --- /dev/null +++ b/source/sbase/confstr_l.h @@ -0,0 +1,51 @@ +#ifdef _CS_PATH + {"PATH", _CS_PATH}, +#endif +#ifdef _CS_POSIX_V7_ILP32_OFF32_CFLAGS + {"POSIX_V7_ILP32_OFF32_CFLAGS", _CS_POSIX_V7_ILP32_OFF32_CFLAGS}, +#endif +#ifdef _CS_POSIX_V7_ILP32_OFF32_LDFLAGS + {"POSIX_V7_ILP32_OFF32_LDFLAGS", _CS_POSIX_V7_ILP32_OFF32_LDFLAGS}, +#endif +#ifdef _CS_POSIX_V7_ILP32_OFF32_LIBS + {"POSIX_V7_ILP32_OFF32_LIBS", _CS_POSIX_V7_ILP32_OFF32_LIBS}, +#endif +#ifdef _CS_POSIX_V7_ILP32_OFFBIG_CFLAGS + {"POSIX_V7_ILP32_OFFBIG_CFLAGS", _CS_POSIX_V7_ILP32_OFFBIG_CFLAGS}, +#endif +#ifdef _CS_POSIX_V7_ILP32_OFFBIG_LDFLAGS + {"POSIX_V7_ILP32_OFFBIG_LDFLAGS", _CS_POSIX_V7_ILP32_OFFBIG_LDFLAGS}, +#endif +#ifdef _CS_POSIX_V7_ILP32_OFFBIG_LIBS + {"POSIX_V7_ILP32_OFFBIG_LIBS", _CS_POSIX_V7_ILP32_OFFBIG_LIBS}, +#endif +#ifdef _CS_POSIX_V7_LP64_OFF64_CFLAGS + {"POSIX_V7_LP64_OFF64_CFLAGS", _CS_POSIX_V7_LP64_OFF64_CFLAGS}, +#endif +#ifdef _CS_POSIX_V7_LP64_OFF64_LDFLAGS + {"POSIX_V7_LP64_OFF64_LDFLAGS", _CS_POSIX_V7_LP64_OFF64_LDFLAGS}, +#endif +#ifdef _CS_POSIX_V7_LP64_OFF64_LIBS + {"POSIX_V7_LP64_OFF64_LIBS", _CS_POSIX_V7_LP64_OFF64_LIBS}, +#endif +#ifdef _CS_POSIX_V7_LPBIG_OFFBIG_CFLAGS + {"POSIX_V7_LPBIG_OFFBIG_CFLAGS", _CS_POSIX_V7_LPBIG_OFFBIG_CFLAGS}, +#endif +#ifdef _CS_POSIX_V7_LPBIG_OFFBIG_LDFLAGS + {"POSIX_V7_LPBIG_OFFBIG_LDFLAGS", _CS_POSIX_V7_LPBIG_OFFBIG_LDFLAGS}, +#endif +#ifdef _CS_POSIX_V7_LPBIG_OFFBIG_LIBS + {"POSIX_V7_LPBIG_OFFBIG_LIBS", _CS_POSIX_V7_LPBIG_OFFBIG_LIBS}, +#endif +#ifdef _CS_POSIX_V7_THREADS_CFLAGS + {"POSIX_V7_THREADS_CFLAGS", _CS_POSIX_V7_THREADS_CFLAGS}, +#endif +#ifdef _CS_POSIX_V7_THREADS_LDFLAGS + {"POSIX_V7_THREADS_LDFLAGS", _CS_POSIX_V7_THREADS_LDFLAGS}, +#endif +#ifdef _CS_POSIX_V7_WIDTH_RESTRICTED_ENVS + {"POSIX_V7_WIDTH_RESTRICTED_ENVS", _CS_POSIX_V7_WIDTH_RESTRICTED_ENVS}, +#endif +#ifdef _CS_V7_ENV + {"V7_ENV", _CS_V7_ENV}, +#endif diff --git a/source/sbase/cp.1 b/source/sbase/cp.1 new file mode 100644 index 00000000..54126e2f --- /dev/null +++ b/source/sbase/cp.1 @@ -0,0 +1,71 @@ +.Dd 2015-10-08 +.Dt CP 1 +.Os sbase +.Sh NAME +.Nm cp +.Nd copy files and directories +.Sh SYNOPSIS +.Nm +.Op Fl afpv +.Oo +.Fl R +.Op Fl H | L | P +.Oc +.Op Ar source ... +.Op Ar dest +.Sh DESCRIPTION +.Nm +copies +.Ar source +to +.Ar dest . +If more than one +.Ar source +is given +.Ar dest +has to be a directory. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl a +Preserve block devices, character devices, sockets and FIFOs. Implies +.Fl p , +.Fl P +and +.Fl R . +.It Fl f +If an existing +.Ar dest +cannot be opened, remove it and try again. +.It Fl p +Preserve mode, timestamp and permissions. +.It Fl v +Write "'source' -> 'dest'" for each +.Ar source +to stdout. +.It Fl H +Dereference +.Ar source +if it is a symbolic link. +.It Fl L +Dereference all symbolic links. +This is the default. +.It Fl P +Preserve symbolic links. +.It Fl R +Traverse directories recursively. If this flag is not specified, directories +are not copied. +.El +.Sh SEE ALSO +.Xr mv 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification except from the +.Op Fl i +flag. +.Pp +The +.Op Fl av +flags are an extension to that specification. diff --git a/source/sbase/cp.c b/source/sbase/cp.c new file mode 100644 index 00000000..d87e77e8 --- /dev/null +++ b/source/sbase/cp.c @@ -0,0 +1,57 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "fs.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-afpv] [-R [-H | -L | -P]] source ... dest\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct stat st; + + ARGBEGIN { + case 'a': + cp_follow = 'P'; + cp_aflag = cp_pflag = cp_rflag = 1; + break; + case 'f': + cp_fflag = 1; + break; + case 'p': + cp_pflag = 1; + break; + case 'r': + case 'R': + cp_rflag = 1; + break; + case 'v': + cp_vflag = 1; + break; + case 'H': + case 'L': + case 'P': + cp_follow = ARGC(); + break; + default: + usage(); + } ARGEND + + if (argc < 2) + usage(); + + if (argc > 2) { + if (stat(argv[argc - 1], &st) < 0) + eprintf("stat %s:", argv[argc - 1]); + if (!S_ISDIR(st.st_mode)) + eprintf("%s: not a directory\n", argv[argc - 1]); + } + enmasse(argc, argv, cp); + + return fshut(stdout, "") || cp_status; +} diff --git a/source/sbase/cron.1 b/source/sbase/cron.1 new file mode 100644 index 00000000..4553b46a --- /dev/null +++ b/source/sbase/cron.1 @@ -0,0 +1,23 @@ +.Dd 2015-10-08 +.Dt CRON 1 +.Os sbase +.Sh NAME +.Nm cron +.Nd clock daemon +.Sh SYNOPSIS +.Nm +.Op Fl f Ar file +.Op Fl n +.Sh DESCRIPTION +.Nm +schedules commands to be run at specified dates and times. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl f Ar file +Use the specified +.Ar file +instead of the default +.Pa /etc/crontab . +.It Fl n +Do not daemonize. +.El diff --git a/source/sbase/cron.c b/source/sbase/cron.c new file mode 100644 index 00000000..77304ccf --- /dev/null +++ b/source/sbase/cron.c @@ -0,0 +1,566 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "queue.h" +#include "util.h" + +struct field { + enum { + ERROR, + WILDCARD, + NUMBER, + RANGE, + REPEAT, + LIST + } type; + long *val; + int len; +}; + +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; +}; + +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; + } + } + + switch ((pid = fork())) { + case -1: + logerr("error: failed to fork job: %s time: %s", + cmd, ctime(&t)); + return; + case 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); + default: + 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; + int j; + + for (i = 0; i < LEN(matchtbl); i++) { + switch (matchtbl[i].f->type) { + case WILDCARD: + continue; + case NUMBER: + if (matchtbl[i].f->val[0] == matchtbl[i].tm) + continue; + break; + case RANGE: + if (matchtbl[i].f->val[0] <= matchtbl[i].tm) + if (matchtbl[i].f->val[1] >= matchtbl[i].tm) + continue; + break; + case REPEAT: + if (matchtbl[i].tm > 0) { + if (matchtbl[i].tm % matchtbl[i].f->val[0] == 0) + continue; + } else { + if (matchtbl[i].len % matchtbl[i].f->val[0] == 0) + continue; + } + break; + case LIST: + for (j = 0; j < matchtbl[i].f->len; j++) + if (matchtbl[i].f->val[j] == matchtbl[i].tm) + break; + if (j < matchtbl[i].f->len) + continue; + break; + default: + break; + } + break; + } + if (i != LEN(matchtbl)) + return 0; + return 1; +} + +static int +parsefield(const char *field, long low, long high, struct field *f) +{ + int i; + char *e1, *e2; + const char *p; + + p = field; + while (isdigit(*p)) + p++; + + f->type = ERROR; + + switch (*p) { + case '*': + if (strcmp(field, "*") == 0) { + f->val = NULL; + f->len = 0; + f->type = WILDCARD; + } else if (strncmp(field, "*/", 2) == 0) { + f->val = emalloc(sizeof(*f->val)); + f->len = 1; + + errno = 0; + f->val[0] = strtol(field + 2, &e1, 10); + if (e1[0] != '\0' || errno != 0 || f->val[0] == 0) + break; + + f->type = REPEAT; + } + break; + case '\0': + f->val = emalloc(sizeof(*f->val)); + f->len = 1; + + errno = 0; + f->val[0] = strtol(field, &e1, 10); + if (e1[0] != '\0' || errno != 0) + break; + + f->type = NUMBER; + break; + case '-': + f->val = emalloc(2 * sizeof(*f->val)); + f->len = 2; + + errno = 0; + f->val[0] = strtol(field, &e1, 10); + if (e1[0] != '-' || errno != 0) + break; + + errno = 0; + f->val[1] = strtol(e1 + 1, &e2, 10); + if (e2[0] != '\0' || errno != 0) + break; + + f->type = RANGE; + break; + case ',': + for (i = 1; isdigit(*p) || *p == ','; p++) + if (*p == ',') + i++; + f->val = emalloc(i * sizeof(*f->val)); + f->len = i; + + errno = 0; + f->val[0] = strtol(field, &e1, 10); + if (f->val[0] < low || f->val[0] > high) + break; + + for (i = 1; *e1 == ',' && errno == 0; i++) { + errno = 0; + f->val[i] = strtol(e1 + 1, &e2, 10); + e1 = e2; + } + if (e1[0] != '\0' || errno != 0) + break; + + f->type = LIST; + break; + default: + return -1; + } + + for (i = 0; i < f->len; i++) + if (f->val[i] < low || f->val[i] > high) + f->type = ERROR; + + if (f->type == ERROR) { + free(f->val); + return -1; + } + + return 0; +} + +static void +freecte(struct ctabentry *cte, int nfields) +{ + switch (nfields) { + case 6: + free(cte->cmd); + case 5: + free(cte->wday.val); + case 4: + free(cte->mon.val); + case 3: + free(cte->mday.val); + case 2: + free(cte->hour.val); + case 1: + free(cte->min.val); + } + free(cte); +} + +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); + freecte(cte, 6); + } +} + +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; + struct fieldlimits { + char *name; + long min; + long max; + struct field *f; + } flim[] = { + { "min", 0, 59, NULL }, + { "hour", 0, 23, NULL }, + { "mday", 1, 31, NULL }, + { "mon", 1, 12, NULL }, + { "wday", 0, 6, NULL } + }; + size_t x; + + 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)); + flim[0].f = &cte->min; + flim[1].f = &cte->hour; + flim[2].f = &cte->mday; + flim[3].f = &cte->mon; + flim[4].f = &cte->wday; + + for (x = 0; x < LEN(flim); x++) { + do + col = strsep(&p, "\t\n "); + while (col && col[0] == '\0'); + + if (!col || parsefield(col, flim[x].min, flim[x].max, flim[x].f) < 0) { + logerr("error: failed to parse `%s' field on line %d\n", + flim[x].name, y + 1); + freecte(cte, x); + r = -1; + break; + } + } + + if (r == -1) + break; + + col = strsep(&p, "\n"); + if (col) + while (col[0] == '\t' || col[0] == ' ') + col++; + if (!col || col[0] == '\0') { + logerr("error: missing `cmd' field on line %d\n", + y + 1); + freecte(cte, 5); + 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/source/sbase/crypt.h b/source/sbase/crypt.h new file mode 100644 index 00000000..e0cc08d1 --- /dev/null +++ b/source/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(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/source/sbase/cut.1 b/source/sbase/cut.1 new file mode 100644 index 00000000..50825b7c --- /dev/null +++ b/source/sbase/cut.1 @@ -0,0 +1,71 @@ +.Dd 2015-10-08 +.Dt CUT 1 +.Os sbase +.Sh NAME +.Nm cut +.Nd extract columns of data +.Sh SYNOPSIS +.Nm +.Fl b Ar list +.Op Fl n +.Op Ar file ... +.Nm +.Fl c Ar list +.Op Ar file ... +.Nm +.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 d Ar delim +Use +.Ar delim +as field delimiter, which can be an arbitrary string. Default is '\et'. +.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 n +Do not split multibyte characters. A character is written when its +last byte is selected. +.It Fl s +Suppress lines not containing field delimiters. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The possibility of separating numbers and ranges with a space and specifying +multibyte delimiters of arbitrary length is an extension to that specification. diff --git a/source/sbase/cut.c b/source/sbase/cut.c new file mode 100644 index 00000000..a50bdcb5 --- /dev/null +++ b/source/sbase/cut.c @@ -0,0 +1,215 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "text.h" +#include "utf.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 size_t delimlen = 1; +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; + + if (!*str) + eprintf("empty list\n"); + for (s = str; *s; s++) { + if (*s == ' ') + *s = ','; + if (*s == ',') + n++; + } + r = ereallocarray(NULL, n, sizeof(*r)); + 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("bad list value\n"); + insert(r++); + } +} + +static size_t +seek(struct line *s, size_t pos, size_t *prev, size_t count) +{ + size_t n = pos - *prev, i, j; + + if (mode == 'b') { + if (n >= s->len) + return s->len; + if (nflag) + while (n && !UTF8_POINT(s->data[n])) + n--; + *prev += n; + return n; + } else if (mode == 'c') { + for (n++, i = 0; i < s->len; i++) + if (UTF8_POINT(s->data[i]) && !--n) + break; + } else { + for (i = (count < delimlen + 1) ? 0 : delimlen; n && i < s->len; ) { + if ((s->len - i) >= delimlen && + !memcmp(s->data + i, delim, delimlen)) { + if (!--n && count) + break; + i += delimlen; + continue; + } + for (j = 1; j + i <= s->len && !fullrune(s->data + i, j); j++); + i += j; + } + } + *prev = pos; + + return i; +} + +static void +cut(FILE *fp, const char *fname) +{ + Range *r; + struct line s; + static struct line line; + static size_t size; + size_t i, n, p; + ssize_t len; + + while ((len = getline(&line.data, &size, fp)) > 0) { + line.len = len; + if (line.data[line.len - 1] == '\n') + line.data[--line.len] = '\0'; + if (mode == 'f' && !memmem(line.data, line.len, delim, delimlen)) { + if (!sflag) { + fwrite(line.data, 1, line.len, stdout); + fputc('\n', stdout); + } + continue; + } + for (i = 0, p = 1, s = line, r = list; r; r = r->next) { + n = seek(&s, r->min, &p, i); + s.data += n; + s.len -= n; + i += (mode == 'f') ? delimlen : 1; + if (!s.len) + break; + if (!r->max) { + fwrite(s.data, 1, s.len, stdout); + break; + } + n = seek(&s, r->max + 1, &p, i); + i += (mode == 'f') ? delimlen : 1; + if (fwrite(s.data, 1, n, stdout) != n) + eprintf("fwrite :"); + s.data += n; + s.len -= n; + } + putchar('\n'); + } + if (ferror(fp)) + eprintf("getline %s:", fname); +} + +static void +usage(void) +{ + eprintf("usage: %s -b list [-n] [file ...]\n" + " %s -c list [file ...]\n" + " %s -f list [-d delim] [-s] [file ...]\n", + argv0, argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + int ret = 0; + + ARGBEGIN { + case 'b': + case 'c': + case 'f': + mode = ARGC(); + parselist(EARGF(usage())); + break; + case 'd': + delim = EARGF(usage()); + if (!*delim) + eprintf("empty delimiter\n"); + delimlen = unescape(delim); + break; + case 'n': + nflag = 1; + break; + case 's': + sflag = 1; + break; + default: + usage(); + } ARGEND + + if (!mode) + usage(); + + if (!argc) + cut(stdin, ""); + else { + for (; *argv; argc--, argv++) { + if (!strcmp(*argv, "-")) { + *argv = ""; + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + ret = 1; + continue; + } + cut(fp, *argv); + if (fp != stdin && fshut(fp, *argv)) + ret = 1; + } + } + + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/date.1 b/source/sbase/date.1 new file mode 100644 index 00000000..29081a58 --- /dev/null +++ b/source/sbase/date.1 @@ -0,0 +1,39 @@ +.Dd 2015-10-08 +.Dt DATE 1 +.Os sbase +.Sh NAME +.Nm date +.Nd print date and time +.Sh SYNOPSIS +.Nm +.Op Fl d Ar time +.Op Fl u +.Op Cm + Ns Ar format +.Sh DESCRIPTION +.Nm +prints the date and time according to +.Xr locale 7 +or +.Ar format +using +.Xr strftime 3 . +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl d Ar time +Print +.Ar time +given as the number of seconds since the +Unix epoch 1970-01-01T00:00:00Z. +.It Fl u +Print UTC time instead of local time. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The +.Op Fl d +flag is an extension to that specification. diff --git a/source/sbase/date.c b/source/sbase/date.c new file mode 100644 index 00000000..1671e1f4 --- /dev/null +++ b/source/sbase/date.c @@ -0,0 +1,49 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-u] [-d time] [+format]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct tm *now; + struct tm *(*tztime)(const time_t *) = localtime; + time_t t; + char buf[BUFSIZ], *fmt = "%c", *tz = "local"; + + t = time(NULL); + + ARGBEGIN { + case 'd': + t = estrtonum(EARGF(usage()), 0, LLONG_MAX); + break; + case 'u': + tztime = gmtime; + tz = "gm"; + break; + default: + usage(); + } ARGEND + + if (argc) { + if (argc != 1 || argv[0][0] != '+') + usage(); + else + fmt = &argv[0][1]; + } + if (!(now = tztime(&t))) + eprintf("%stime failed\n", tz); + + strftime(buf, sizeof(buf), fmt, now); + puts(buf); + + return fshut(stdout, ""); +} diff --git a/source/sbase/dirname.1 b/source/sbase/dirname.1 new file mode 100644 index 00000000..7904d3b7 --- /dev/null +++ b/source/sbase/dirname.1 @@ -0,0 +1,23 @@ +.Dd 2015-10-08 +.Dt DIRNAME 1 +.Os sbase +.Sh NAME +.Nm dirname +.Nd strip final path component +.Sh SYNOPSIS +.Nm +.Ar path +.Sh DESCRIPTION +.Nm +writes +.Ar path +with its final path component removed to stdout. +.Sh SEE ALSO +.Xr basename 1 , +.Xr dirname 3 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/dirname.c b/source/sbase/dirname.c new file mode 100644 index 00000000..4bef9a45 --- /dev/null +++ b/source/sbase/dirname.c @@ -0,0 +1,24 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s path\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + argv0 = argv[0], argc--, argv++; + + if (argc != 1) + usage(); + + puts(dirname(argv[0])); + + return fshut(stdout, ""); +} diff --git a/source/sbase/du.1 b/source/sbase/du.1 new file mode 100644 index 00000000..ec3d9b30 --- /dev/null +++ b/source/sbase/du.1 @@ -0,0 +1,59 @@ +.Dd 2015-10-08 +.Dt DU 1 +.Os sbase +.Sh NAME +.Nm du +.Nd display disk usage statistics +.Sh SYNOPSIS +.Nm +.Op Fl a | s +.Op Fl d Ar depth +.Op Fl h +.Op Fl k +.Op Fl H | L | P +.Op Fl x +.Op Ar file ... +.Sh DESCRIPTION +.Nm +displays the file system block usage for each +.Ar file +argument and for each directory in the file hierarchy rooted in directory +argument. If no +.Ar file +is specified, the block usage of the hierarchy rooted in the current directory +is displayed. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl a +Display an entry for each file in the file hierarchy. +.It Fl s +Display only the grand total for the specified files. +.It Fl d Ar depth +Maximum directory depth to print files and directories. +.It Fl h +Enable human-readable output. +.It Fl k +By default all sizes are reported in 512-byte block counts. +The +.Fl k +option causes the numbers to be reported in kilobyte counts. +.It Fl H +Only dereference symbolic links that are passed as command line arguments when +recursively traversing directories. +.It Fl L +Always dereference symbolic links while recursively traversing directories. +.It Fl P +Don't dereference symbolic links. This is the default. +.It Fl x +Do not traverse file systems mount points. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The +.Op Fl dhP +flags are an extension to that specification. diff --git a/source/sbase/du.c b/source/sbase/du.c new file mode 100644 index 00000000..3dc35458 --- /dev/null +++ b/source/sbase/du.c @@ -0,0 +1,115 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "fs.h" +#include "util.h" + +static size_t maxdepth = SIZE_MAX; +static size_t blksize = 512; + +static int aflag = 0; +static int sflag = 0; +static int hflag = 0; + +static void +printpath(off_t n, const char *path) +{ + if (hflag) + printf("%s\t%s\n", humansize(n * blksize), path); + else + printf("%jd\t%s\n", (intmax_t)n, path); +} + +static off_t +nblks(blkcnt_t blocks) +{ + return (512 * blocks + blksize - 1) / blksize; +} + +static void +du(const char *path, struct stat *st, void *total, struct recursor *r) +{ + off_t subtotal = 0; + + if (st && S_ISDIR(st->st_mode)) + recurse(path, &subtotal, r); + *((off_t *)total) += subtotal + nblks(st ? st->st_blocks : 0); + + if (!sflag && r->depth <= maxdepth && r->depth && st && (S_ISDIR(st->st_mode) || aflag)) + printpath(subtotal + nblks(st->st_blocks), path); +} + +static void +usage(void) +{ + eprintf("usage: %s [-a | -s] [-d depth] [-h] [-k] [-H | -L | -P] [-x] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct recursor r = { .fn = du, .hist = NULL, .depth = 0, .maxdepth = 0, + .follow = 'P', .flags = 0 }; + off_t n = 0; + int kflag = 0, dflag = 0; + char *bsize; + + ARGBEGIN { + case 'a': + aflag = 1; + break; + case 'd': + dflag = 1; + maxdepth = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, SIZE_MAX)); + break; + case 'h': + hflag = 1; + break; + case 'k': + kflag = 1; + break; + case 's': + sflag = 1; + break; + case 'x': + r.flags |= SAMEDEV; + break; + case 'H': + case 'L': + case 'P': + r.follow = ARGC(); + break; + default: + usage(); + } ARGEND + + if ((aflag && sflag) || (dflag && sflag)) + usage(); + + bsize = getenv("BLOCKSIZE"); + if (bsize) + blksize = estrtonum(bsize, 1, MIN(LLONG_MAX, SIZE_MAX)); + if (kflag) + blksize = 1024; + + if (!argc) { + recurse(".", &n, &r); + printpath(n, "."); + } else { + for (; *argv; argc--, argv++) { + n = 0; + recurse(*argv, &n, &r); + printpath(n, *argv); + } + } + + return fshut(stdout, "") || recurse_status; +} diff --git a/source/sbase/echo.1 b/source/sbase/echo.1 new file mode 100644 index 00000000..2d7a28a3 --- /dev/null +++ b/source/sbase/echo.1 @@ -0,0 +1,31 @@ +.Dd 2015-10-08 +.Dt ECHO 1 +.Os sbase +.Sh NAME +.Nm echo +.Nd print arguments +.Sh SYNOPSIS +.Nm +.Op Fl n +.Op Ar string ... +.Sh DESCRIPTION +.Nm +writes each +.Ar string +to stdout, separated by spaces and terminated by +a newline. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl n +Do not print the terminating newline. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The +.Op Fl n +flag is an extension to that specification. diff --git a/source/sbase/echo.c b/source/sbase/echo.c new file mode 100644 index 00000000..23bc55f2 --- /dev/null +++ b/source/sbase/echo.c @@ -0,0 +1,24 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include "util.h" + +int +main(int argc, char *argv[]) +{ + int nflag = 0; + + argv0 = argv[0], argc--, argv++; + + if (*argv && !strcmp(*argv, "-n")) { + nflag = 1; + argc--, argv++; + } + + for (; *argv; argc--, argv++) + putword(stdout, *argv); + if (!nflag) + putchar('\n'); + + return fshut(stdout, ""); +} diff --git a/source/sbase/ed.1 b/source/sbase/ed.1 new file mode 100644 index 00000000..93e3012f --- /dev/null +++ b/source/sbase/ed.1 @@ -0,0 +1,9 @@ +.Dd 2015-12-14 +.Dt ED 1 +.Os sbase +.Sh NAME +.Nm ed +.Nd text editor +.Sh SYNOPSIS +.Nm +is the standard text editor. diff --git a/source/sbase/ed.c b/source/sbase/ed.c new file mode 100644 index 00000000..ce19cf77 --- /dev/null +++ b/source/sbase/ed.c @@ -0,0 +1,1424 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +#define REGEXSIZE 100 +#define LINESIZE 80 +#define NUMLINES 32 +#define CACHESIZ 4096 + +struct hline { + off_t seek; + char global; + int next, prev; +}; + +struct undo { + int curln; + size_t nr, cap; + struct link { + int to1, from1; + int to2, from2; + } *vec; +}; + +static char *prompt = "*"; +static regex_t *pattern; +static regmatch_t matchs[10]; +static char *lastre; + +static int optverbose, optprompt, exstatus, optdiag = 1; +static int marks['z' - 'a']; +static int nlines, line1, line2; +static int curln, lastln, ocurln; +static jmp_buf savesp; +static char *lasterr; +static size_t idxsize, lastidx; +static struct hline *zero; +static char *text; +static char savfname[FILENAME_MAX]; +static char tmpname[FILENAME_MAX]; +static size_t sizetxt, memtxt; +static int scratch; +static int pflag, modflag, uflag, gflag; +static size_t csize; +static char *cmdline; +static char *ocmdline; +static size_t cmdsiz, cmdcap; +static int repidx; +static char *rhs; +static char *lastmatch; +static struct undo udata; +static int newcmd; +int eol, bol; + +static void +discard(void) +{ + int c; + + /* discard until end of line */ + if (repidx < 0 && + ((cmdsiz > 0 && cmdline[cmdsiz-1] != '\n') || cmdsiz == 0)) { + while ((c = getchar()) != '\n' && c != EOF) + /* nothing */; + } +} + +static void undo(void); + +static void +error(char *msg) +{ + exstatus = 1; + lasterr = msg; + fputs("?\n", stderr); + + if (optverbose) + fprintf(stderr, "%s\n", msg); + if (!newcmd) + undo(); + + discard(); + curln = ocurln; + longjmp(savesp, 1); +} + +static int +nextln(int line) +{ + ++line; + return (line > lastln) ? 0 : line; +} + +static int +prevln(int line) +{ + --line; + return (line < 0) ? lastln : line; +} + +static char * +addchar(char c, char *t, size_t *capacity, size_t *size) +{ + size_t cap = *capacity, siz = *size; + + if (siz >= cap && + (cap > SIZE_MAX - LINESIZE || + (t = realloc(t, cap += LINESIZE)) == NULL)) + error("out of memory"); + t[siz++] = c; + *size = siz; + *capacity = cap; + return t; +} + +static int +input(void) +{ + int c; + + if (repidx >= 0) + return ocmdline[repidx++]; + + if ((c = getchar()) != EOF) + cmdline = addchar(c, cmdline, &cmdcap, &cmdsiz); + return c; +} + +static int +back(int c) +{ + if (repidx > 0) { + --repidx; + } else { + ungetc(c, stdin); + if (c != EOF) + --cmdsiz; + } + return c; +} + +static int +makeline(char *s, int *off) +{ + struct hline *lp; + size_t len; + char c, *begin = s; + + if (lastidx >= idxsize) { + if (idxsize > SIZE_MAX - NUMLINES || + !(lp = realloc(zero, (idxsize + NUMLINES) * sizeof(*lp)))) + error("out of memory"); + idxsize += NUMLINES; + zero = lp; + } + lp = zero + lastidx; + + if (!s) { + lp->seek = -1; + len = 0; + } else { + while ((c = *s++) != '\n') + /* nothing */; + len = s - begin; + if ((lp->seek = lseek(scratch, 0, SEEK_END)) < 0 || + write(scratch, begin, len) < 0) { + error("input/output error"); + } + } + if (off) + *off = len; + ++lastidx; + return lp - zero; +} + +static int +getindex(int line) +{ + struct hline *lp; + int n; + + for (n = 0, lp = zero; n != line; ++n) + lp = zero + lp->next; + + return lp - zero; +} + +static char * +gettxt(int line) +{ + static char buf[CACHESIZ]; + static off_t lasto; + struct hline *lp; + off_t off, block; + ssize_t n; + char *p; + + lp = zero + getindex(line); + sizetxt = 0; + off = lp->seek; + + if (off == (off_t) -1) + return text = addchar('\0', text, &memtxt, &sizetxt); + +repeat: + if (!csize || off < lasto || off - lasto >= csize) { + block = off & ~(CACHESIZ-1); + if (lseek(scratch, block, SEEK_SET) < 0 || + (n = read(scratch, buf, CACHESIZ)) < 0) { + error("input/output error"); + } + csize = n; + lasto = block; + } + for (p = buf + off - lasto; p < buf + csize && *p != '\n'; ++p) { + ++off; + text = addchar(*p, text, &memtxt, &sizetxt); + } + if (csize && p == buf + csize) + goto repeat; + + text = addchar('\n', text, &memtxt, &sizetxt); + text = addchar('\0', text, &memtxt, &sizetxt); + return text; +} + +static void +setglobal(int i, int v) +{ + zero[getindex(i)].global = v; +} + +static void +clearundo(void) +{ + free(udata.vec); + udata.vec = NULL; + newcmd = udata.nr = udata.cap = 0; + modflag = 0; +} + +static void +relink(int to1, int from1, int from2, int to2) +{ + struct link *p; + + if (newcmd) { + clearundo(); + udata.curln = ocurln; + } + if (udata.nr >= udata.cap) { + size_t siz = (udata.cap + 10) * sizeof(struct link); + if ((p = realloc(udata.vec, siz)) == NULL) + error("out of memory"); + udata.vec = p; + udata.cap = udata.cap + 10; + } + p = &udata.vec[udata.nr++]; + p->from1 = from1; + p->to1 = zero[from1].next; + p->from2 = from2; + p->to2 = zero[from2].prev; + + zero[from1].next = to1; + zero[from2].prev = to2; + modflag = 1; +} + +static void +undo(void) +{ + struct link *p; + + if (udata.nr == 0) + return; + for (p = &udata.vec[udata.nr-1]; udata.nr--; --p) { + zero[p->from1].next = p->to1; + zero[p->from2].prev = p->to2; + } + free(udata.vec); + udata.vec = NULL; + udata.cap = 0; + curln = udata.curln; +} + +static void +inject(char *s) +{ + int off, k, begin, end; + + begin = getindex(curln); + end = getindex(nextln(curln)); + + while (*s) { + k = makeline(s, &off); + s += off; + relink(k, begin, k, begin); + relink(end, k, end, k); + ++lastln; + ++curln; + begin = k; + } +} + +static void +clearbuf() +{ + if (scratch) + close(scratch); + remove(tmpname); + free(zero); + zero = NULL; + scratch = csize = idxsize = lastidx = curln = lastln = 0; + modflag = lastln = curln = 0; +} + +static void +setscratch() +{ + int r, k; + char *dir; + + clearbuf(); + clearundo(); + if ((dir = getenv("TMPDIR")) == NULL) + dir = "/tmp"; + r = snprintf(tmpname, sizeof(tmpname), "%s/%s", + dir, "ed.XXXXXX"); + if (r < 0 || (size_t)r >= sizeof(tmpname)) + error("scratch filename too long"); + if ((scratch = mkstemp(tmpname)) < 0) + error("failed to create scratch file"); + if ((k = makeline(NULL, NULL))) + error("input/output error in scratch file"); + relink(k, k, k, k); + clearundo(); +} + +static void +compile(int delim) +{ + int n, ret, c,bracket; + static size_t siz, cap; + static char buf[BUFSIZ]; + + if (!isgraph(delim)) + error("invalid pattern delimiter"); + + eol = bol = bracket = siz = 0; + for (n = 0;; ++n) { + if ((c = input()) == delim && !bracket) + break; + if (c == '^') { + bol = 1; + } else if (c == '$') { + eol = 1; + } else if (c == '\n' || c == EOF) { + back(c); + break; + } + + if (c == '\\') { + lastre = addchar(c, lastre, &cap, &siz); + c = input(); + } else if (c == '[') { + bracket = 1; + } else if (c == ']') { + bracket = 0; + } + lastre = addchar(c, lastre, &cap, &siz); + } + if (n == 0) { + if (!pattern) + error("no previous pattern"); + return; + } + lastre = addchar('\0', lastre, &cap, &siz); + + if (pattern) + regfree(pattern); + if (!pattern && (!(pattern = malloc(sizeof(*pattern))))) + error("out of memory"); + if ((ret = regcomp(pattern, lastre, REG_NEWLINE))) { + regerror(ret, pattern, buf, sizeof(buf)); + error(buf); + } +} + +static int +match(int num) +{ + lastmatch = gettxt(num); + return !regexec(pattern, lastmatch, 10, matchs, 0); +} + +static int +rematch(int num) +{ + regoff_t off = matchs[0].rm_eo; + + if (!regexec(pattern, lastmatch + off, 10, matchs, 0)) { + lastmatch += off; + return 1; + } + return 0; +} + +static int +search(int way) +{ + int i; + + i = curln; + do { + i = (way == '?') ? prevln(i) : nextln(i); + if (match(i)) + return i; + } while (i != curln); + + error("invalid address"); + return -1; /* not reached */ +} + +static void +skipblank(void) +{ + char c; + + while ((c = input()) == ' ' || c == '\t') + /* nothing */; + back(c); +} + +static int +getnum(void) +{ + int ln, n, c; + + for (ln = 0; isdigit(c = input()); ln += n) { + if (ln > INT_MAX/10) + goto invalid; + n = c - '0'; + ln *= 10; + if (INT_MAX - ln < n) + goto invalid; + } + back(c); + return ln; + +invalid: + error("invalid address"); + return -1; /* not reached */ +} + +static int +linenum(int *line) +{ + int ln, c; + + skipblank(); + + switch (c = input()) { + case '.': + ln = curln; + break; + case '\'': + skipblank(); + if (!isalpha(c = input())) + error("invalid mark character"); + if (!(ln = marks[c])) + error("invalid address"); + break; + case '$': + ln = lastln; + break; + case '?': + case '/': + compile(c); + ln = search(c); + break; + case '^': + case '-': + case '+': + ln = curln; + back(c); + break; + default: + back(c); + if (isdigit(c)) + ln = getnum(); + else + return 0; + break; + } + *line = ln; + return 1; +} + +static int +address(int *line) +{ + int ln, sign, c, num; + + if (!linenum(&ln)) + return 0; + + for (;;) { + skipblank(); + if ((c = input()) != '+' && c != '-' && c != '^') + break; + sign = c == '+' ? 1 : -1; + num = isdigit(back(input())) ? getnum() : 1; + num *= sign; + if (INT_MAX - ln < num) + goto invalid; + ln += num; + } + back(c); + + if (ln < 0 || ln > lastln) + error("invalid address"); + *line = ln; + return 1; + +invalid: + error("invalid address"); + return -1; /* not reached */ +} + +static void +getlst() +{ + int ln, c; + + if ((c = input()) == ',') { + line1 = 1; + line2 = lastln; + nlines = lastln; + return; + } else if (c == ';') { + line1 = curln; + line2 = lastln; + nlines = lastln - curln + 1; + return; + } + back(c); + line2 = curln; + for (nlines = 0; address(&ln); ) { + line1 = line2; + line2 = ln; + ++nlines; + + skipblank(); + if ((c = input()) != ',' && c != ';') { + back(c); + break; + } + if (c == ';') + curln = line2; + } + if (nlines > 2) + nlines = 2; + else if (nlines <= 1) + line1 = line2; +} + +static void +deflines(int def1, int def2) +{ + if (!nlines) { + line1 = def1; + line2 = def2; + } + if (line1 > line2 || line1 < 0 || line2 > lastln) + error("invalid address"); +} + +static void +dowrite(char *fname, int trunc) +{ + FILE *fp; + int i, line; + + if (!(fp = fopen(fname, (trunc) ? "w" : "a"))) + error("input/output error"); + + line = curln; + for (i = line1; i <= line2; ++i) + fputs(gettxt(i), fp); + + curln = line2; + if (fclose(fp)) + error("input/output error"); + strcpy(savfname, fname); + modflag = 0; + curln = line; +} + +static void +doread(char *fname) +{ + size_t cnt; + ssize_t n; + char *p; + FILE *aux; + static size_t len; + static char *s; + static FILE *fp; + + if (fp) + fclose(fp); + if ((fp = fopen(fname, "r")) == NULL) + error("cannot open input file"); + + curln = line2; + for (cnt = 0; (n = getline(&s, &len, fp)) > 0; cnt += (size_t)n) { + if (s[n-1] != '\n') { + if (len == SIZE_MAX || !(p = realloc(s, ++len))) + error("out of memory"); + s = p; + s[n-1] = '\n'; + s[n] = '\0'; + } + inject(s); + } + if (optdiag) + printf("%zu\n", cnt); + + aux = fp; + fp = NULL; + if (fclose(aux)) + error("input/output error"); +} + +static void +doprint(void) +{ + int i, c; + char *s, *str; + + if (line1 <= 0 || line2 > lastln) + error("incorrect address"); + for (i = line1; i <= line2; ++i) { + if (pflag == 'n') + printf("%d\t", i); + for (s = gettxt(i); (c = *s) != '\n'; ++s) { + if (pflag != 'l') + goto print_char; + switch (c) { + case '$': + str = "\\$"; + goto print_str; + case '\t': + str = "\\t"; + goto print_str; + case '\b': + str = "\\b"; + goto print_str; + case '\\': + str = "\\\\"; + goto print_str; + default: + if (!isprint(c)) { + printf("\\x%x", 0xFF & c); + break; + } + print_char: + putchar(c); + break; + print_str: + fputs(str, stdout); + break; + } + } + if (pflag == 'l') + fputs("$", stdout); + putc('\n', stdout); + } + curln = i - 1; +} + +static void +dohelp(void) +{ + if (lasterr) + fprintf(stderr, "%s\n", lasterr); +} + +static void +chkprint(int flag) +{ + char c; + + if (flag) { + if ((c = input()) == 'p' || c == 'l' || c == 'n') + pflag = c; + else + back(c); + } + if (input() != '\n') + error("invalid command suffix"); +} + +static char * +getfname(char comm) +{ + int c; + char *bp; + static char fname[FILENAME_MAX]; + + skipblank(); + for (bp = fname; bp < &fname[FILENAME_MAX]; *bp++ = c) { + if ((c = input()) == EOF || c == '\n') + break; + } + if (bp == fname) { + if (savfname[0] == '\0') + error("no current filename"); + return savfname; + } else if (bp == &fname[FILENAME_MAX]) { + error("file name too long"); + } else { + *bp = '\0'; + if (savfname[0] == '\0' || comm == 'e' || comm == 'f') + strcpy(savfname, fname); + return fname; + } + return NULL; /* not reached */ +} + +static void +append(int num) +{ + char *s = NULL; + size_t len = 0; + + curln = num; + while (getline(&s, &len, stdin) > 0) { + if (*s == '.' && s[1] == '\n') + break; + inject(s); + } + free(s); +} + +static void +delete(int from, int to) +{ + int lto, lfrom; + + if (!from) + error("incorrect address"); + + lfrom = getindex(prevln(from)); + lto = getindex(nextln(to)); + lastln -= to - from + 1; + curln = (from > lastln) ? lastln : from;; + relink(lto, lfrom, lto, lfrom); +} + +static void +move(int where) +{ + int before, after, lto, lfrom; + + if (!line1 || (where >= line1 && where <= line2)) + error("incorrect address"); + + before = getindex(prevln(line1)); + after = getindex(nextln(line2)); + lfrom = getindex(line1); + lto = getindex(line2); + relink(after, before, after, before); + + if (where < line1) { + curln = where + line1 - line2 + 1; + } else { + curln = where; + where -= line1 - line2 + 1; + } + before = getindex(where); + after = getindex(nextln(where)); + relink(lfrom, before, lfrom, before); + relink(after, lto, after, lto); +} + +static void +join(void) +{ + int i; + char *t, c; + size_t len = 0, cap = 0; + static char *s; + + free(s); + for (s = NULL, i = line1; i <= line2; i = nextln(i)) { + for (t = gettxt(i); (c = *t) != '\n'; ++t) + s = addchar(*t, s, &cap, &len); + } + + s = addchar('\n', s, &cap, &len); + s = addchar('\0', s, &cap, &len); + delete(line1, line2); + inject(s); + free(s); +} + +static void +scroll(int num) +{ + int i; + + if (!line1 || line1 == lastln) + error("incorrect address"); + + for (i = line1; i <= line1 + num && i <= lastln; ++i) + fputs(gettxt(i), stdout); + curln = i; +} + +static void +copy(int where) +{ + int i; + + if (!line1 || (where >= line1 && where <= line2)) + error("incorrect address"); + curln = where; + + for (i = line1; i <= line2; ++i) + inject(gettxt(i)); +} + +static void +quit(void) +{ + clearbuf(); + exit(exstatus); +} + +static void +execsh(void) +{ + static char *cmd; + static size_t siz, cap; + char c, *p; + int repl = 0; + + skipblank(); + if ((c = input()) != '!') { + back(c); + siz = 0; + } else if (cmd) { + --siz; + repl = 1; + } else { + error("no previous command"); + } + + while ((c = input()) != EOF && c != '\n') { + if (c == '%' && (siz == 0 || cmd[siz - 1] != '\\')) { + if (savfname[0] == '\0') + error("no current filename"); + repl = 1; + for (p = savfname; *p; ++p) + cmd = addchar(*p, cmd, &cap, &siz); + } else { + cmd = addchar(c, cmd, &cap, &siz); + } + } + cmd = addchar('\0', cmd, &cap, &siz); + + if (repl) + puts(cmd); + system(cmd); + if (optdiag) + puts("!"); +} + +static void +getrhs(int delim) +{ + int c; + size_t siz, cap; + static char *s; + + free(s); + s = NULL; + siz = cap = 0; + while ((c = input()) != '\n' && c != EOF && c != delim) { + if (c == '\\') { + if ((c = input()) == '&' || isdigit(c)) + s = addchar(c, s, &siz, &cap); + } + s = addchar(c, s, &siz, &cap); + } + s = addchar('\0', s, &siz, &cap); + if (c == EOF) + error("invalid pattern delimiter"); + if (c == '\n') { + pflag = 'p'; + back(c); + } + + if (!strcmp("%", s)) { + free(s); + if (!rhs) + error("no previous substitution"); + } else { + free(rhs); + rhs = s; + } + s = NULL; +} + +static int +getnth(void) +{ + int c; + + if ((c = input()) == 'g') { + return -1; + } else if (isdigit(c)) { + if (c == '0') + return -1; + return c - '0'; + } else { + back(c); + return 1; + } +} + +static void +addpre(char **s, size_t *cap, size_t *siz) +{ + char *p; + + for (p = lastmatch; p < lastmatch + matchs[0].rm_so; ++p) + *s = addchar(*p, *s, cap, siz); +} + +static void +addpost(char **s, size_t *cap, size_t *siz) +{ + char c, *p; + + for (p = lastmatch + matchs[0].rm_eo; (c = *p); ++p) + *s = addchar(c, *s, cap, siz); + *s = addchar('\0', *s, cap, siz); +} + +static int +addsub(char **s, size_t *cap, size_t *siz, int nth, int nmatch) +{ + char *end, *q, *p, c; + int sub; + + if (nth != nmatch && nth != -1) { + q = lastmatch + matchs[0].rm_so; + end = lastmatch + matchs[0].rm_eo; + while (q < end) + *s = addchar(*q++, *s, cap, siz); + return 0; + } + + for (p = rhs; (c = *p); ++p) { + switch (c) { + case '&': + sub = 0; + goto copy_match; + case '\\': + if ((c = *++p) == '\0') + return 1; + if (!isdigit(c)) + goto copy_char; + sub = c - '0'; + copy_match: + q = lastmatch + matchs[sub].rm_so; + end = lastmatch + matchs[sub].rm_eo; + while (q < end) + *s = addchar(*q++, *s, cap, siz); + break; + default: + copy_char: + *s = addchar(c, *s, cap, siz); + break; + } + } + return 1; +} + +static void +subline(int num, int nth) +{ + int i, m, changed; + static char *s; + static size_t siz, cap; + + i = changed = siz = 0; + for (m = match(num); m; m = rematch(num)) { + addpre(&s, &cap, &siz); + changed |= addsub(&s, &cap, &siz, nth, ++i); + if (eol || bol) + break; + } + if (!changed) + return; + addpost(&s, &cap, &siz); + delete(num, num); + curln = prevln(num); + inject(s); +} + +static void +subst(int nth) +{ + int i; + + for (i = line1; i <= line2; ++i) + subline(i, nth); +} + +static void +docmd(void) +{ + char cmd; + int rep = 0, c, line3, num, trunc; + +repeat: + skipblank(); + cmd = input(); + trunc = pflag = 0; + switch (cmd) { + case '&': + skipblank(); + chkprint(0); + if (!ocmdline) + error("no previous command"); + rep = 1; + repidx = 0; + getlst(); + goto repeat; + case '!': + execsh(); + break; + case EOF: + if (cmdsiz == 0) + quit(); + case '\n': + if (gflag && uflag) + return; + num = gflag ? curln : curln+1; + deflines(num, num); + pflag = 'p'; + goto print; + case 'l': + case 'n': + case 'p': + back(cmd); + chkprint(1); + deflines(curln, curln); + goto print; + case 'g': + case 'G': + case 'v': + case 'V': + error("cannot nest global commands"); + case 'H': + if (nlines > 0) + goto unexpected; + chkprint(0); + optverbose ^= 1; + break; + case 'h': + if (nlines > 0) + goto unexpected; + chkprint(0); + dohelp(); + break; + case 'w': + trunc = 1; + case 'W': + deflines(nextln(0), lastln); + dowrite(getfname(cmd), trunc); + break; + case 'r': + if (nlines > 1) + goto bad_address; + deflines(lastln, lastln); + doread(getfname(cmd)); + break; + case 'd': + chkprint(1); + deflines(curln, curln); + delete(line1, line2); + break; + case '=': + if (nlines > 1) + goto bad_address; + chkprint(1); + deflines(lastln, lastln); + printf("%d\n", line1); + break; + case 'u': + if (nlines > 0) + goto bad_address; + chkprint(1); + if (udata.nr == 0) + error("nothing to undo"); + undo(); + break; + case 's': + deflines(curln, curln); + c = input(); + compile(c); + getrhs(c); + num = getnth(); + chkprint(1); + subst(num); + break; + case 'i': + if (nlines > 1) + goto bad_address; + chkprint(1); + deflines(curln, curln); + if (!line1) + goto bad_address; + append(prevln(line1)); + break; + case 'a': + if (nlines > 1) + goto bad_address; + chkprint(1); + deflines(curln, curln); + append(line1); + break; + case 'm': + deflines(curln, curln); + if (!address(&line3)) + line3 = curln; + chkprint(1); + move(line3); + break; + case 't': + deflines(curln, curln); + if (!address(&line3)) + line3 = curln; + chkprint(1); + copy(line3); + break; + case 'c': + chkprint(1); + deflines(curln, curln); + delete(line1, line2); + append(prevln(line1)); + break; + case 'j': + chkprint(1); + deflines(curln, curln+1); + if (!line1) + goto bad_address; + join(); + break; + case 'z': + if (nlines > 1) + goto bad_address; + if (isdigit(back(input()))) + num = getnum(); + else + num = 24; + chkprint(1); + scroll(num); + break; + case 'k': + if (nlines > 1) + goto bad_address; + if (!islower(c = input())) + error("invalid mark character"); + chkprint(1); + deflines(curln, curln); + marks[c] = line1; + break; + case 'P': + if (nlines > 0) + goto unexpected; + chkprint(1); + optprompt ^= 1; + break; + case 'Q': + modflag = 0; + case 'q': + if (nlines > 0) + goto unexpected; + if (modflag) + goto modified; + quit(); + break; + case 'f': + if (nlines > 0) + goto unexpected; + if (back(input()) != '\n') + getfname(cmd); + else + puts(savfname); + chkprint(0); + break; + case 'E': + modflag = 0; + case 'e': + if (nlines > 0) + goto unexpected; + if (modflag) + goto modified; + getfname(cmd); + setscratch(); + deflines(curln, curln); + doread(savfname); + clearundo(); + break; + default: + error("unknown command"); + bad_address: + error("invalid address"); + modified: + modflag = 0; + error("warning: file modified"); + unexpected: + error("unexpected address"); + } + + if (!pflag) + goto save_last_cmd; + + line1 = line2 = curln; +print: + doprint(); + +save_last_cmd: + if (!uflag) + repidx = 0; + if (rep) + return; + free(ocmdline); + cmdline = addchar('\0', cmdline, &cmdcap, &cmdsiz); + if ((ocmdline = strdup(cmdline)) == NULL) + error("out of memory"); +} + +static int +chkglobal(void) +{ + int delim, c, dir, i, v; + + uflag = 1; + gflag = 0; + skipblank(); + + switch (c = input()) { + case 'g': + uflag = 0; + case 'G': + dir = 1; + break; + case 'v': + uflag = 0; + case 'V': + dir = 0; + break; + default: + back(c); + return 0; + } + gflag = 1; + deflines(nextln(0), lastln); + delim = input(); + compile(delim); + + for (i = 1; i <= lastln; ++i) { + if (i >= line1 && i <= line2) + v = match(i) == dir; + else + v = 0; + setglobal(i, v); + } + + return 1; +} + +static void +doglobal(void) +{ + int i, k; + + skipblank(); + cmdsiz = 0; + gflag = 1; + if (uflag) + chkprint(0); + + for (i = 1; i <= lastln; i++) { + k = getindex(i); + if (!zero[k].global) + continue; + curln = i; + nlines = 0; + if (uflag) { + line1 = line2 = i; + pflag = 0; + doprint(); + } + docmd(); + } + discard(); /* cover the case of not matching anything */ +} + +static void +usage(void) +{ + eprintf("usage: %s [-s] [-p] [file]\n", argv0); +} + +static void +sigintr(int n) +{ + signal(SIGINT, sigintr); + error("interrupt"); +} + +static void +sighup(int dummy) +{ + int n; + char *home = getenv("HOME"), fname[FILENAME_MAX]; + + if (modflag) { + line1 = nextln(0); + line2 = lastln; + if (!setjmp(savesp)) { + dowrite("ed.hup", 1); + } else if (home && !setjmp(savesp)) { + n = snprintf(fname, + sizeof(fname), "%s/%s", home, "ed.hup"); + if (n < sizeof(fname) && n > 0) + dowrite(fname, 1); + } + } + exstatus = 1; + quit(); +} + +static void +edit(void) +{ + setjmp(savesp); + for (;;) { + newcmd = 1; + ocurln = curln; + cmdsiz = 0; + repidx = -1; + if (optprompt) + fputs(prompt, stdout); + getlst(); + chkglobal() ? doglobal() : docmd(); + } +} + +static void +init(char *fname) +{ + size_t len; + + if (setjmp(savesp)) + return; + setscratch(); + if (!fname) + return; + if ((len = strlen(fname)) >= FILENAME_MAX || len == 0) + error("incorrect filename"); + memcpy(savfname, fname, len); + doread(fname); + clearundo(); +} + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + case 'p': + prompt = EARGF(usage()); + optprompt = 1; + break; + case 's': + optdiag = 0; + break; + default: + usage(); + } ARGEND + + if (argc > 1) + usage(); + + signal(SIGINT, sigintr); + signal(SIGHUP, sighup); + signal(SIGQUIT, SIG_IGN); + + init(*argv); + edit(); + + /* not reached */ + return 0; +} diff --git a/source/sbase/env.1 b/source/sbase/env.1 new file mode 100644 index 00000000..57fa5187 --- /dev/null +++ b/source/sbase/env.1 @@ -0,0 +1,51 @@ +.Dd 2015-10-08 +.Dt ENV 1 +.Os sbase +.Sh NAME +.Nm env +.Nd modify the environment, then print it or run a command +.Sh SYNOPSIS +.Nm +.Op Fl i +.Oo Fl u Ar var Oc ... +.Oo Ar var Ns = Ns Ar value Oc ... +.Oo Ar cmd Oo arg ... Oc Oc +.Sh DESCRIPTION +.Nm +unsets each +.Ar var , +then adds or sets each +.Ar ( var , value ) +tuple in the environment. +.Pp +If +.Ar cmd +is given, it is executed in this new environment; +otherwise, the modified environment is printed to stdout. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl i +Completely ignore the existing environment and execute +.Ar cmd +only with each +.Ar ( var , value ) +tuple specified. +.It Fl u Ar var +Unset +.Ar var +in the environment. +.El +.Sh SEE ALSO +.Xr printenv 1 , +.Xr putenv 3 , +.Xr environ 7 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The +.Op Fl u +flag is an extension to that specification. diff --git a/source/sbase/env.c b/source/sbase/env.c new file mode 100644 index 00000000..5d7e8a55 --- /dev/null +++ b/source/sbase/env.c @@ -0,0 +1,49 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "util.h" + +extern char **environ; + +static void +usage(void) +{ + eprintf("usage: %s [-i] [-u var] ... [var=value] ... [cmd [arg ...]]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int savederrno; + + ARGBEGIN { + case 'i': + *environ = NULL; + break; + case 'u': + if (unsetenv(EARGF(usage())) < 0) + eprintf("unsetenv:"); + break; + default: + usage(); + } ARGEND + + for (; *argv && strchr(*argv, '='); argc--, argv++) + putenv(*argv); + + if (*argv) { + execvp(*argv, argv); + savederrno = errno; + weprintf("execvp %s:", *argv); + _exit(126 + (savederrno == ENOENT)); + } + + for (; *environ; environ++) + puts(*environ); + + return fshut(stdout, ""); +} diff --git a/source/sbase/expand.1 b/source/sbase/expand.1 new file mode 100644 index 00000000..6dbf421f --- /dev/null +++ b/source/sbase/expand.1 @@ -0,0 +1,51 @@ +.Dd 2015-10-08 +.Dt EXPAND 1 +.Os sbase +.Sh NAME +.Nm expand +.Nd expand tabs to spaces +.Sh SYNOPSIS +.Nm +.Op Fl i +.Op Fl t Ar tablist +.Op Ar file ... +.Sh DESCRIPTION +.Nm +converts tabs to spaces in each +.Ar file +as specified in +.Ar tablist . +If no file is given, +.Nm +reads from stdin. +.Pp +Backspace characters are preserved and decrement the column count +for tab calculations. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl i +Only expand tabs at the beginning of lines, i.e. expand each +line until a character different from '\et' and ' ' is reached. +.It Fl t Ar tablist +Specify tab size or tabstops. +.Ar tablist +is a list of one (in the former case) or multiple (in the latter case) +strictly positive integers separated by ' ' or ','. +.Pp +The default +.Ar tablist +is "8". +.El +.Sh SEE ALSO +.Xr fold 1 , +.Xr unexpand 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The +.Op Fl i +flag is an extension to that specification. diff --git a/source/sbase/expand.c b/source/sbase/expand.c new file mode 100644 index 00000000..f534134f --- /dev/null +++ b/source/sbase/expand.c @@ -0,0 +1,131 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "utf.h" +#include "util.h" + +static int iflag = 0; +static size_t *tablist = NULL; +static size_t tablistlen = 0; + +static size_t +parselist(const char *s) +{ + size_t i; + char *p, *tmp; + + tmp = estrdup(s); + for (i = 0; (p = strsep(&tmp, " ,")); i++) { + if (*p == '\0') + eprintf("empty field in tablist\n"); + tablist = ereallocarray(tablist, i + 1, sizeof(*tablist)); + tablist[i] = estrtonum(p, 1, MIN(LLONG_MAX, SIZE_MAX)); + if (i > 0 && tablist[i - 1] >= tablist[i]) + eprintf("tablist must be ascending\n"); + } + tablist = ereallocarray(tablist, i + 1, sizeof(*tablist)); + /* tab length = 1 for the overflowing case later in the matcher */ + tablist[i] = 1; + + return i; +} + +static int +expand(const char *file, FILE *fp) +{ + size_t bol = 1, col = 0, i; + Rune r; + + while (efgetrune(&r, fp, file)) { + switch (r) { + case '\t': + if (tablistlen == 1) + i = 0; + else for (i = 0; i < tablistlen; i++) + if (col < tablist[i]) + break; + if (bol || !iflag) { + do { + col++; + putchar(' '); + } while (col % tablist[i]); + } else { + putchar('\t'); + col = tablist[i]; + } + break; + case '\b': + bol = 0; + if (col) + col--; + putchar('\b'); + break; + case '\n': + bol = 1; + col = 0; + putchar('\n'); + break; + default: + col++; + if (r != ' ') + bol = 0; + efputrune(&r, stdout, ""); + break; + } + } + + return 0; +} + +static void +usage(void) +{ + eprintf("usage: %s [-i] [-t tablist] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + int ret = 0; + char *tl = "8"; + + ARGBEGIN { + case 'i': + iflag = 1; + break; + case 't': + tl = EARGF(usage()); + if (!*tl) + eprintf("tablist cannot be empty\n"); + break; + default: + usage(); + } ARGEND + + tablistlen = parselist(tl); + + if (!argc) { + expand("", stdin); + } else { + for (; *argv; argc--, argv++) { + if (!strcmp(*argv, "-")) { + *argv = ""; + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + ret = 1; + continue; + } + expand(*argv, fp); + if (fp != stdin && fshut(fp, *argv)) + ret = 1; + } + } + + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/expr.1 b/source/sbase/expr.1 new file mode 100644 index 00000000..104b6946 --- /dev/null +++ b/source/sbase/expr.1 @@ -0,0 +1,103 @@ +.Dd 2015-10-08 +.Dt EXPR 1 +.Os sbase +.Sh NAME +.Nm expr +.Nd evaluate expression +.Sh SYNOPSIS +.Nm +.Ar expression +.Sh DESCRIPTION +.Nm +evaluates +.Ar expression +and writes the result to stdout. +.Pp +There are two elemental expressions, +.Sy integer +and +.Sy string. +Let +.Sy expr +be a non-elemental expression and +.Sy expr1 , +.Sy expr2 +arbitrary expressions. Then +.Sy expr +has the recursive form +.Sy expr = [(] expr1 operand expr2 [)]. +.Pp +With +.Sy operand +being in order of increasing precedence: +.Bl -tag -width Ds +.It | +Evaluate to +.Sy expr1 +if it is neither an empty string nor 0; otherwise evaluate to +.Sy expr2 . +.It & +Evaluate to +.Sy expr1 +if +.Sy expr1 +and +.Sy expr2 +are neither empty strings nor 0; otherwise evaluate to 0. +.It = > >= < <= != +If +.Sy expr1 +and +.Sy expr2 +are integers, evaluate to 1 if the relation is true and 0 if it is false. +If +.Sy expr1 +and +.Sy expr2 +are strings, apply the relation to the return value of +.Xr strcmp 3 . +.It + - +If +.Sy expr1 +and +.Sy expr2 +are integers, evaluate to their sum or subtraction. +.It * / % +If +.Sy expr1 +and +.Sy expr2 +are integers, evaluate to their multiplication, division or remainder. +.It : +Evaluate to the number of characters matched in +.Sy expr1 +against +.Sy expr2 . expr2 +is anchored with an implicit '^'. +.Pp +You can't directly match the empty string, since zero matched characters +resolve equally to a failed match. To work around this limitation, use +"expr X'' : 'X$' instead of "expr '' : '$'" +.El +.Sh EXIT STATUS +.Bl -tag -width Ds +.It 0 +.Ar expression +is neither an empty string nor 0. +.It 1 +.Ar expression +is an empty string or 0. +.It 2 +.Ar expression +is invalid. +.It > 2 +An error occurred. +.El +.Sh SEE ALSO +.Xr test 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/expr.c b/source/sbase/expr.c new file mode 100644 index 00000000..4570b33d --- /dev/null +++ b/source/sbase/expr.c @@ -0,0 +1,268 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "utf.h" +#include "util.h" + +/* tokens, one-character operators represent themselves */ +enum { + VAL = CHAR_MAX + 1, GE, LE, NE +}; + +struct val { + char *str; + long long num; +}; + +static void +enan(struct val *v) +{ + if (!v->str) + return; + enprintf(2, "syntax error: expected integer, got %s\n", v->str); +} + +static void +ezero(struct val *v) +{ + if (v->num != 0) + return; + enprintf(2, "division by zero\n"); +} + +static int +valcmp(struct val *a, struct val *b) +{ + int ret; + char buf[BUFSIZ]; + + if (!a->str && !b->str) { + ret = (a->num > b->num) - (a->num < b->num); + } else if (a->str && !b->str) { + snprintf(buf, sizeof(buf), "%lld", b->num); + ret = strcmp(a->str, buf); + } else if (!a->str && b->str) { + snprintf(buf, sizeof(buf), "%lld", a->num); + ret = strcmp(buf, b->str); + } else { + ret = strcmp(a->str, b->str); + } + + return ret; +} + +static void +match(struct val *vstr, struct val *vregx, struct val *ret) +{ + regex_t re; + regmatch_t matches[2]; + long long d; + size_t anchlen; + char strbuf[BUFSIZ], regxbuf[BUFSIZ], + *s, *p, *anchreg, *str, *regx; + const char *errstr; + + if (!vstr->str) { + snprintf(strbuf, sizeof(strbuf), "%lld", vstr->num); + str = strbuf; + } else { + str = vstr->str; + } + + if (!vregx->str) { + snprintf(regxbuf, sizeof(regxbuf), "%lld", vregx->num); + regx = regxbuf; + } else { + regx = vregx->str; + } + + /* anchored regex */ + anchlen = strlen(regx) + 1 + 1; + anchreg = emalloc(anchlen); + estrlcpy(anchreg, "^", anchlen); + estrlcat(anchreg, regx, anchlen); + enregcomp(3, &re, anchreg, 0); + free(anchreg); + + if (regexec(&re, str, 2, matches, 0)) { + regfree(&re); + ret->str = re.re_nsub ? "" : NULL; + return; + } else if (re.re_nsub) { + regfree(&re); + + s = str + matches[1].rm_so; + p = str + matches[1].rm_eo; + *p = '\0'; + + d = strtonum(s, LLONG_MIN, LLONG_MAX, &errstr); + if (!errstr) { + ret->num = d; + return; + } else { + ret->str = enstrdup(3, s); + return; + } + } else { + regfree(&re); + str += matches[0].rm_so; + ret->num = utfnlen(str, matches[0].rm_eo - matches[0].rm_so); + return; + } +} + +static void +doop(int *ophead, int *opp, struct val *valhead, struct val *valp) +{ + struct val ret = { .str = NULL, .num = 0 }, *a, *b; + int op; + + /* an operation "a op b" needs an operator and two values */ + if (opp[-1] == '(') + enprintf(2, "syntax error: extra (\n"); + if (valp - valhead < 2) + enprintf(2, "syntax error: missing expression or extra operator\n"); + + a = valp - 2; + b = valp - 1; + op = opp[-1]; + + switch (op) { + case '|': + if ( a->str && *a->str) ret.str = a->str; + else if (!a->str && a->num) ret.num = a->num; + else if ( b->str && *b->str) ret.str = b->str; + else ret.num = b->num; + break; + case '&': + if (((a->str && *a->str) || a->num) && + ((b->str && *b->str) || b->num)) { + ret.str = a->str; + ret.num = a->num; + } + break; + + case '=': ret.num = (valcmp(a, b) == 0); break; + case '>': ret.num = (valcmp(a, b) > 0); break; + case GE : ret.num = (valcmp(a, b) >= 0); break; + case '<': ret.num = (valcmp(a, b) < 0); break; + case LE : ret.num = (valcmp(a, b) <= 0); break; + case NE : ret.num = (valcmp(a, b) != 0); break; + + case '+': enan(a); enan(b); ret.num = a->num + b->num; break; + case '-': enan(a); enan(b); ret.num = a->num - b->num; break; + case '*': enan(a); enan(b); ret.num = a->num * b->num; break; + case '/': enan(a); enan(b); ezero(b); ret.num = a->num / b->num; break; + case '%': enan(a); enan(b); ezero(b); ret.num = a->num % b->num; break; + + case ':': match(a, b, &ret); break; + } + + valp[-2] = ret; +} + +static int +lex(char *s, struct val *v) +{ + long long d; + int type = VAL; + char *ops = "|&=><+-*/%():"; + const char *errstr; + + d = strtonum(s, LLONG_MIN, LLONG_MAX, &errstr); + + if (!errstr) { + /* integer */ + v->num = d; + } else if (s[0] && strchr(ops, s[0]) && !s[1]) { + /* one-char operand */ + type = s[0]; + } else if (s[0] && strchr(">') ? GE : (s[0] == '<') ? LE : NE; + } else { + /* string */ + v->str = s; + } + + return type; +} + +static int +parse(char *expr[], int numexpr) +{ + struct val valhead[numexpr], *valp = valhead, v = { .str = NULL, .num = 0 }; + int ophead[numexpr], *opp = ophead, type, lasttype = 0; + char prec[] = { + [ 0 ] = 0, [VAL] = 0, ['('] = 0, [')'] = 0, + ['|'] = 1, + ['&'] = 2, + ['='] = 3, ['>'] = 3, [GE] = 3, ['<'] = 3, [LE] = 3, [NE] = 3, + ['+'] = 4, ['-'] = 4, + ['*'] = 5, ['/'] = 5, ['%'] = 5, + [':'] = 6, + }; + + for (; *expr; expr++) { + switch ((type = lex(*expr, &v))) { + case VAL: + valp->str = v.str; + valp->num = v.num; + valp++; + break; + case '(': + *opp++ = type; + break; + case ')': + if (lasttype == '(') + enprintf(2, "syntax error: empty ( )\n"); + while (opp > ophead && opp[-1] != '(') + doop(ophead, opp--, valhead, valp--); + if (opp == ophead) + enprintf(2, "syntax error: extra )\n"); + opp--; + break; + default: /* operator */ + if (prec[lasttype]) + enprintf(2, "syntax error: extra operator\n"); + while (opp > ophead && prec[opp[-1]] >= prec[type]) + doop(ophead, opp--, valhead, valp--); + *opp++ = type; + break; + } + lasttype = type; + v.str = NULL; + v.num = 0; + } + while (opp > ophead) + doop(ophead, opp--, valhead, valp--); + if (valp == valhead) + enprintf(2, "syntax error: missing expression\n"); + if (--valp > valhead) + enprintf(2, "syntax error: extra expression\n"); + + if (valp->str) + puts(valp->str); + else + printf("%lld\n", valp->num); + + return (valp->str && *valp->str) || valp->num; +} + +int +main(int argc, char *argv[]) +{ + int ret; + + argv0 = argv[0], argc--, argv++; + + ret = !parse(argv, argc); + + if (fshut(stdout, "")) + ret = 3; + + return ret; +} diff --git a/source/sbase/false.1 b/source/sbase/false.1 new file mode 100644 index 00000000..12cd80bb --- /dev/null +++ b/source/sbase/false.1 @@ -0,0 +1,17 @@ +.Dd 2015-10-08 +.Dt FALSE 1 +.Os sbase +.Sh NAME +.Nm false +.Nd return failure +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +.Nm +returns a status code indicating failure. +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/false.c b/source/sbase/false.c new file mode 100644 index 00000000..fce3fd98 --- /dev/null +++ b/source/sbase/false.c @@ -0,0 +1,6 @@ +/* See LICENSE file for copyright and license details. */ +int +main(void) +{ + return 1; +} diff --git a/source/sbase/find.1 b/source/sbase/find.1 new file mode 100644 index 00000000..8c033b26 --- /dev/null +++ b/source/sbase/find.1 @@ -0,0 +1,135 @@ +.Dd 2015-10-08 +.Dt FIND 1 +.Os sbase +.Sh NAME +.Nm find +.Nd find files +.Sh SYNOPSIS +.Nm +.Op Fl H | L +.Ar path Op ... +.Op Ar expression +.Sh DESCRIPTION +.Nm +walks a file hierarchy starting at each +.Ar path +and applies the +.Ar expression +to each file encountered. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl H +Dereference symbolic links provided as +.Ar path . +.It Fl L +Dereference all symbolic links encountered. +.El +.Sh EXTENDED DESCRIPTION +.Ar expression +is a combination of the following primaries and boolean operators. In +the following descriptions the number n can be replaced by +n, n, or -n, +to mean more than, exactly, or less than n respectively. +.Ss Primaries +.Bl -tag -width Ds +.It Fl name Ar pattern +True if the name of the file matches the given pattern. +.It Fl path Ar pattern +True if the path to the file matches the given pattern. +.It Fl nouser +True if the file belongs to a user for which getpwuid() returns NULL. +.It Fl nogroup +True if the file belongs to a group for which getgrgid() returns NULL. +.It Fl xdev +True. Do not enter directory on a different device. +.It Fl prune +True. Do not enter directory. +.It Fl perm Ar mode +True if permissions on the file match mode. Mode is a symbolic mode +as used in chmod. A leading '-' in mode checks that at least all bits +in mode are set in permissions for file. Without the leading '-' the +permissions for file must exactly match mode. +.It Fl type Ar t +True if file is of type specified by +.Ar t . +.Bl -tag -width Ds +.It Ar b +block special +.It Ar c +character special +.It Ar d +directory +.It Ar l +symbolic link +.It Ar p +FIFO +.It Ar f +regular file +.It Ar s +socket +.El +.It Fl links Ar n +True if file has +.Ar n +links. +.It Fl user Ar name +True if file belongs to user +.Ar name . +.It Fl group Ar name +True if file belongs to group +.Ar name . +.It Fl size Ar n[c] +True if file size in 512 byte sectors (rounded up), or bytes (if +.Ar c +is given), is +.Ar n . +.It Fl atime n +True if file access time is +.Ar n +days. +.It Fl ctime +True if file status change time is +.Ar n +days. +.It Fl mtime +True if file modified time is +.Ar n +days. +.It Fl exec Ar cmd [arg ...] \&; +Execute cmd with given arguments, replacing each {} in argument list +with the current file. True if cmd exits with status 0. +.It Fl exec Ar cmd [arg ...] {} + +True. Add as many files as possible to argument list and execute when +the list is full or all files have been found. +.It Fl ok Ar cmd [arg ...] \&; +Prompt the user on each file encountered whether or not to execute cmd +as with -exec. True if the user responds yes and cmd exits with status 0, +false otherwise. +.It Fl print +True. Print the path to the current file. +.It Fl newer Ar file +True if the modification time of the current file is newer than that of +the provided file. +.It Fl depth +True. Causes find to evaluate files within in a directory before the +directory itself. +.El +.Ss Operators +In order of decreasing precedence +.Bl -tag -width Ds +.It Ar \&( expression \&) +True if expression is true. +.It Ar \&! expression +True if expression if false. +.It Ar expression [ Fl a ] Ar expression +True if both expressions are true. Second expression is not evaluated +if first expression is false. +.Fl a +is implied if there is no operator between primaries. +.It Ar expression Fl o Ar expression +True if either expression is true. Second expression is not evaluated +if first expression is true. +.El +.Pp +If no expression is supplied, -print is used. If an expression is supplied +but none of -print, -exec, or -ok is supplied, then -a -print is appended +to the expressions. diff --git a/source/sbase/find.c b/source/sbase/find.c new file mode 100644 index 00000000..ad0c731a --- /dev/null +++ b/source/sbase/find.c @@ -0,0 +1,1044 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "util.h" + +/* because putting integers in pointers is undefined by the standard */ +union extra { + void *p; + intmax_t i; +}; + +/* Argument passed into a primary's function */ +struct arg { + char *path; + struct stat *st; + union extra extra; +}; + +/* Information about each primary, for lookup table */ +struct pri_info { + char *name; + int (*func)(struct arg *arg); + char **(*getarg)(char **argv, union extra *extra); + void (*freearg)(union extra extra); + char narg; /* -xdev, -depth, -print don't take args but have getarg() */ +}; + +/* Information about operators, for lookup table */ +struct op_info { + char *name; /* string representation of op */ + char type; /* from tok.type */ + char prec; /* precedence */ + char nargs; /* number of arguments (unary or binary) */ + char lassoc; /* left associative */ +}; + +/* Token when lexing/parsing + * (although also used for the expression tree) */ +struct tok { + struct tok *left, *right; /* if (type == NOT) left = NULL */ + union extra extra; + union { + struct pri_info *pinfo; /* if (type == PRIM) */ + struct op_info *oinfo; + } u; + enum { + PRIM = 0, LPAR, RPAR, NOT, AND, OR, END + } type; +}; + +/* structures used for arg.extra.p and tok.extra.p */ +struct permarg { + mode_t mode; + char exact; +}; + +struct okarg { + char ***braces; + char **argv; +}; + +/* for all arguments that take a number + * +n, n, -n mean > n, == n, < n respectively */ +struct narg { + int (*cmp)(int a, int b); + int n; +}; + +struct sizearg { + struct narg n; + char bytes; /* size is in bytes, not 512 byte sectors */ +}; + +struct execarg { + union { + struct { + char ***braces; /* NULL terminated list of pointers into argv where {} were */ + } s; /* semicolon */ + struct { + size_t arglen; /* number of bytes in argv before files are added */ + size_t filelen; /* numer of bytes in file names added to argv */ + size_t first; /* index one past last arg, where first file goes */ + size_t next; /* index where next file goes */ + size_t cap; /* capacity of argv */ + } p; /* plus */ + } u; + char **argv; /* NULL terminated list of arguments (allocated if isplus) */ + char isplus; /* -exec + instead of -exec ; */ +}; + +/* used to find loops while recursing through directory structure */ +struct findhist { + struct findhist *next; + char *path; + dev_t dev; + ino_t ino; +}; + +/* Primaries */ +static int pri_name (struct arg *arg); +static int pri_path (struct arg *arg); +static int pri_nouser (struct arg *arg); +static int pri_nogroup(struct arg *arg); +static int pri_xdev (struct arg *arg); +static int pri_prune (struct arg *arg); +static int pri_perm (struct arg *arg); +static int pri_type (struct arg *arg); +static int pri_links (struct arg *arg); +static int pri_user (struct arg *arg); +static int pri_group (struct arg *arg); +static int pri_size (struct arg *arg); +static int pri_atime (struct arg *arg); +static int pri_ctime (struct arg *arg); +static int pri_mtime (struct arg *arg); +static int pri_exec (struct arg *arg); +static int pri_ok (struct arg *arg); +static int pri_print (struct arg *arg); +static int pri_newer (struct arg *arg); +static int pri_depth (struct arg *arg); + +/* Getargs */ +static char **get_name_arg (char *argv[], union extra *extra); +static char **get_path_arg (char *argv[], union extra *extra); +static char **get_xdev_arg (char *argv[], union extra *extra); +static char **get_perm_arg (char *argv[], union extra *extra); +static char **get_type_arg (char *argv[], union extra *extra); +static char **get_n_arg (char *argv[], union extra *extra); +static char **get_user_arg (char *argv[], union extra *extra); +static char **get_group_arg(char *argv[], union extra *extra); +static char **get_size_arg (char *argv[], union extra *extra); +static char **get_exec_arg (char *argv[], union extra *extra); +static char **get_ok_arg (char *argv[], union extra *extra); +static char **get_print_arg(char *argv[], union extra *extra); +static char **get_newer_arg(char *argv[], union extra *extra); +static char **get_depth_arg(char *argv[], union extra *extra); + +/* Freeargs */ +static void free_extra (union extra extra); +static void free_exec_arg(union extra extra); +static void free_ok_arg (union extra extra); + +/* Parsing/Building/Running */ +static void fill_narg(char *s, struct narg *n); +static struct pri_info *find_primary(char *name); +static struct op_info *find_op(char *name); +static void parse(int argc, char **argv); +static int eval(struct tok *tok, struct arg *arg); +static void find(char *path, struct findhist *hist); +static void usage(void); + +/* for comparisons with narg */ +static int cmp_gt(int a, int b) { return a > b; } +static int cmp_eq(int a, int b) { return a == b; } +static int cmp_lt(int a, int b) { return a < b; } + +/* order from find(1p), may want to alphabetize */ +static struct pri_info primaries[] = { + { "-name" , pri_name , get_name_arg , NULL , 1 }, + { "-path" , pri_path , get_path_arg , NULL , 1 }, + { "-nouser" , pri_nouser , NULL , NULL , 1 }, + { "-nogroup", pri_nogroup, NULL , NULL , 1 }, + { "-xdev" , pri_xdev , get_xdev_arg , NULL , 0 }, + { "-prune" , pri_prune , NULL , NULL , 1 }, + { "-perm" , pri_perm , get_perm_arg , free_extra , 1 }, + { "-type" , pri_type , get_type_arg , NULL , 1 }, + { "-links" , pri_links , get_n_arg , free_extra , 1 }, + { "-user" , pri_user , get_user_arg , NULL , 1 }, + { "-group" , pri_group , get_group_arg, NULL , 1 }, + { "-size" , pri_size , get_size_arg , free_extra , 1 }, + { "-atime" , pri_atime , get_n_arg , free_extra , 1 }, + { "-ctime" , pri_ctime , get_n_arg , free_extra , 1 }, + { "-mtime" , pri_mtime , get_n_arg , free_extra , 1 }, + { "-exec" , pri_exec , get_exec_arg , free_exec_arg, 1 }, + { "-ok" , pri_ok , get_ok_arg , free_ok_arg , 1 }, + { "-print" , pri_print , get_print_arg, NULL , 0 }, + { "-newer" , pri_newer , get_newer_arg, NULL , 1 }, + { "-depth" , pri_depth , get_depth_arg, NULL , 0 }, + + { NULL, NULL, NULL, NULL, 0 } +}; + +static struct op_info ops[] = { + { "(" , LPAR, 0, 0, 0 }, /* parens are handled specially */ + { ")" , RPAR, 0, 0, 0 }, + { "!" , NOT , 3, 1, 0 }, + { "-a", AND , 2, 2, 1 }, + { "-o", OR , 1, 2, 1 }, + + { NULL, 0, 0, 0, 0 } +}; + +extern char **environ; + +static struct tok *toks; /* holds allocated array of all toks created while parsing */ +static struct tok *root; /* points to root of expression tree, inside toks array */ + +static struct timespec start; /* time find was started, used for -[acm]time */ + +static size_t envlen; /* number of bytes in environ, used to calculate against ARG_MAX */ +static size_t argmax; /* value of ARG_MAX retrieved using sysconf(3p) */ + +static struct { + char ret ; /* return value from main */ + char depth; /* -depth, directory contents before directory itself */ + char h ; /* -H, follow symlinks on command line */ + char l ; /* -L, follow all symlinks (command line and search) */ + char prune; /* hit -prune */ + char xdev ; /* -xdev, prune directories on different devices */ + char print; /* whether we will need -print when parsing */ +} gflags; + +/* + * Primaries + */ +static int +pri_name(struct arg *arg) +{ + return !fnmatch((char *)arg->extra.p, basename(arg->path), 0); +} + +static int +pri_path(struct arg *arg) +{ + return !fnmatch((char *)arg->extra.p, arg->path, 0); +} + +/* FIXME: what about errors? find(1p) literally just says + * "for which the getpwuid() function ... returns NULL" */ +static int +pri_nouser(struct arg *arg) +{ + return !getpwuid(arg->st->st_uid); +} + +static int +pri_nogroup(struct arg *arg) +{ + return !getgrgid(arg->st->st_gid); +} + +static int +pri_xdev(struct arg *arg) +{ + return 1; +} + +static int +pri_prune(struct arg *arg) +{ + return gflags.prune = 1; +} + +static int +pri_perm(struct arg *arg) +{ + struct permarg *p = (struct permarg *)arg->extra.p; + + return (arg->st->st_mode & 07777 & (p->exact ? -1U : p->mode)) == p->mode; +} + +static int +pri_type(struct arg *arg) +{ + switch ((char)arg->extra.i) { + default : return 0; /* impossible, but placate warnings */ + case 'b': return S_ISBLK (arg->st->st_mode); + case 'c': return S_ISCHR (arg->st->st_mode); + case 'd': return S_ISDIR (arg->st->st_mode); + case 'l': return S_ISLNK (arg->st->st_mode); + case 'p': return S_ISFIFO(arg->st->st_mode); + case 'f': return S_ISREG (arg->st->st_mode); + case 's': return S_ISSOCK(arg->st->st_mode); + } +} + +static int +pri_links(struct arg *arg) +{ + struct narg *n = arg->extra.p; + return n->cmp(arg->st->st_nlink, n->n); +} + +static int +pri_user(struct arg *arg) +{ + return arg->st->st_uid == (uid_t)arg->extra.i; +} + +static int +pri_group(struct arg *arg) +{ + return arg->st->st_gid == (gid_t)arg->extra.i; +} + +static int +pri_size(struct arg *arg) +{ + struct sizearg *s = arg->extra.p; + off_t size = arg->st->st_size; + + if (!s->bytes) + size = size / 512 + !!(size % 512); + + return s->n.cmp(size, s->n.n); +} + +/* FIXME: ignoring nanoseconds in atime, ctime, mtime */ +static int +pri_atime(struct arg *arg) +{ + struct narg *n = arg->extra.p; + time_t time = (n->n - start.tv_sec) / 86400; + return n->cmp(time, n->n); +} + +static int +pri_ctime(struct arg *arg) +{ + struct narg *n = arg->extra.p; + time_t time = (n->n - start.tv_sec) / 86400; + return n->cmp(time, n->n); +} + +static int +pri_mtime(struct arg *arg) +{ + struct narg *n = arg->extra.p; + time_t time = (n->n - start.tv_sec) / 86400; + return n->cmp(time, n->n); +} + +static int +pri_exec(struct arg *arg) +{ + int status; + size_t len; + pid_t pid; + char **sp, ***brace; + struct execarg *e = arg->extra.p; + + if (e->isplus) { + len = strlen(arg->path) + 1; + + /* if we reached ARG_MAX, fork, exec, wait, free file names, reset list */ + if (len + e->u.p.arglen + e->u.p.filelen + envlen > argmax) { + e->argv[e->u.p.next] = NULL; + + switch((pid = fork())) { + case -1: + eprintf("fork:"); + case 0: + execvp(*e->argv, e->argv); + weprintf("exec %s failed:", *e->argv); + _exit(1); + } + waitpid(pid, &status, 0); + gflags.ret = gflags.ret || status; + + for (sp = e->argv + e->u.p.first; *sp; sp++) + free(*sp); + + e->u.p.next = e->u.p.first; + e->u.p.filelen = 0; + } + + /* if we have too many files, realloc (with space for NULL termination) */ + if (e->u.p.next + 1 == e->u.p.cap) + e->argv = ereallocarray(e->argv, e->u.p.cap *= 2, sizeof(*e->argv)); + + e->argv[e->u.p.next++] = estrdup(arg->path); + e->u.p.filelen += len + sizeof(arg->path); + + return 1; + } else { + /* insert path everywhere user gave us {} */ + for (brace = e->u.s.braces; *brace; brace++) + **brace = arg->path; + + switch((pid = fork())) { + case -1: + eprintf("fork:"); + case 0: + execvp(*e->argv, e->argv); + weprintf("exec %s failed:", *e->argv); + _exit(1); + } + /* FIXME: propper course of action for all waitpid() on EINTR? */ + waitpid(pid, &status, 0); + return !!status; + } +} + +static int +pri_ok(struct arg *arg) +{ + int status, reply; + pid_t pid; + char ***brace, c; + struct okarg *o = arg->extra.p; + + fprintf(stderr, "%s: %s ? ", *o->argv, arg->path); + reply = fgetc(stdin); + + /* throw away rest of line */ + while ((c = fgetc(stdin)) != '\n' && c != EOF) + /* FIXME: what if the first character of the rest of the line is a null + * byte? */ + ; + + if (feof(stdin)) /* FIXME: ferror()? */ + clearerr(stdin); + + if (reply != 'y' && reply != 'Y') + return 0; + + /* insert filename everywhere user gave us {} */ + for (brace = o->braces; *brace; brace++) + **brace = arg->path; + + switch((pid = fork())) { + case -1: + eprintf("fork:"); + case 0: + execvp(*o->argv, o->argv); + weprintf("exec %s failed:", *o->argv); + _exit(1); + } + waitpid(pid, &status, 0); + return !!status; +} + +static int +pri_print(struct arg *arg) +{ + if (puts(arg->path) == EOF) + eprintf("puts failed:"); + return 1; +} + +/* FIXME: ignoring nanoseconds */ +static int +pri_newer(struct arg *arg) +{ + return arg->st->st_mtime > (time_t)arg->extra.i; +} + +static int +pri_depth(struct arg *arg) +{ + return 1; +} + +/* + * Getargs + * consume any arguments for given primary and fill extra + * return pointer to last argument, the pointer will be incremented in parse() + */ +static char ** +get_name_arg(char *argv[], union extra *extra) +{ + extra->p = *argv; + return argv; +} + +static char ** +get_path_arg(char *argv[], union extra *extra) +{ + extra->p = *argv; + return argv; +} + +static char ** +get_xdev_arg(char *argv[], union extra *extra) +{ + gflags.xdev = 1; + return argv; +} + +static char ** +get_perm_arg(char *argv[], union extra *extra) +{ + struct permarg *p = extra->p = emalloc(sizeof(*p)); + + if (**argv == '-') + (*argv)++; + else + p->exact = 1; + + p->mode = parsemode(*argv, 0, 0); + + return argv; +} + +static char ** +get_type_arg(char *argv[], union extra *extra) +{ + if (!strchr("bcdlpfs", **argv)) + eprintf("invalid type %c for -type primary\n", **argv); + + extra->i = **argv; + return argv; +} + +static char ** +get_n_arg(char *argv[], union extra *extra) +{ + struct narg *n = extra->p = emalloc(sizeof(*n)); + fill_narg(*argv, n); + return argv; +} + +static char ** +get_user_arg(char *argv[], union extra *extra) +{ + char *end; + struct passwd *p = getpwnam(*argv); + + if (p) { + extra->i = p->pw_uid; + } else { + extra->i = strtol(*argv, &end, 10); + if (end == *argv || *end) + eprintf("unknown user '%s'\n", *argv); + } + return argv; +} + +static char ** +get_group_arg(char *argv[], union extra *extra) +{ + char *end; + struct group *g = getgrnam(*argv); + + if (g) { + extra->i = g->gr_gid; + } else { + extra->i = strtol(*argv, &end, 10); + if (end == *argv || *end) + eprintf("unknown group '%s'\n", *argv); + } + return argv; +} + +static char ** +get_size_arg(char *argv[], union extra *extra) +{ + char *p = *argv + strlen(*argv); + struct sizearg *s = extra->p = emalloc(sizeof(*s)); + /* if the number is followed by 'c', the size will by in bytes */ + if ((s->bytes = (p > *argv && *--p == 'c'))) + *p = '\0'; + + fill_narg(*argv, &s->n); + return argv; +} + +static char ** +get_exec_arg(char *argv[], union extra *extra) +{ + char **arg, **new, ***braces; + int nbraces = 0; + struct execarg *e = extra->p = emalloc(sizeof(*e)); + + for (arg = argv; *arg; arg++) + if (!strcmp(*arg, ";")) + break; + else if (arg > argv && !strcmp(*(arg - 1), "{}") && !strcmp(*arg, "+")) + break; + else if (!strcmp(*arg, "{}")) + nbraces++; + + if (!*arg) + eprintf("no terminating ; or {} + for -exec primary\n"); + + e->isplus = **arg == '+'; + *arg = NULL; + + if (e->isplus) { + *(arg - 1) = NULL; /* don't need the {} in there now */ + e->u.p.arglen = e->u.p.filelen = 0; + e->u.p.first = e->u.p.next = arg - argv - 1; + e->u.p.cap = (arg - argv) * 2; + e->argv = ereallocarray(NULL, e->u.p.cap, sizeof(*e->argv)); + + for (arg = argv, new = e->argv; *arg; arg++, new++) { + *new = *arg; + e->u.p.arglen += strlen(*arg) + 1 + sizeof(*arg); + } + arg++; /* due to our extra NULL */ + } else { + e->argv = argv; + e->u.s.braces = ereallocarray(NULL, ++nbraces, sizeof(*e->u.s.braces)); /* ++ for NULL */ + + for (arg = argv, braces = e->u.s.braces; *arg; arg++) + if (!strcmp(*arg, "{}")) + *braces++ = arg; + *braces = NULL; + } + gflags.print = 0; + return arg; +} + +static char ** +get_ok_arg(char *argv[], union extra *extra) +{ + char **arg, ***braces; + int nbraces = 0; + struct okarg *o = extra->p = emalloc(sizeof(*o)); + + for (arg = argv; *arg; arg++) + if (!strcmp(*arg, ";")) + break; + else if (!strcmp(*arg, "{}")) + nbraces++; + + if (!*arg) + eprintf("no terminating ; for -ok primary\n"); + *arg = NULL; + + o->argv = argv; + o->braces = ereallocarray(NULL, ++nbraces, sizeof(*o->braces)); + + for (arg = argv, braces = o->braces; *arg; arg++) + if (!strcmp(*arg, "{}")) + *braces++ = arg; + *braces = NULL; + + gflags.print = 0; + return arg; +} + +static char ** +get_print_arg(char *argv[], union extra *extra) +{ + gflags.print = 0; + return argv; +} + +/* FIXME: ignoring nanoseconds */ +static char ** +get_newer_arg(char *argv[], union extra *extra) +{ + struct stat st; + + if (stat(*argv, &st)) + eprintf("failed to stat '%s':", *argv); + + extra->i = st.st_mtime; + return argv; +} + +static char ** +get_depth_arg(char *argv[], union extra *extra) +{ + gflags.depth = 1; + return argv; +} + +/* + * Freeargs + */ +static void +free_extra(union extra extra) +{ + free(extra.p); +} + +static void +free_exec_arg(union extra extra) +{ + int status; + pid_t pid; + char **arg; + struct execarg *e = extra.p; + + if (!e->isplus) { + free(e->u.s.braces); + } else { + e->argv[e->u.p.next] = NULL; + + /* if we have files, do the last exec */ + if (e->u.p.first != e->u.p.next) { + switch((pid = fork())) { + case -1: + eprintf("fork:"); + case 0: + execvp(*e->argv, e->argv); + weprintf("exec %s failed:", *e->argv); + _exit(1); + } + waitpid(pid, &status, 0); + gflags.ret = gflags.ret || status; + } + for (arg = e->argv + e->u.p.first; *arg; arg++) + free(*arg); + free(e->argv); + } + free(e); +} + +static void +free_ok_arg(union extra extra) +{ + struct okarg *o = extra.p; + + free(o->braces); + free(o); +} + +/* + * Parsing/Building/Running + */ +static void +fill_narg(char *s, struct narg *n) +{ + char *end; + + switch (*s) { + case '+': n->cmp = cmp_gt; s++; break; + case '-': n->cmp = cmp_lt; s++; break; + default : n->cmp = cmp_eq; break; + } + n->n = strtol(s, &end, 10); + if (end == s || *end) + eprintf("bad number '%s'\n", s); +} + +static struct pri_info * +find_primary(char *name) +{ + struct pri_info *p; + + for (p = primaries; p->name; p++) + if (!strcmp(name, p->name)) + return p; + return NULL; +} + +static struct op_info * +find_op(char *name) +{ + struct op_info *o; + + for (o = ops; o->name; o++) + if (!strcmp(name, o->name)) + return o; + return NULL; +} + +/* given the expression from the command line + * 1) convert arguments from strings to tok and place in an array duplicating + * the infix expression given, inserting "-a" where it was omitted + * 2) allocate an array to hold the correct number of tok, and convert from + * infix to rpn (using shunting-yard), add -a and -print if necessary + * 3) evaluate the rpn filling in left and right pointers to create an + * expression tree (tok are still all contained in the rpn array, just + * pointing at eachother) + */ +static void +parse(int argc, char **argv) +{ + struct tok infix[2 * argc + 1], *stack[argc], *tok, *rpn, *out, **top; + struct op_info *op; + struct pri_info *pri; + char **arg; + int lasttype = -1; + size_t ntok = 0; + struct tok and = { .u.oinfo = find_op("-a"), .type = AND }; + + gflags.print = 1; + + /* convert argv to infix expression of tok, inserting in *tok */ + for (arg = argv, tok = infix; *arg; arg++, tok++) { + pri = find_primary(*arg); + + if (pri) { /* token is a primary, fill out tok and get arguments */ + if (lasttype == PRIM || lasttype == RPAR) { + *tok++ = and; + ntok++; + } + if (pri->getarg) { + if (pri->narg && !*++arg) + eprintf("no argument for primary %s\n", pri->name); + arg = pri->getarg(arg, &tok->extra); + } + tok->u.pinfo = pri; + tok->type = PRIM; + } else if ((op = find_op(*arg))) { /* token is an operator */ + if (lasttype == LPAR && op->type == RPAR) + eprintf("empty parens\n"); + if ((lasttype == PRIM || lasttype == RPAR) && op->type == NOT) { /* need another implicit -a */ + *tok++ = and; + ntok++; + } + tok->type = op->type; + tok->u.oinfo = op; + } else { /* token is neither primary nor operator, must be path in the wrong place */ + eprintf("paths must precede expression: %s\n", *arg); + } + if (tok->type != LPAR && tok->type != RPAR) + ntok++; /* won't have parens in rpn */ + lasttype = tok->type; + } + tok->type = END; + ntok++; + + if (gflags.print && (arg != argv)) /* need to add -a -print (not just -print) */ + gflags.print++; + + /* use shunting-yard to convert from infix to rpn + * https://en.wikipedia.org/wiki/Shunting-yard_algorithm + * read from infix, resulting rpn ends up in rpn, next position in rpn is out + * push operators onto stack, next position in stack is top */ + rpn = ereallocarray(NULL, ntok + gflags.print, sizeof(*rpn)); + for (tok = infix, out = rpn, top = stack; tok->type != END; tok++) { + switch (tok->type) { + case PRIM: *out++ = *tok; break; + case LPAR: *top++ = tok; break; + case RPAR: + while (top-- > stack && (*top)->type != LPAR) + *out++ = **top; + if (top < stack) + eprintf("extra )\n"); + break; + default: + /* this expression can be simplified, but I decided copy the + * verbage from the wikipedia page in order to more clearly explain + * what's going on */ + while (top-- > stack && + (( tok->u.oinfo->lassoc && tok->u.oinfo->prec <= (*top)->u.oinfo->prec) || + (!tok->u.oinfo->lassoc && tok->u.oinfo->prec < (*top)->u.oinfo->prec))) + *out++ = **top; + + /* top now points to either an operator we didn't pop, or stack[-1] + * either way we need to increment it before using it, then + * increment again so the stack works */ + top++; + *top++ = tok; + break; + } + } + while (top-- > stack) { + if ((*top)->type == LPAR) + eprintf("extra (\n"); + *out++ = **top; + } + + /* if there was no expression, use -print + * if there was an expression but no -print, -exec, or -ok, add -a -print + * in rpn, not infix */ + if (gflags.print) + *out++ = (struct tok){ .u.pinfo = find_primary("-print"), .type = PRIM }; + if (gflags.print == 2) + *out++ = and; + + out->type = END; + + /* rpn now holds all operators and arguments in reverse polish notation + * values are pushed onto stack, operators pop values off stack into left + * and right pointers, pushing operator node back onto stack + * could probably just do this during shunting-yard, but this is simpler + * code IMO */ + for (tok = rpn, top = stack; tok->type != END; tok++) { + if (tok->type == PRIM) { + *top++ = tok; + } else { + if (top - stack < tok->u.oinfo->nargs) + eprintf("insufficient arguments for operator %s\n", tok->u.oinfo->name); + tok->right = *--top; + tok->left = tok->u.oinfo->nargs == 2 ? *--top : NULL; + *top++ = tok; + } + } + if (--top != stack) + eprintf("extra arguments\n"); + + toks = rpn; + root = *top; +} + +/* for a primary, run and return result + * for an operator evaluate the left side of the tree, decide whether or not to + * evaluate the right based on the short-circuit boolean logic, return result + * NOTE: operator NOT has NULL left side, expression on right side + */ +static int +eval(struct tok *tok, struct arg *arg) +{ + int ret; + + if (!tok) + return 0; + + if (tok->type == PRIM) { + arg->extra = tok->extra; + return tok->u.pinfo->func(arg); + } + + ret = eval(tok->left, arg); + + if ((tok->type == AND && ret) || (tok->type == OR && !ret) || tok->type == NOT) + ret = eval(tok->right, arg); + + return ret ^ (tok->type == NOT); +} + +/* evaluate path, if it's a directory iterate through directory entries and + * recurse + */ +static void +find(char *path, struct findhist *hist) +{ + struct stat st; + DIR *dir; + struct dirent *de; + struct findhist *f, cur; + size_t len = strlen(path) + 2; /* null and '/' */ + struct arg arg = { path, &st, { NULL } }; + + if ((gflags.l || (gflags.h && !hist) ? stat(path, &st) : lstat(path, &st)) < 0) { + weprintf("failed to stat %s:", path); + return; + } + + gflags.prune = 0; + + /* don't eval now iff we will hit the eval at the bottom which means + * 1. we are a directory 2. we have -depth 3. we don't have -xdev or we are + * on same device (so most of the time we eval here) */ + if (!S_ISDIR(st.st_mode) || + !gflags.depth || + (gflags.xdev && hist && st.st_dev != hist->dev)) + eval(root, &arg); + + if (!S_ISDIR(st.st_mode) || + gflags.prune || + (gflags.xdev && hist && st.st_dev != hist->dev)) + return; + + for (f = hist; f; f = f->next) { + if (f->dev == st.st_dev && f->ino == st.st_ino) { + weprintf("loop detected '%s' is '%s'\n", path, f->path); + return; + } + } + cur.next = hist; + cur.path = path; + cur.dev = st.st_dev; + cur.ino = st.st_ino; + + if (!(dir = opendir(path))) { + weprintf("failed to opendir %s:", path); + /* should we just ignore this since we hit an error? */ + if (gflags.depth) + eval(root, &arg); + return; + } + + /* FIXME: check errno to see if we are done or encountered an error? */ + while ((de = readdir(dir))) { + size_t pathcap = len + strlen(de->d_name); + char pathbuf[pathcap], *p; + + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + + p = pathbuf + estrlcpy(pathbuf, path, pathcap); + if (*--p != '/') + estrlcat(pathbuf, "/", pathcap); + estrlcat(pathbuf, de->d_name, pathcap); + find(pathbuf, &cur); + } + closedir(dir); /* check return value? */ + + if (gflags.depth) + eval(root, &arg); +} + +static void +usage(void) +{ + eprintf("usage: %s [-H | -L] path ... [expression ...]\n", argv0); +} + +int +main(int argc, char **argv) +{ + char **paths; + int npaths; + struct tok *t; + + ARGBEGIN { + case 'H': gflags.l = !(gflags.h = 1); break; + case 'L': gflags.h = !(gflags.l = 1); break; + default : usage(); + } ARGEND + + paths = argv; + + for (; *argv && **argv != '-' && strcmp(*argv, "!") && strcmp(*argv, "("); argv++) + ; + + if (!(npaths = argv - paths)) + eprintf("must specify a path\n"); + + parse(argc - npaths, argv); + + /* calculate number of bytes in environ for -exec {} + ARG_MAX avoidance + * libc implementation defined whether null bytes, pointers, and alignment + * are counted, so count them */ + for (argv = environ; *argv; argv++) + envlen += strlen(*argv) + 1 + sizeof(*argv); + + if ((argmax = sysconf(_SC_ARG_MAX)) == (size_t)-1) + argmax = _POSIX_ARG_MAX; + + if (clock_gettime(CLOCK_REALTIME, &start) < 0) + weprintf("clock_gettime() failed:"); + + while (npaths--) + find(*paths++, NULL); + + for (t = toks; t->type != END; t++) + if (t->type == PRIM && t->u.pinfo->freearg) + t->u.pinfo->freearg(t->extra); + free(toks); + + gflags.ret |= fshut(stdin, "") | fshut(stdout, ""); + + return gflags.ret; +} diff --git a/source/sbase/flock.1 b/source/sbase/flock.1 new file mode 100644 index 00000000..84a0098f --- /dev/null +++ b/source/sbase/flock.1 @@ -0,0 +1,32 @@ +.Dd 2015-10-08 +.Dt FLOCK 1 +.Os sbase +.Sh NAME +.Nm flock +.Nd tool to manage locks on files +.Sh SYNOPSIS +.Nm +.Op Fl nosux +.Ar file +.Ar cmd Op arg ... +.Sh DESCRIPTION +.Nm +is used to manage advisory locks on open files. It is commonly used to prevent +long running cron jobs from running in parallel. If +.Ar file +does not exist, it will be created. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl n +Set non-blocking mode on the lock. Fail immediately if the lock +cannot be acquired. +.It Fl o +Close the file descriptor before exec to avoid having the exec'ed +program holding on to the lock. +.It Fl s +Acquire a shared lock. +.It Fl u +Release the lock. +.It Fl x +Acquire an exclusive lock. This is the default. +.El diff --git a/source/sbase/flock.c b/source/sbase/flock.c new file mode 100644 index 00000000..fc2b6ed6 --- /dev/null +++ b/source/sbase/flock.c @@ -0,0 +1,82 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include +#include +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-nosux] file cmd [arg ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int fd, status, savederrno, flags = LOCK_EX, nonblk = 0, oflag = 0; + pid_t pid; + + ARGBEGIN { + case 'n': + nonblk = LOCK_NB; + break; + case 'o': + oflag = 1; + break; + case 's': + flags = LOCK_SH; + break; + case 'u': + flags = LOCK_UN; + break; + case 'x': + flags = LOCK_EX; + break; + default: + usage(); + } ARGEND + + if (argc < 2) + usage(); + + if ((fd = open(*argv, O_RDONLY | O_CREAT, 0644)) < 0) + eprintf("open %s:", *argv); + + if (flock(fd, flags | nonblk)) { + if (nonblk && errno == EWOULDBLOCK) + return 1; + eprintf("flock:"); + } + + switch ((pid = fork())) { + case -1: + eprintf("fork:"); + case 0: + if (oflag && close(fd) < 0) + eprintf("close:"); + argv++; + execvp(*argv, argv); + savederrno = errno; + weprintf("execvp %s:", *argv); + _exit(126 + (savederrno == ENOENT)); + default: + break; + } + if (waitpid(pid, &status, 0) < 0) + eprintf("waitpid:"); + + if (close(fd) < 0) + eprintf("close:"); + + if (WIFSIGNALED(status)) + return 128 + WTERMSIG(status); + if (WIFEXITED(status)) + return WEXITSTATUS(status); + + return 0; +} diff --git a/source/sbase/fold.1 b/source/sbase/fold.1 new file mode 100644 index 00000000..189149e5 --- /dev/null +++ b/source/sbase/fold.1 @@ -0,0 +1,42 @@ +.Dd 2015-10-08 +.Dt FOLD 1 +.Os sbase +.Sh NAME +.Nm fold +.Nd wrap lines to width +.Sh SYNOPSIS +.Nm +.Op Fl bs +.Op Fl w Ar num | Fl Ns Ar num +.Op Ar file ... +.Sh DESCRIPTION +.Nm +reads each +.Ar file +and prints its lines wrapped such that no line +exceeds a certain width. +If no file is given, +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl b +Count bytes rather than characters. +.It Fl s +If a line contains spaces, break +at the last space within width. +.It Fl w Ar num | Fl Ns Ar num +Break at +.Ar num +characters. The default is 80. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The +.Op Fl Ns Ar num +syntax is an extension to that specification. diff --git a/source/sbase/fold.c b/source/sbase/fold.c new file mode 100644 index 00000000..a5a987d5 --- /dev/null +++ b/source/sbase/fold.c @@ -0,0 +1,119 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "text.h" +#include "util.h" + +static int bflag = 0; +static int sflag = 0; +static size_t width = 80; + +static void +foldline(struct line *l) { + size_t i, col, last, spacesect, len; + + for (i = 0, last = 0, col = 0, spacesect = 0; i < l->len; i++) { + if (!UTF8_POINT(l->data[i]) && !bflag) + continue; + if (col >= width) { + len = ((sflag && spacesect) ? spacesect : i) - last; + if (fwrite(l->data + last, 1, len, stdout) != len) + eprintf("fwrite :"); + putchar('\n'); + last = (sflag && spacesect) ? spacesect : i; + col = 0; + spacesect = 0; + } + if (sflag && isspace(l->data[i])) + spacesect = i + 1; + if (!bflag && iscntrl(l->data[i])) { + switch(l->data[i]) { + case '\b': + col -= (col > 0); + break; + case '\r': + col = 0; + break; + case '\t': + col += (col + 1) % 8; + break; + } + } else { + col++; + } + } + if (l->len - last) + fwrite(l->data + last, 1, l->len - last, stdout); +} + +static void +fold(FILE *fp, const char *fname) +{ + static struct line line; + static size_t size = 0; + ssize_t len; + + while ((len = getline(&line.data, &size, fp)) > 0) { + line.len = len; + foldline(&line); + } + if (ferror(fp)) + eprintf("getline %s:", fname); +} + +static void +usage(void) +{ + eprintf("usage: %s [-bs] [-w num | -num] [FILE ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + int ret = 0; + + ARGBEGIN { + case 'b': + bflag = 1; + break; + case 's': + sflag = 1; + break; + case 'w': + width = estrtonum(EARGF(usage()), 1, MIN(LLONG_MAX, SIZE_MAX)); + break; + ARGNUM: + if (!(width = ARGNUMF())) + eprintf("illegal width value, too small\n"); + break; + default: + usage(); + } ARGEND + + if (!argc) { + fold(stdin, ""); + } else { + for (; *argv; argc--, argv++) { + if (!strcmp(*argv, "-")) { + *argv = ""; + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + ret = 1; + continue; + } + fold(fp, *argv); + if (fp != stdin && fshut(fp, *argv)) + ret = 1; + } + } + + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/fs.h b/source/sbase/fs.h new file mode 100644 index 00000000..15ae5f46 --- /dev/null +++ b/source/sbase/fs.h @@ -0,0 +1,43 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +struct history { + struct history *prev; + dev_t dev; + ino_t ino; +}; + +struct recursor { + void (*fn)(const char *, struct stat *st, void *, struct recursor *); + struct history *hist; + int depth; + int maxdepth; + int follow; + int flags; +}; + +enum { + SAMEDEV = 1 << 0, + DIRFIRST = 1 << 1, + SILENT = 1 << 2, +}; + +extern int cp_aflag; +extern int cp_fflag; +extern int cp_pflag; +extern int cp_rflag; +extern int cp_vflag; +extern int cp_follow; +extern int cp_status; + +extern int rm_fflag; +extern int rm_rflag; +extern int rm_status; + +extern int recurse_status; + +void recurse(const char *, void *, struct recursor *); + +int cp(const char *, const char *, int); +void rm(const char *, struct stat *st, void *, struct recursor *); diff --git a/source/sbase/getconf.1 b/source/sbase/getconf.1 new file mode 100644 index 00000000..8fcf9605 --- /dev/null +++ b/source/sbase/getconf.1 @@ -0,0 +1,60 @@ +.Dd 2015-10-08 +.Dt GETCONF 1 +.Os sbase +.Sh NAME +.Nm getconf +.Nd get configuration values +.Sh SYNOPSIS +.Nm +.Op Fl v Ar spec +.Ar var +.Ar [path] +.Sh DESCRIPTION +.Nm +writes the value of the variable +.Ar var +to stdout. +.sp +If +.Ar path +is given, +.Ar var +is matched against configuration values from +.Xr pathconf 3 . +If +.Ar path +is not given, +.Ar var +is matched against configuration values from +.Xr sysconf 3 , +.Xr confstr 3 +and limits.h (Minimum and Maximum). +.sp +If +.Ar var +is not defined or has not been found, +.Nm +writes "undefined" to stdout. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl v Ar spec +Ignored. +.El +.Sh EXIT STATUS +.Bl -tag -width Ds +.It 0 +.Ar var +was matched and its value written successfully. +.It 1 +An error occured or +.Ar var +was neither defined nor found. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification except from the +.Op Fl v +flag. diff --git a/source/sbase/getconf.c b/source/sbase/getconf.c new file mode 100644 index 00000000..94bff806 --- /dev/null +++ b/source/sbase/getconf.c @@ -0,0 +1,120 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "util.h" + +struct var { + const char *k; + long v; +}; + +static const struct var pathconf_l[] = { +#include "pathconf_l.h" +}; + +static const struct var sysconf_l[] = { +#include "sysconf_l.h" +}; + +static const struct var confstr_l[] = { +#include "confstr_l.h" +}; + +static const struct var limits_l[] = { +#include "limits_l.h" +}; + + +void +usage(void) +{ + eprintf("usage: %s [-v spec] var [path]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + size_t len; + long res; + int i; + char *str; + + ARGBEGIN { + case 'v': + /* ignore */ + EARGF(usage()); + break; + } ARGEND + + if (argc == 1) { + /* sysconf */ + for (i = 0; i < LEN(sysconf_l); i++) { + if (strcmp(argv[0], sysconf_l[i].k)) + continue; + errno = 0; + if ((res = sysconf(sysconf_l[i].v)) < 0) { + if (errno) + eprintf("sysconf %ld:", sysconf_l[i].v); + puts("undefined"); + } else { + printf("%ld\n", res); + } + return 0; + } + /* confstr */ + for (i = 0; i < LEN(confstr_l); i++) { + if (strcmp(argv[0], confstr_l[i].k)) + continue; + errno = 0; + if (!(len = confstr(confstr_l[i].v, NULL, 0))) { + if (errno) + eprintf("confstr %ld:", confstr_l[i].v); + puts("undefined"); + } else { + str = emalloc(len); + errno = 0; + if (!confstr(confstr_l[i].v, str, len)) { + if (errno) + eprintf("confstr %ld:", confstr_l[i].v); + puts("undefined"); + } else { + puts(str); + } + free(str); + } + return 0; + } + /* limits */ + for (i = 0; i < LEN(limits_l); i++) { + if (strcmp(argv[0], limits_l[i].k)) + continue; + printf("%ld\n", limits_l[i].v); + return 0; + } + } else if (argc == 2) { + /* pathconf */ + for (i = 0; i < LEN(pathconf_l); i++) { + if (strcmp(argv[0], pathconf_l[i].k)) + continue; + errno = 0; + if ((res = pathconf(argv[1], pathconf_l[i].v)) < 0) { + if (errno) + eprintf("pathconf %ld:", pathconf_l[i].v); + puts("undefined"); + } else { + printf("%ld\n", res); + } + return 0; + } + } else { + usage(); + } + + puts("undefined"); + + return 1; +} diff --git a/source/sbase/getconf.sh b/source/sbase/getconf.sh new file mode 100755 index 00000000..cca58734 --- /dev/null +++ b/source/sbase/getconf.sh @@ -0,0 +1,220 @@ +#!/bin/sh + +ifdef() +{ + awk '{printf("#ifdef %s\n"\ + "\t{\"%s\",\t%s},\n"\ + "#endif\n", $2, $1, $2)}' > $1 +} + + +cat <. 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 +.Sy not +match the pattern. +.It Fl w +The expression is searched for as a word (as if surrounded by '\<' and '\>'). +.It Fl x +Consider only input lines that use all characters in the line excluding the +terminating 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 sed 1 , +.Xr regex 7 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The +.Op Fl Hhw +flags are an extension to that specification. diff --git a/source/sbase/grep.c b/source/sbase/grep.c new file mode 100644 index 00000000..64ffbe2c --- /dev/null +++ b/source/sbase/grep.c @@ -0,0 +1,288 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "queue.h" +#include "util.h" + +enum { Match = 0, NoMatch = 1, Error = 2 }; + +static void addpattern(const char *, size_t); +static void addpatternfile(FILE *); +static int grep(FILE *, const char *); + +static int Eflag; +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 wflag; +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 +addpattern(const char *pattern, size_t patlen) +{ + struct pattern *pnode; + char *tmp; + int bol, eol; + size_t len; + + if (!patlen) + return; + + /* a null BRE/ERE matches every line */ + if (!Fflag) + if (pattern[0] == '\0') + pattern = "^"; + + if (!Fflag && xflag) { + tmp = enmalloc(Error, patlen + 3); + snprintf(tmp, patlen + 3, "%s%s%s", + pattern[0] == '^' ? "" : "^", + pattern, + pattern[patlen - 1] == '$' ? "" : "$"); + } else if (!Fflag && wflag) { + len = patlen + 5 + (Eflag ? 2 : 4); + tmp = enmalloc(Error, len); + + bol = eol = 0; + if (pattern[0] == '^') + bol = 1; + if (pattern[patlen - 1] == '$') + eol = 1; + + snprintf(tmp, len, "%s\\<%s%.*s%s\\>%s", + bol ? "^" : "", + Eflag ? "(" : "\\(", + (int)patlen - bol - eol, pattern + bol, + Eflag ? ")" : "\\)", + eol ? "$" : ""); + } else { + tmp = enstrdup(Error, pattern); + } + + pnode = enmalloc(Error, sizeof(*pnode)); + pnode->pattern = tmp; + SLIST_INSERT_HEAD(&phead, pnode, entry); +} + +static void +addpatternfile(FILE *fp) +{ + static char *buf = NULL; + static size_t size = 0; + ssize_t len = 0; + + while ((len = getline(&buf, &size, fp)) > 0) { + if (len > 0 && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + addpattern(buf, (size_t)len); + } + if (ferror(fp)) + enprintf(Error, "read error:"); +} + +static int +grep(FILE *fp, const char *str) +{ + static char *buf = NULL; + static size_t size = 0; + ssize_t len = 0; + long c = 0, n; + struct pattern *pnode; + int match = NoMatch; + + for (n = 1; (len = getline(&buf, &size, fp)) > 0; 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, 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; +} + +static void +usage(void) +{ + enprintf(Error, "usage: %s [-EFHchilnqsvwx] [-e pattern] [-f file] " + "[pattern] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct pattern *pnode; + int m, flags = REG_NOSUB, match = NoMatch; + FILE *fp; + char *arg; + + SLIST_INIT(&phead); + + ARGBEGIN { + case 'E': + Eflag = 1; + Fflag = 0; + flags |= REG_EXTENDED; + break; + case 'F': + Fflag = 1; + Eflag = 0; + flags &= ~REG_EXTENDED; + break; + case 'H': + Hflag = 1; + hflag = 0; + break; + case 'e': + arg = EARGF(usage()); + if (!(fp = fmemopen(arg, strlen(arg) + 1, "r"))) + eprintf("fmemopen:"); + addpatternfile(fp); + efshut(fp, arg); + eflag = 1; + break; + case 'f': + arg = EARGF(usage()); + fp = fopen(arg, "r"); + if (!fp) + enprintf(Error, "fopen %s:", arg); + addpatternfile(fp); + efshut(fp, arg); + 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 'w': + wflag = 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) { + if (!(fp = fmemopen(argv[0], strlen(argv[0]) + 1, "r"))) + eprintf("fmemopen:"); + addpatternfile(fp); + efshut(fp, argv[0]); + 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, ""); + } else { + for (; *argv; argc--, argv++) { + if (!strcmp(*argv, "-")) { + *argv = ""; + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + if (!sflag) + weprintf("fopen %s:", *argv); + match = Error; + continue; + } + m = grep(fp, *argv); + if (m == Error || (match != Error && m == Match)) + match = m; + if (fp != stdin && fshut(fp, *argv)) + match = Error; + } + } + + if (fshut(stdin, "") | fshut(stdout, "")) + match = Error; + + return match; +} diff --git a/source/sbase/head.1 b/source/sbase/head.1 new file mode 100644 index 00000000..a7df5e21 --- /dev/null +++ b/source/sbase/head.1 @@ -0,0 +1,43 @@ +.Dd 2015-10-08 +.Dt HEAD 1 +.Os sbase +.Sh NAME +.Nm head +.Nd display initial lines of files +.Sh SYNOPSIS +.Nm +.Op Fl n Ar num | Fl Ns Ar num +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes +.Ar num +lines of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl n Ar num | Fl Ns Ar num +Display initial +.Ar num +| +.Sy N +lines. Default is 10. +.El +.Sh SEE ALSO +.Xr tail 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The +.Op Fl Ns num +syntax is an extension to that specification. diff --git a/source/sbase/head.c b/source/sbase/head.c new file mode 100644 index 00000000..ae550c01 --- /dev/null +++ b/source/sbase/head.c @@ -0,0 +1,77 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "util.h" + +static void +head(FILE *fp, const char *fname, size_t n) +{ + char *buf = NULL; + size_t i = 0, size = 0; + ssize_t len; + + while (i < n && (len = getline(&buf, &size, fp)) > 0) { + fwrite(buf, 1, len, stdout); + i += (len && (buf[len - 1] == '\n')); + } + free(buf); + if (ferror(fp)) + eprintf("getline %s:", fname); +} + +static void +usage(void) +{ + eprintf("usage: %s [-num | -n num] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + size_t n = 10; + FILE *fp; + int ret = 0, newline = 0, many = 0; + + ARGBEGIN { + case 'n': + n = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, SIZE_MAX)); + break; + ARGNUM: + n = ARGNUMF(); + break; + default: + usage(); + } ARGEND + + if (!argc) { + head(stdin, "", n); + } else { + many = argc > 1; + for (newline = 0; *argv; argc--, argv++) { + if (!strcmp(*argv, "-")) { + *argv = ""; + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + ret = 1; + continue; + } + if (many) { + if (newline) + putchar('\n'); + printf("==> %s <==\n", *argv); + } + newline = 1; + head(fp, *argv, n); + if (fp != stdin && fshut(fp, *argv)) + ret = 1; + } + } + + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/hostname.1 b/source/sbase/hostname.1 new file mode 100644 index 00000000..a8f15de0 --- /dev/null +++ b/source/sbase/hostname.1 @@ -0,0 +1,18 @@ +.Dd 2015-10-08 +.Dt HOSTNAME 1 +.Os sbase +.Sh NAME +.Nm hostname +.Nd set or print host name +.Sh SYNOPSIS +.Nm +.Op Ar name +.Sh DESCRIPTION +.Nm +sets the current host name to +.Ar name . +If no +.Ar name +is given, the current host name is written to stdout. +.Sh SEE ALSO +.Xr hostname 7 diff --git a/source/sbase/hostname.c b/source/sbase/hostname.c new file mode 100644 index 00000000..a0e39698 --- /dev/null +++ b/source/sbase/hostname.c @@ -0,0 +1,33 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#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]; + + argv0 = argv[0], argc--, argv++; + + if (!argc) { + if (gethostname(host, sizeof(host)) < 0) + eprintf("gethostname:"); + puts(host); + } else if (argc == 1) { + if (sethostname(argv[0], strlen(argv[0])) < 0) + eprintf("sethostname:"); + } else { + usage(); + } + + return fshut(stdout, ""); +} diff --git a/source/sbase/join.1 b/source/sbase/join.1 new file mode 100644 index 00000000..5a01a423 --- /dev/null +++ b/source/sbase/join.1 @@ -0,0 +1,105 @@ +.Dd 2015-10-08 +.Dt JOIN 1 +.Os sbase +.Sh NAME +.Nm join +.Nd relational database operator +.Sh SYNOPSIS +.Nm +.Op Fl 1 Ar field +.Op Fl 2 Ar field +.Op Fl o Ar list +.Op Fl e Ar string +.Op Fl a Ar fileno | Fl v Ar fileno +.Op Fl t Ar delim +.Ar file1 file2 +.Sh DESCRIPTION +.Nm +lines from +.Ar file1 +and +.Ar file2 +on a matching field. If one of the input files is '-', standard input +is read for that file. +.Pp +Files are read sequentially and are assumed to be sorted on the join +field. +.Nm +does not check the order of input, and joining two unsorted files will +produce unexpected output. +.Pp +By default, input lines are matched on the first blank-separated +field; output lines are space-separated and consist of the join field +followed by the remaining fields from +.Ar file1 Ns , +then the remaining fields from +.Ar file2 Ns . +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl 1 Ar field +Join on the +.Ar field Ns th +field of file 1. +.It Fl 2 Ar field +Join on the +.Ar field Ns th +field of file 2. +.It Fl a Ar fileno +Print unpairable lines from file +.Ar fileno +in addition to normal output. +.It Fl e Ar string +When used with +.Fl o Ns , +replace empty fields in the output list with +.Ar string Ns . +.It Fl o Ar list +Format output according to the string +.Ar list Ns . +Each element of +.Ar list +may be either +.Ar fileno.field +or 0 (representing the join field). +Elements in +.Ar list +may be separated by blanks or commas. For example, +.Bd -literal -offset indent +join -o "0 2.1 1.3" +.Ed +.Pp +would print the join field, the first field of +.Ar file2 Ns , +then the third field of +.Ar file1 Ns . +.Pp +Only paired lines are formatted with the +.Fl o +option. Unpairable lines (selected with +.Fl a +or +.Fl v Ns ) +are printed raw. +.It Fl t Ar delim +Use the arbitrary string +.Ar delim +as field delimiter for both input and output. +.It Fl v Ar fileno +Print unpairable lines from file +.Ar fileno +instead of normal output. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification with the following exeption: +.Bl -bullet -offset indent +.It +Unpairable lines ignore formatting specified with +.Fl o Ns . +.El +.Pp +The possibility of specifying multibyte delimiters of arbitrary +length is an extension to the specification. diff --git a/source/sbase/join.c b/source/sbase/join.c new file mode 100644 index 00000000..d3e23439 --- /dev/null +++ b/source/sbase/join.c @@ -0,0 +1,529 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "text.h" +#include "utf.h" +#include "util.h" + +enum { + INIT = 1, + GROW = 2, +}; + +enum { + EXPAND = 0, + RESET = 1, +}; + +enum { FIELD_ERROR = -2, }; + +struct field { + char *s; + size_t len; +}; + +struct jline { + struct line text; + size_t nf; + size_t maxf; + struct field *fields; +}; + +struct spec { + size_t fileno; + size_t fldno; +}; + +struct outlist { + size_t ns; + size_t maxs; + struct spec **specs; +}; + +struct span { + size_t nl; + size_t maxl; + struct jline **lines; +}; + +static char *sep = NULL; +static char *replace = NULL; +static const char defaultofs = ' '; +static const int jfield = 1; /* POSIX default join field */ +static int unpairsa = 0, unpairsb = 0; +static int oflag = 0; +static int pairs = 1; +static size_t seplen; +static struct outlist output; + +static void +usage(void) +{ + eprintf("usage: %s [-1 field] [-2 field] [-o list] [-e string] " + "[-a | -v fileno] [-t delim] file1 file2\n", argv0); +} + +static void +prfield(struct field *fp) +{ + if (fwrite(fp->s, 1, fp->len, stdout) != fp->len) + eprintf("fwrite:"); +} + +static void +prsep(void) +{ + if (sep) + fwrite(sep, 1, seplen, stdout); + else + putchar(defaultofs); +} + +static void +swaplines(struct jline *la, struct jline *lb) +{ + struct jline tmp; + + tmp = *la; + *la = *lb; + *lb = tmp; +} + +static void +prjoin(struct jline *la, struct jline *lb, size_t jfa, size_t jfb) +{ + struct spec *sp; + struct field *joinfield; + size_t i; + + if (jfa >= la->nf || jfb >= lb->nf) + return; + + joinfield = &la->fields[jfa]; + + if (oflag) { + for (i = 0; i < output.ns; i++) { + sp = output.specs[i]; + + if (sp->fileno == 1) { + if (sp->fldno < la->nf) + prfield(&la->fields[sp->fldno]); + else if (replace) + fputs(replace, stdout); + } else if (sp->fileno == 2) { + if (sp->fldno < lb->nf) + prfield(&lb->fields[sp->fldno]); + else if (replace) + fputs(replace, stdout); + } else if (sp->fileno == 0) { + prfield(joinfield); + } + + if (i < output.ns - 1) + prsep(); + } + } else { + prfield(joinfield); + prsep(); + + for (i = 0; i < la->nf; i++) { + if (i != jfa) { + prfield(&la->fields[i]); + prsep(); + } + } + for (i = 0; i < lb->nf; i++) { + if (i != jfb) { + prfield(&lb->fields[i]); + if (i < lb->nf - 1) + prsep(); + } + } + } + putchar('\n'); +} + +static void +prline(struct jline *lp) +{ + if (fwrite(lp->text.data, 1, lp->text.len, stdout) != lp->text.len) + eprintf("fwrite:"); + putchar('\n'); +} + +static int +jlinecmp(struct jline *la, struct jline *lb, size_t jfa, size_t jfb) +{ + int status; + + /* return FIELD_ERROR if both lines are short */ + if (jfa >= la->nf) { + status = (jfb >= lb->nf) ? FIELD_ERROR : -1; + } else if (jfb >= lb->nf) { + status = 1; + } else { + status = memcmp(la->fields[jfa].s, lb->fields[jfb].s, + MAX(la->fields[jfa].len, lb->fields[jfb].len)); + LIMIT(status, -1, 1); + } + + return status; +} + +static void +addfield(struct jline *lp, char *sp, size_t len) +{ + if (lp->nf >= lp->maxf) { + lp->fields = ereallocarray(lp->fields, (GROW * lp->maxf), + sizeof(struct field)); + lp->maxf *= GROW; + } + lp->fields[lp->nf].s = sp; + lp->fields[lp->nf].len = len; + lp->nf++; +} + +static void +prspanjoin(struct span *spa, struct span *spb, size_t jfa, size_t jfb) +{ + size_t i, j; + + for (i = 0; i < (spa->nl - 1); i++) + for (j = 0; j < (spb->nl - 1); j++) + prjoin(spa->lines[i], spb->lines[j], jfa, jfb); +} + +static struct jline * +makeline(char *s, size_t len) +{ + struct jline *lp; + char *tmp; + size_t i, end; + + if (s[len - 1] == '\n') + s[--len] = '\0'; + + lp = ereallocarray(NULL, INIT, sizeof(struct jline)); + lp->text.data = s; + lp->text.len = len; + lp->fields = ereallocarray(NULL, INIT, sizeof(struct field)); + lp->nf = 0; + lp->maxf = INIT; + + for (i = 0; i < lp->text.len && isblank(lp->text.data[i]); i++) + ; + while (i < lp->text.len) { + if (sep) { + if ((lp->text.len - i) < seplen || + !(tmp = memmem(lp->text.data + i, + lp->text.len - i, sep, seplen))) { + goto eol; + } + end = tmp - lp->text.data; + addfield(lp, lp->text.data + i, end - i); + i = end + seplen; + } else { + for (end = i; !(isblank(lp->text.data[end])); end++) { + if (end + 1 == lp->text.len) + goto eol; + } + addfield(lp, lp->text.data + i, end - i); + for (i = end; isblank(lp->text.data[i]); i++) + ; + } + } +eol: + addfield(lp, lp->text.data + i, lp->text.len - i); + + return lp; +} + +static int +addtospan(struct span *sp, FILE *fp, int reset) +{ + char *newl = NULL; + ssize_t len; + size_t size = 0; + + if ((len = getline(&newl, &size, fp)) < 0) { + if (ferror(fp)) + eprintf("getline:"); + else + return 0; + } + + if (reset) + sp->nl = 0; + + if (sp->nl >= sp->maxl) { + sp->lines = ereallocarray(sp->lines, (GROW * sp->maxl), + sizeof(struct jline *)); + sp->maxl *= GROW; + } + + sp->lines[sp->nl] = makeline(newl, len); + sp->nl++; + return 1; +} + +static void +initspan(struct span *sp) +{ + sp->nl = 0; + sp->maxl = INIT; + sp->lines = ereallocarray(NULL, INIT, sizeof(struct jline *)); +} + +static void +freespan(struct span *sp) +{ + size_t i; + + for (i = 0; i < sp->nl; i++) { + free(sp->lines[i]->fields); + free(sp->lines[i]->text.data); + } + free(sp->lines); +} + +static void +initolist(struct outlist *olp) +{ + olp->ns = 0; + olp->maxs = 1; + olp->specs = ereallocarray(NULL, INIT, sizeof(struct spec *)); +} + +static void +addspec(struct outlist *olp, struct spec *sp) +{ + if (olp->ns >= olp->maxs) { + olp->specs = ereallocarray(olp->specs, (GROW * olp->maxs), + sizeof(struct spec *)); + olp->maxs *= GROW; + } + olp->specs[olp->ns] = sp; + olp->ns++; +} + +static struct spec * +makespec(char *s) +{ + struct spec *sp; + int fileno; + size_t fldno; + + if (!strcmp(s, "0")) { /* join field must be 0 and nothing else */ + fileno = 0; + fldno = 0; + } else if ((s[0] == '1' || s[0] == '2') && s[1] == '.') { + fileno = s[0] - '0'; + fldno = estrtonum(&s[2], 1, MIN(LLONG_MAX, SIZE_MAX)) - 1; + } else { + eprintf("%s: invalid format\n", s); + } + + sp = ereallocarray(NULL, INIT, sizeof(struct spec)); + sp->fileno = fileno; + sp->fldno = fldno; + return sp; +} + +static void +makeolist(struct outlist *olp, char *s) +{ + char *item, *sp; + sp = s; + + while (sp) { + item = sp; + sp = strpbrk(sp, ", \t"); + if (sp) + *sp++ = '\0'; + addspec(olp, makespec(item)); + } +} + +static void +freespecs(struct outlist *olp) +{ + size_t i; + + for (i = 0; i < olp->ns; i++) + free(olp->specs[i]); +} + +static void +join(FILE *fa, FILE *fb, size_t jfa, size_t jfb) +{ + struct span spa, spb; + int cmp, eofa, eofb; + + initspan(&spa); + initspan(&spb); + cmp = eofa = eofb = 0; + + addtospan(&spa, fa, RESET); + addtospan(&spb, fb, RESET); + + while (spa.nl && spb.nl) { + if ((cmp = jlinecmp(spa.lines[0], spb.lines[0], jfa, jfb)) < 0) { + if (unpairsa) + prline(spa.lines[0]); + if (!addtospan(&spa, fa, RESET)) { + if (unpairsb) { /* a is EOF'd; print the rest of b */ + do + prline(spb.lines[0]); + while (addtospan(&spb, fb, RESET)); + } + eofa = eofb = 1; + } else { + continue; + } + } else if (cmp > 0) { + if (unpairsb) + prline(spb.lines[0]); + if (!addtospan(&spb, fb, RESET)) { + if (unpairsa) { /* b is EOF'd; print the rest of a */ + do + prline(spa.lines[0]); + while (addtospan(&spa, fa, RESET)); + } + eofa = eofb = 1; + } else { + continue; + } + } else if (cmp == 0) { + /* read all consecutive matching lines from a */ + do { + if (!addtospan(&spa, fa, EXPAND)) { + eofa = 1; + spa.nl++; + break; + } + } while (jlinecmp(spa.lines[spa.nl-1], spb.lines[0], jfa, jfb) == 0); + + /* read all consecutive matching lines from b */ + do { + if (!addtospan(&spb, fb, EXPAND)) { + eofb = 1; + spb.nl++; + break; + } + } while (jlinecmp(spa.lines[0], spb.lines[spb.nl-1], jfa, jfb) == 0); + + if (pairs) + prspanjoin(&spa, &spb, jfa, jfb); + + } else { /* FIELD_ERROR: both lines lacked join fields */ + if (unpairsa) + prline(spa.lines[0]); + if (unpairsb) + prline(spb.lines[0]); + eofa = addtospan(&spa, fa, RESET) ? 0 : 1; + eofb = addtospan(&spb, fb, RESET) ? 0 : 1; + if (!eofa && !eofb) + continue; + } + + if (eofa) { + spa.nl = 0; + } else { + swaplines(spa.lines[0], spa.lines[spa.nl - 1]); /* ugly */ + spa.nl = 1; + } + + if (eofb) { + spb.nl = 0; + } else { + swaplines(spb.lines[0], spb.lines[spb.nl - 1]); /* ugly */ + spb.nl = 1; + } + } + freespan(&spa); + freespan(&spb); +} + + +int +main(int argc, char *argv[]) +{ + size_t jf[2] = { jfield, jfield, }; + FILE *fp[2]; + int ret = 0, n; + char *fno; + + ARGBEGIN { + case '1': + jf[0] = estrtonum(EARGF(usage()), 1, MIN(LLONG_MAX, SIZE_MAX)); + break; + case '2': + jf[1] = estrtonum(EARGF(usage()), 1, MIN(LLONG_MAX, SIZE_MAX)); + break; + case 'a': + fno = EARGF(usage()); + if (strcmp(fno, "1") == 0) + unpairsa = 1; + else if (strcmp(fno, "2") == 0) + unpairsb = 1; + else + usage(); + break; + case 'e': + replace = EARGF(usage()); + break; + case 'o': + oflag = 1; + initolist(&output); + makeolist(&output, EARGF(usage())); + break; + case 't': + sep = EARGF(usage()); + break; + case 'v': + pairs = 0; + fno = EARGF(usage()); + if (strcmp(fno, "1") == 0) + unpairsa = 1; + else if (strcmp(fno, "2") == 0) + unpairsb = 1; + else + usage(); + break; + default: + usage(); + } ARGEND + + if (sep) + seplen = unescape(sep); + + if (argc != 2) + usage(); + + for (n = 0; n < 2; n++) { + if (!strcmp(argv[n], "-")) { + argv[n] = ""; + fp[n] = stdin; + } else if (!(fp[n] = fopen(argv[n], "r"))) { + eprintf("fopen %s:", argv[n]); + } + } + + jf[0]--; + jf[1]--; + + join(fp[0], fp[1], jf[0], jf[1]); + + if (oflag) + freespecs(&output); + + if (fshut(fp[0], argv[0]) | (fp[0] != fp[1] && fshut(fp[1], argv[1])) | + fshut(stdout, "")) + ret = 2; + + return ret; +} diff --git a/source/sbase/kill.1 b/source/sbase/kill.1 new file mode 100644 index 00000000..47f99152 --- /dev/null +++ b/source/sbase/kill.1 @@ -0,0 +1,47 @@ +.Dd 2015-10-08 +.Dt KILL 1 +.Os sbase +.Sh NAME +.Nm kill +.Nd signal processes +.Sh SYNOPSIS +.Nm +.Op Fl s Ar signame | Fl num | Fl signame +.Ar pid ... +.Nm +.Fl l Op Ar num +.Sh DESCRIPTION +.Nm +signals TERM to each process or process group specified by +.Ar pid . +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl l Op Ar num +List all available signals or the signal name of +.Ar num . +.It Fl s Ar signame | Fl num | Fl signame +Send signal corresponding to +.Ar signame +| +.Ar num . +The default is TERM. +.El +.Sh SEE ALSO +.Xr kill 2 , +.Xr signal 7 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The +.Fl Ar signame +and +.Fl Ar num +syntax is marked by +.St -p1003.1-2013 +as an +X/OPEN System Interfaces +option. diff --git a/source/sbase/kill.c b/source/sbase/kill.c new file mode 100644 index 00000000..f7dd132d --- /dev/null +++ b/source/sbase/kill.c @@ -0,0 +1,129 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include +#include +#include +#include + +#include "util.h" + +struct { + const char *name; + const 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(const 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); + + return NULL; /* not reached */ +} + +int +name2sig(const char *name) +{ + size_t i; + + for (i = 0; i < LEN(sigs); i++) + if (!strcasecmp(sigs[i].name, name)) + return sigs[i].sig; + eprintf("%s: bad signal name\n", name); + + return -1; /* not reached */ +} + +static void +usage(void) +{ + eprintf("usage: %s [-s signame | -num | -signame] pid ...\n" + " %s -l [num]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + pid_t pid; + size_t i; + int ret = 0, sig = SIGTERM; + + argv0 = argv[0], argc--, argv++; + if (!argc) + usage(); + + if ((*argv)[0] == '-') { + switch ((*argv)[1]) { + case 'l': + if ((*argv)[2]) + goto longopt; + argc--, argv++; + if (!argc) { + for (i = 0; i < LEN(sigs); i++) + puts(sigs[i].name); + } else if (argc == 1) { + sig = estrtonum(*argv, 0, INT_MAX); + if (sig > 128) + sig = WTERMSIG(sig); + puts(sig2name(sig)); + } else { + usage(); + } + return fshut(stdout, ""); + case 's': + if ((*argv)[2]) + goto longopt; + argc--, argv++; + if (!argc) + usage(); + sig = name2sig(*argv); + argc--, argv++; + break; + case '-': + if ((*argv)[2]) + goto longopt; + argc--, argv++; + break; + default: + longopt: + /* XSI-extensions -argnum and -argname*/ + if (isdigit((*argv)[1])) { + sig = estrtonum((*argv) + 1, 0, INT_MAX); + sig2name(sig); + } else { + sig = name2sig((*argv) + 1); + } + argc--, argv++; + } + } + + if (argc && !strcmp(*argv, "--")) + argc--, argv++; + + if (!argc) + usage(); + + for (; *argv; argc--, argv++) { + pid = estrtonum(*argv, INT_MIN, INT_MAX); + if (kill(pid, sig) < 0) { + weprintf("kill %d:", pid); + ret = 1; + } + } + + return ret; +} diff --git a/source/sbase/libutf/Makefile b/source/sbase/libutf/Makefile new file mode 100644 index 00000000..aac2d2e6 --- /dev/null +++ b/source/sbase/libutf/Makefile @@ -0,0 +1,6 @@ +AWK = awk +UNICODE = http://unicode.org/Public/UCD/latest/ucd/UnicodeData.txt + +default: + @echo Downloading and parsing $(UNICODE) + @curl -\# $(UNICODE) | $(AWK) -f mkrunetype.awk diff --git a/source/sbase/libutf/fgetrune.c b/source/sbase/libutf/fgetrune.c new file mode 100644 index 00000000..8cd78c64 --- /dev/null +++ b/source/sbase/libutf/fgetrune.c @@ -0,0 +1,36 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "../utf.h" + +int +fgetrune(Rune *r, FILE *fp) +{ + char buf[UTFmax]; + int i = 0, c; + + while (i < UTFmax && (c = fgetc(fp)) != EOF) { + buf[i++] = c; + if (charntorune(r, buf, i) > 0) + break; + } + if (ferror(fp)) + return -1; + + return i; +} + +int +efgetrune(Rune *r, FILE *fp, const char *file) +{ + int ret; + + if ((ret = fgetrune(r, fp)) < 0) { + fprintf(stderr, "fgetrune %s: %s\n", file, strerror(errno)); + exit(1); + } + return ret; +} diff --git a/source/sbase/libutf/fputrune.c b/source/sbase/libutf/fputrune.c new file mode 100644 index 00000000..6a393b5a --- /dev/null +++ b/source/sbase/libutf/fputrune.c @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "../utf.h" + +int +fputrune(const Rune *r, FILE *fp) +{ + char buf[UTFmax]; + + return fwrite(buf, runetochar(buf, r), 1, fp); +} + +int +efputrune(const Rune *r, FILE *fp, const char *file) +{ + int ret; + + if ((ret = fputrune(r, fp)) < 0) { + fprintf(stderr, "fputrune %s: %s\n", file, strerror(errno)); + exit(1); + } + return ret; +} diff --git a/source/sbase/libutf/isalnumrune.c b/source/sbase/libutf/isalnumrune.c new file mode 100644 index 00000000..e4720d00 --- /dev/null +++ b/source/sbase/libutf/isalnumrune.c @@ -0,0 +1,9 @@ +/* Automatically generated by mkrunetype.awk */ +#include "../utf.h" +#include "runetype.h" + +int +isalnumrune(Rune r) +{ + return isalpharune(r) || isdigitrune(r); +} diff --git a/source/sbase/libutf/isalpharune.c b/source/sbase/libutf/isalpharune.c new file mode 100644 index 00000000..453be235 --- /dev/null +++ b/source/sbase/libutf/isalpharune.c @@ -0,0 +1,679 @@ +/* Automatically generated by mkrunetype.awk */ +#include + +#include "../utf.h" +#include "runetype.h" + +static Rune alpha3[][2] = { + { 0x00D6, 0x00D8 }, + { 0x00F6, 0x00F8 }, + { 0x02EC, 0x02EE }, + { 0x0374, 0x0376 }, + { 0x037D, 0x037F }, + { 0x0386, 0x0388 }, + { 0x038A, 0x038E }, + { 0x03A1, 0x03A3 }, + { 0x03F5, 0x03F7 }, + { 0x052F, 0x0531 }, + { 0x066F, 0x0671 }, + { 0x06D3, 0x06D5 }, + { 0x0710, 0x0712 }, + { 0x09A8, 0x09AA }, + { 0x09B0, 0x09B2 }, + { 0x09DD, 0x09DF }, + { 0x0A28, 0x0A2A }, + { 0x0A30, 0x0A32 }, + { 0x0A33, 0x0A35 }, + { 0x0A36, 0x0A38 }, + { 0x0A5C, 0x0A5E }, + { 0x0A8D, 0x0A8F }, + { 0x0A91, 0x0A93 }, + { 0x0AA8, 0x0AAA }, + { 0x0AB0, 0x0AB2 }, + { 0x0AB3, 0x0AB5 }, + { 0x0B28, 0x0B2A }, + { 0x0B30, 0x0B32 }, + { 0x0B33, 0x0B35 }, + { 0x0B5D, 0x0B5F }, + { 0x0B83, 0x0B85 }, + { 0x0B90, 0x0B92 }, + { 0x0B9A, 0x0B9E }, + { 0x0C0C, 0x0C0E }, + { 0x0C10, 0x0C12 }, + { 0x0C28, 0x0C2A }, + { 0x0C8C, 0x0C8E }, + { 0x0C90, 0x0C92 }, + { 0x0CA8, 0x0CAA }, + { 0x0CB3, 0x0CB5 }, + { 0x0CDE, 0x0CE0 }, + { 0x0D0C, 0x0D0E }, + { 0x0D10, 0x0D12 }, + { 0x0DB1, 0x0DB3 }, + { 0x0DBB, 0x0DBD }, + { 0x0E30, 0x0E32 }, + { 0x0E82, 0x0E84 }, + { 0x0E88, 0x0E8A }, + { 0x0E97, 0x0E99 }, + { 0x0E9F, 0x0EA1 }, + { 0x0EA3, 0x0EA7 }, + { 0x0EAB, 0x0EAD }, + { 0x0EB0, 0x0EB2 }, + { 0x0EC4, 0x0EC6 }, + { 0x0F47, 0x0F49 }, + { 0x10C5, 0x10C7 }, + { 0x10FA, 0x10FC }, + { 0x1248, 0x124A }, + { 0x1256, 0x125A }, + { 0x1288, 0x128A }, + { 0x12B0, 0x12B2 }, + { 0x12BE, 0x12C2 }, + { 0x12D6, 0x12D8 }, + { 0x1310, 0x1312 }, + { 0x167F, 0x1681 }, + { 0x170C, 0x170E }, + { 0x176C, 0x176E }, + { 0x18A8, 0x18AA }, + { 0x1CEC, 0x1CEE }, + { 0x1F57, 0x1F5F }, + { 0x1FB4, 0x1FB6 }, + { 0x1FBC, 0x1FBE }, + { 0x1FC4, 0x1FC6 }, + { 0x1FF4, 0x1FF6 }, + { 0x2113, 0x2115 }, + { 0x2124, 0x212A }, + { 0x212D, 0x212F }, + { 0x2C2E, 0x2C30 }, + { 0x2C5E, 0x2C60 }, + { 0x2D25, 0x2D27 }, + { 0x2DA6, 0x2DA8 }, + { 0x2DAE, 0x2DB0 }, + { 0x2DB6, 0x2DB8 }, + { 0x2DBE, 0x2DC0 }, + { 0x2DC6, 0x2DC8 }, + { 0x2DCE, 0x2DD0 }, + { 0x2DD6, 0x2DD8 }, + { 0x309F, 0x30A1 }, + { 0x30FA, 0x30FC }, + { 0xA78E, 0xA790 }, + { 0xA801, 0xA803 }, + { 0xA805, 0xA807 }, + { 0xA80A, 0xA80C }, + { 0xA9E4, 0xA9E6 }, + { 0xA9FE, 0xAA00 }, + { 0xAA42, 0xAA44 }, + { 0xAAAF, 0xAAB1 }, + { 0xAAC0, 0xAAC2 }, + { 0xAB26, 0xAB28 }, + { 0xAB2E, 0xAB30 }, + { 0xAB5A, 0xAB5C }, + { 0xFB1D, 0xFB1F }, + { 0xFB28, 0xFB2A }, + { 0xFB36, 0xFB38 }, + { 0xFB3C, 0xFB40 }, + { 0xFB41, 0xFB43 }, + { 0xFB44, 0xFB46 }, + { 0xFE74, 0xFE76 }, + { 0x1000B, 0x1000D }, + { 0x10026, 0x10028 }, + { 0x1003A, 0x1003C }, + { 0x1003D, 0x1003F }, + { 0x10340, 0x10342 }, + { 0x10808, 0x1080A }, + { 0x10835, 0x10837 }, + { 0x10A13, 0x10A15 }, + { 0x10A17, 0x10A19 }, + { 0x10AC7, 0x10AC9 }, + { 0x11211, 0x11213 }, + { 0x11328, 0x1132A }, + { 0x11330, 0x11332 }, + { 0x11333, 0x11335 }, + { 0x114C5, 0x114C7 }, + { 0x1D454, 0x1D456 }, + { 0x1D49C, 0x1D49E }, + { 0x1D4AC, 0x1D4AE }, + { 0x1D4B9, 0x1D4BD }, + { 0x1D4C3, 0x1D4C5 }, + { 0x1D505, 0x1D507 }, + { 0x1D514, 0x1D516 }, + { 0x1D51C, 0x1D51E }, + { 0x1D539, 0x1D53B }, + { 0x1D53E, 0x1D540 }, + { 0x1D544, 0x1D546 }, + { 0x1D550, 0x1D552 }, + { 0x1D6C0, 0x1D6C2 }, + { 0x1D6DA, 0x1D6DC }, + { 0x1D6FA, 0x1D6FC }, + { 0x1D714, 0x1D716 }, + { 0x1D734, 0x1D736 }, + { 0x1D74E, 0x1D750 }, + { 0x1D76E, 0x1D770 }, + { 0x1D788, 0x1D78A }, + { 0x1D7A8, 0x1D7AA }, + { 0x1D7C2, 0x1D7C4 }, + { 0x1EE03, 0x1EE05 }, + { 0x1EE1F, 0x1EE21 }, + { 0x1EE22, 0x1EE24 }, + { 0x1EE27, 0x1EE29 }, + { 0x1EE32, 0x1EE34 }, + { 0x1EE37, 0x1EE3B }, + { 0x1EE47, 0x1EE4D }, + { 0x1EE4F, 0x1EE51 }, + { 0x1EE52, 0x1EE54 }, + { 0x1EE57, 0x1EE61 }, + { 0x1EE62, 0x1EE64 }, + { 0x1EE6A, 0x1EE6C }, + { 0x1EE72, 0x1EE74 }, + { 0x1EE77, 0x1EE79 }, + { 0x1EE7C, 0x1EE80 }, + { 0x1EE89, 0x1EE8B }, + { 0x1EEA3, 0x1EEA5 }, + { 0x1EEA9, 0x1EEAB }, +}; + +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, 0x052F }, + { 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 }, + { 0x08A0, 0x08B2 }, + { 0x0904, 0x0939 }, + { 0x0958, 0x0961 }, + { 0x0971, 0x0980 }, + { 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, 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 }, + { 0x16F1, 0x16F8 }, + { 0x1700, 0x170C }, + { 0x170E, 0x1711 }, + { 0x1720, 0x1731 }, + { 0x1740, 0x1751 }, + { 0x1760, 0x176C }, + { 0x176E, 0x1770 }, + { 0x1780, 0x17B3 }, + { 0x1820, 0x1877 }, + { 0x1880, 0x18A8 }, + { 0x18B0, 0x18F5 }, + { 0x1900, 0x191E }, + { 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, 0xA69D }, + { 0xA6A0, 0xA6E5 }, + { 0xA717, 0xA71F }, + { 0xA722, 0xA788 }, + { 0xA78B, 0xA78E }, + { 0xA790, 0xA7AD }, + { 0xA7B0, 0xA7B1 }, + { 0xA7F7, 0xA801 }, + { 0xA803, 0xA805 }, + { 0xA807, 0xA80A }, + { 0xA80C, 0xA822 }, + { 0xA840, 0xA873 }, + { 0xA882, 0xA8B3 }, + { 0xA8F2, 0xA8F7 }, + { 0xA90A, 0xA925 }, + { 0xA930, 0xA946 }, + { 0xA960, 0xA97C }, + { 0xA984, 0xA9B2 }, + { 0xA9E0, 0xA9E4 }, + { 0xA9E6, 0xA9EF }, + { 0xA9FA, 0xA9FE }, + { 0xAA00, 0xAA28 }, + { 0xAA40, 0xAA42 }, + { 0xAA44, 0xAA4B }, + { 0xAA60, 0xAA76 }, + { 0xAA7E, 0xAAAF }, + { 0xAAB5, 0xAAB6 }, + { 0xAAB9, 0xAABD }, + { 0xAADB, 0xAADD }, + { 0xAAE0, 0xAAEA }, + { 0xAAF2, 0xAAF4 }, + { 0xAB01, 0xAB06 }, + { 0xAB09, 0xAB0E }, + { 0xAB11, 0xAB16 }, + { 0xAB20, 0xAB26 }, + { 0xAB28, 0xAB2E }, + { 0xAB30, 0xAB5A }, + { 0xAB5C, 0xAB5F }, + { 0xAB64, 0xAB65 }, + { 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, 0x1031F }, + { 0x10330, 0x10340 }, + { 0x10342, 0x10349 }, + { 0x10350, 0x10375 }, + { 0x10380, 0x1039D }, + { 0x103A0, 0x103C3 }, + { 0x103C8, 0x103CF }, + { 0x10400, 0x1049D }, + { 0x10500, 0x10527 }, + { 0x10530, 0x10563 }, + { 0x10600, 0x10736 }, + { 0x10740, 0x10755 }, + { 0x10760, 0x10767 }, + { 0x10800, 0x10805 }, + { 0x1080A, 0x10835 }, + { 0x10837, 0x10838 }, + { 0x1083F, 0x10855 }, + { 0x10860, 0x10876 }, + { 0x10880, 0x1089E }, + { 0x10900, 0x10915 }, + { 0x10920, 0x10939 }, + { 0x10980, 0x109B7 }, + { 0x109BE, 0x109BF }, + { 0x10A10, 0x10A13 }, + { 0x10A15, 0x10A17 }, + { 0x10A19, 0x10A33 }, + { 0x10A60, 0x10A7C }, + { 0x10A80, 0x10A9C }, + { 0x10AC0, 0x10AC7 }, + { 0x10AC9, 0x10AE4 }, + { 0x10B00, 0x10B35 }, + { 0x10B40, 0x10B55 }, + { 0x10B60, 0x10B72 }, + { 0x10B80, 0x10B91 }, + { 0x10C00, 0x10C48 }, + { 0x11003, 0x11037 }, + { 0x11083, 0x110AF }, + { 0x110D0, 0x110E8 }, + { 0x11103, 0x11126 }, + { 0x11150, 0x11172 }, + { 0x11183, 0x111B2 }, + { 0x111C1, 0x111C4 }, + { 0x11200, 0x11211 }, + { 0x11213, 0x1122B }, + { 0x112B0, 0x112DE }, + { 0x11305, 0x1130C }, + { 0x1130F, 0x11310 }, + { 0x11313, 0x11328 }, + { 0x1132A, 0x11330 }, + { 0x11332, 0x11333 }, + { 0x11335, 0x11339 }, + { 0x1135D, 0x11361 }, + { 0x11480, 0x114AF }, + { 0x114C4, 0x114C5 }, + { 0x11580, 0x115AE }, + { 0x11600, 0x1162F }, + { 0x11680, 0x116AA }, + { 0x118A0, 0x118DF }, + { 0x11AC0, 0x11AF8 }, + { 0x12000, 0x12398 }, + { 0x13000, 0x1342E }, + { 0x16800, 0x16A38 }, + { 0x16A40, 0x16A5E }, + { 0x16AD0, 0x16AED }, + { 0x16B00, 0x16B2F }, + { 0x16B40, 0x16B43 }, + { 0x16B63, 0x16B77 }, + { 0x16B7D, 0x16B8F }, + { 0x16F00, 0x16F44 }, + { 0x16F93, 0x16F9F }, + { 0x1B000, 0x1B001 }, + { 0x1BC00, 0x1BC6A }, + { 0x1BC70, 0x1BC7C }, + { 0x1BC80, 0x1BC88 }, + { 0x1BC90, 0x1BC99 }, + { 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 }, + { 0x1E800, 0x1E8C4 }, + { 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, + 0x0559, + 0x06FF, + 0x07B1, + 0x07FA, + 0x081A, + 0x0824, + 0x0828, + 0x093D, + 0x0950, + 0x09BD, + 0x09CE, + 0x0ABD, + 0x0AD0, + 0x0B3D, + 0x0B71, + 0x0BD0, + 0x0C3D, + 0x0CBD, + 0x0D3D, + 0x0D4E, + 0x0E8D, + 0x0EBD, + 0x0F00, + 0x103F, + 0x1061, + 0x108E, + 0x10CD, + 0x17D7, + 0x17DC, + 0x1AA7, + 0x2071, + 0x207F, + 0x2102, + 0x2107, + 0x214E, + 0x2D2D, + 0x2D6F, + 0x2E2F, + 0x3400, + 0x4DB5, + 0x4E00, + 0x9FCC, + 0xA8FB, + 0xA9CF, + 0xAA7A, + 0xAC00, + 0xD7A3, + 0x1083C, + 0x10A00, + 0x11176, + 0x111DA, + 0x1133D, + 0x11644, + 0x118FF, + 0x16F50, + 0x1D4A2, + 0x1EE42, + 0x20000, + 0x2A6D6, + 0x2A700, + 0x2B734, + 0x2B740, + 0x2B81D, +}; + +int +isalpharune(Rune r) +{ + Rune *match; + + if((match = bsearch(&r, alpha3, nelem(alpha3), sizeof *alpha3, &rune2cmp))) + return !((r - match[0]) % 2); + if(bsearch(&r, alpha2, nelem(alpha2), sizeof *alpha2, &rune2cmp)) + return 1; + if(bsearch(&r, alpha1, nelem(alpha1), sizeof *alpha1, &rune1cmp)) + return 1; + return 0; +} diff --git a/source/sbase/libutf/isblankrune.c b/source/sbase/libutf/isblankrune.c new file mode 100644 index 00000000..7cf91597 --- /dev/null +++ b/source/sbase/libutf/isblankrune.c @@ -0,0 +1,9 @@ +/* Automatically generated by mkrunetype.awk */ +#include "../utf.h" +#include "runetype.h" + +int +isblankrune(Rune r) +{ + return r == ' ' || r == '\t'; +} diff --git a/source/sbase/libutf/iscntrlrune.c b/source/sbase/libutf/iscntrlrune.c new file mode 100644 index 00000000..286dce83 --- /dev/null +++ b/source/sbase/libutf/iscntrlrune.c @@ -0,0 +1,18 @@ +/* Automatically generated by mkrunetype.awk */ +#include + +#include "../utf.h" +#include "runetype.h" + +static Rune cntrl2[][2] = { + { 0x0000, 0x001F }, + { 0x007F, 0x009F }, +}; + +int +iscntrlrune(Rune r) +{ + if(bsearch(&r, cntrl2, nelem(cntrl2), sizeof *cntrl2, &rune2cmp)) + return 1; + return 0; +} diff --git a/source/sbase/libutf/isdigitrune.c b/source/sbase/libutf/isdigitrune.c new file mode 100644 index 00000000..0dfb0787 --- /dev/null +++ b/source/sbase/libutf/isdigitrune.c @@ -0,0 +1,66 @@ +/* Automatically generated by mkrunetype.awk */ +#include + +#include "../utf.h" +#include "runetype.h" + +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 }, + { 0x0DE6, 0x0DEF }, + { 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 }, + { 0xA9F0, 0xA9F9 }, + { 0xAA50, 0xAA59 }, + { 0xABF0, 0xABF9 }, + { 0xFF10, 0xFF19 }, + { 0x104A0, 0x104A9 }, + { 0x11066, 0x1106F }, + { 0x110F0, 0x110F9 }, + { 0x11136, 0x1113F }, + { 0x111D0, 0x111D9 }, + { 0x112F0, 0x112F9 }, + { 0x114D0, 0x114D9 }, + { 0x11650, 0x11659 }, + { 0x116C0, 0x116C9 }, + { 0x118E0, 0x118E9 }, + { 0x16A60, 0x16A69 }, + { 0x16B50, 0x16B59 }, + { 0x1D7CE, 0x1D7FF }, +}; + +int +isdigitrune(Rune r) +{ + if(bsearch(&r, digit2, nelem(digit2), sizeof *digit2, &rune2cmp)) + return 1; + return 0; +} diff --git a/source/sbase/libutf/isgraphrune.c b/source/sbase/libutf/isgraphrune.c new file mode 100644 index 00000000..08770f64 --- /dev/null +++ b/source/sbase/libutf/isgraphrune.c @@ -0,0 +1,9 @@ +/* Automatically generated by mkrunetype.awk */ +#include "../utf.h" +#include "runetype.h" + +int +isgraphrune(Rune r) +{ + return !isspacerune(r) && isprintrune(r); +} diff --git a/source/sbase/libutf/isprintrune.c b/source/sbase/libutf/isprintrune.c new file mode 100644 index 00000000..f6e2fa48 --- /dev/null +++ b/source/sbase/libutf/isprintrune.c @@ -0,0 +1,10 @@ +/* Automatically generated by mkrunetype.awk */ +#include "../utf.h" +#include "runetype.h" + +int +isprintrune(Rune r) +{ + return !iscntrlrune(r) && (r != 0x2028) && (r != 0x2029) && + ((r < 0xFFF9) || (r > 0xFFFB)); +} diff --git a/source/sbase/libutf/ispunctrune.c b/source/sbase/libutf/ispunctrune.c new file mode 100644 index 00000000..d73cb25b --- /dev/null +++ b/source/sbase/libutf/ispunctrune.c @@ -0,0 +1,9 @@ +/* Automatically generated by mkrunetype.awk */ +#include "../utf.h" +#include "runetype.h" + +int +ispunctrune(Rune r) +{ + return isgraphrune(r) && !isalnumrune(r); +} diff --git a/source/sbase/libutf/isspacerune.c b/source/sbase/libutf/isspacerune.c new file mode 100644 index 00000000..bb8fe28a --- /dev/null +++ b/source/sbase/libutf/isspacerune.c @@ -0,0 +1,31 @@ +/* Automatically generated by mkrunetype.awk */ +#include + +#include "../utf.h" +#include "runetype.h" + +static Rune space2[][2] = { + { 0x0009, 0x000D }, + { 0x001C, 0x0020 }, + { 0x2000, 0x200A }, + { 0x2028, 0x2029 }, +}; + +static Rune space1[] = { + 0x0085, + 0x00A0, + 0x1680, + 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; +} diff --git a/source/sbase/libutf/istitlerune.c b/source/sbase/libutf/istitlerune.c new file mode 100644 index 00000000..211a4aa6 --- /dev/null +++ b/source/sbase/libutf/istitlerune.c @@ -0,0 +1,31 @@ +/* Automatically generated by mkrunetype.awk */ +#include + +#include "../utf.h" +#include "runetype.h" + +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; +} diff --git a/source/sbase/libutf/isxdigitrune.c b/source/sbase/libutf/isxdigitrune.c new file mode 100644 index 00000000..0797240a --- /dev/null +++ b/source/sbase/libutf/isxdigitrune.c @@ -0,0 +1,9 @@ +/* Automatically generated by mkrunetype.awk */ +#include "../utf.h" +#include "runetype.h" + +int +isxdigitrune(Rune r) +{ + return (r >= '0' && (r - '0') < 10) || (r >= 'a' && (r - 'a') < 6); +} diff --git a/source/sbase/libutf/lowerrune.c b/source/sbase/libutf/lowerrune.c new file mode 100644 index 00000000..df49f672 --- /dev/null +++ b/source/sbase/libutf/lowerrune.c @@ -0,0 +1,317 @@ +/* Automatically generated by mkrunetype.awk */ +#include + +#include "../utf.h" +#include "runetype.h" + +static Rune lower4[][2] = { + { 0x0101, 0x012F }, + { 0x0133, 0x0137 }, + { 0x013A, 0x0148 }, + { 0x014B, 0x0177 }, + { 0x017A, 0x017E }, + { 0x0183, 0x0185 }, + { 0x01A1, 0x01A5 }, + { 0x01B4, 0x01B6 }, + { 0x01CE, 0x01DC }, + { 0x01DF, 0x01EF }, + { 0x01F9, 0x021F }, + { 0x0223, 0x0233 }, + { 0x0247, 0x024F }, + { 0x0371, 0x0373 }, + { 0x03D9, 0x03EF }, + { 0x0461, 0x0481 }, + { 0x048B, 0x04BF }, + { 0x04C2, 0x04CE }, + { 0x04D1, 0x052F }, + { 0x1E01, 0x1E95 }, + { 0x1EA1, 0x1EFF }, + { 0x2C68, 0x2C6C }, + { 0x2C81, 0x2CE3 }, + { 0x2CEC, 0x2CEE }, + { 0xA641, 0xA66D }, + { 0xA681, 0xA69B }, + { 0xA723, 0xA72F }, + { 0xA733, 0xA76F }, + { 0xA77A, 0xA77C }, + { 0xA77F, 0xA787 }, + { 0xA791, 0xA793 }, + { 0xA797, 0xA7A9 }, +}; + +static Rune lower2[][3] = { + { 0x0061, 0x007A, 0x0041 }, + { 0x00E0, 0x00F6, 0x00C0 }, + { 0x00F8, 0x00FE, 0x00D8 }, + { 0x01AA, 0x01AB, 0x01AA }, + { 0x0234, 0x0239, 0x0234 }, + { 0x023F, 0x0240, 0x2C7E }, + { 0x0256, 0x0257, 0x0189 }, + { 0x025D, 0x025F, 0x025D }, + { 0x026D, 0x026E, 0x026D }, + { 0x0273, 0x0274, 0x0273 }, + { 0x0276, 0x027C, 0x0276 }, + { 0x027E, 0x027F, 0x027E }, + { 0x0281, 0x0282, 0x0281 }, + { 0x0284, 0x0286, 0x0284 }, + { 0x028A, 0x028B, 0x01B1 }, + { 0x028D, 0x0291, 0x028D }, + { 0x0295, 0x029D, 0x0295 }, + { 0x029F, 0x02AF, 0x029F }, + { 0x037B, 0x037D, 0x03FD }, + { 0x03AD, 0x03AF, 0x0388 }, + { 0x03B1, 0x03C1, 0x0391 }, + { 0x03C3, 0x03CB, 0x03A3 }, + { 0x03CD, 0x03CE, 0x038E }, + { 0x0430, 0x044F, 0x0410 }, + { 0x0450, 0x045F, 0x0400 }, + { 0x0561, 0x0586, 0x0531 }, + { 0x1D00, 0x1D2B, 0x1D00 }, + { 0x1D6B, 0x1D77, 0x1D6B }, + { 0x1D7A, 0x1D7C, 0x1D7A }, + { 0x1D7E, 0x1D9A, 0x1D7E }, + { 0x1E96, 0x1E9A, 0x1E96 }, + { 0x1E9C, 0x1E9D, 0x1E9C }, + { 0x1F00, 0x1F07, 0x1F08 }, + { 0x1F10, 0x1F15, 0x1F18 }, + { 0x1F20, 0x1F27, 0x1F28 }, + { 0x1F30, 0x1F37, 0x1F38 }, + { 0x1F40, 0x1F45, 0x1F48 }, + { 0x1F60, 0x1F67, 0x1F68 }, + { 0x1F70, 0x1F71, 0x1FBA }, + { 0x1F72, 0x1F75, 0x1FC8 }, + { 0x1F76, 0x1F77, 0x1FDA }, + { 0x1F78, 0x1F79, 0x1FF8 }, + { 0x1F7A, 0x1F7B, 0x1FEA }, + { 0x1F7C, 0x1F7D, 0x1FFA }, + { 0x1F80, 0x1F87, 0x1F88 }, + { 0x1F90, 0x1F97, 0x1F98 }, + { 0x1FA0, 0x1FA7, 0x1FA8 }, + { 0x1FB0, 0x1FB1, 0x1FB8 }, + { 0x1FB6, 0x1FB7, 0x1FB6 }, + { 0x1FC6, 0x1FC7, 0x1FC6 }, + { 0x1FD0, 0x1FD1, 0x1FD8 }, + { 0x1FD2, 0x1FD3, 0x1FD2 }, + { 0x1FD6, 0x1FD7, 0x1FD6 }, + { 0x1FE0, 0x1FE1, 0x1FE8 }, + { 0x1FE2, 0x1FE4, 0x1FE2 }, + { 0x1FE6, 0x1FE7, 0x1FE6 }, + { 0x1FF6, 0x1FF7, 0x1FF6 }, + { 0x210E, 0x210F, 0x210E }, + { 0x213C, 0x213D, 0x213C }, + { 0x2146, 0x2149, 0x2146 }, + { 0x2C30, 0x2C5E, 0x2C00 }, + { 0x2C77, 0x2C7B, 0x2C77 }, + { 0x2D00, 0x2D25, 0x10A0 }, + { 0xA730, 0xA731, 0xA730 }, + { 0xA771, 0xA778, 0xA771 }, + { 0xA794, 0xA795, 0xA794 }, + { 0xAB30, 0xAB5A, 0xAB30 }, + { 0xAB64, 0xAB65, 0xAB64 }, + { 0xFB00, 0xFB06, 0xFB00 }, + { 0xFB13, 0xFB17, 0xFB13 }, + { 0xFF41, 0xFF5A, 0xFF21 }, + { 0x10428, 0x1044F, 0x10400 }, + { 0x118C0, 0x118DF, 0x118A0 }, + { 0x1D41A, 0x1D433, 0x1D41A }, + { 0x1D44E, 0x1D454, 0x1D44E }, + { 0x1D456, 0x1D467, 0x1D456 }, + { 0x1D482, 0x1D49B, 0x1D482 }, + { 0x1D4B6, 0x1D4B9, 0x1D4B6 }, + { 0x1D4BD, 0x1D4C3, 0x1D4BD }, + { 0x1D4C5, 0x1D4CF, 0x1D4C5 }, + { 0x1D4EA, 0x1D503, 0x1D4EA }, + { 0x1D51E, 0x1D537, 0x1D51E }, + { 0x1D552, 0x1D56B, 0x1D552 }, + { 0x1D586, 0x1D59F, 0x1D586 }, + { 0x1D5BA, 0x1D5D3, 0x1D5BA }, + { 0x1D5EE, 0x1D607, 0x1D5EE }, + { 0x1D622, 0x1D63B, 0x1D622 }, + { 0x1D656, 0x1D66F, 0x1D656 }, + { 0x1D68A, 0x1D6A5, 0x1D68A }, + { 0x1D6C2, 0x1D6DA, 0x1D6C2 }, + { 0x1D6DC, 0x1D6E1, 0x1D6DC }, + { 0x1D6FC, 0x1D714, 0x1D6FC }, + { 0x1D716, 0x1D71B, 0x1D716 }, + { 0x1D736, 0x1D74E, 0x1D736 }, + { 0x1D750, 0x1D755, 0x1D750 }, + { 0x1D770, 0x1D788, 0x1D770 }, + { 0x1D78A, 0x1D78F, 0x1D78A }, + { 0x1D7AA, 0x1D7C2, 0x1D7AA }, + { 0x1D7C4, 0x1D7C9, 0x1D7C4 }, +}; + +static Rune lower1[][2] = { + { 0x00B5, 0x039C }, + { 0x00DF, 0x00DF }, + { 0x00FF, 0x0178 }, + { 0x0131, 0x0049 }, + { 0x0138, 0x0138 }, + { 0x0149, 0x0149 }, + { 0x017F, 0x0053 }, + { 0x0180, 0x0243 }, + { 0x0188, 0x0187 }, + { 0x018C, 0x018B }, + { 0x018D, 0x018D }, + { 0x0192, 0x0191 }, + { 0x0195, 0x01F6 }, + { 0x0199, 0x0198 }, + { 0x019A, 0x023D }, + { 0x019B, 0x019B }, + { 0x019E, 0x0220 }, + { 0x01A8, 0x01A7 }, + { 0x01AD, 0x01AC }, + { 0x01B0, 0x01AF }, + { 0x01B9, 0x01B8 }, + { 0x01BA, 0x01BA }, + { 0x01BD, 0x01BC }, + { 0x01BE, 0x01BE }, + { 0x01BF, 0x01F7 }, + { 0x01C6, 0x01C4 }, + { 0x01C9, 0x01C7 }, + { 0x01CC, 0x01CA }, + { 0x01DD, 0x018E }, + { 0x01F0, 0x01F0 }, + { 0x01F3, 0x01F1 }, + { 0x01F5, 0x01F4 }, + { 0x0221, 0x0221 }, + { 0x023C, 0x023B }, + { 0x0242, 0x0241 }, + { 0x0250, 0x2C6F }, + { 0x0251, 0x2C6D }, + { 0x0252, 0x2C70 }, + { 0x0253, 0x0181 }, + { 0x0254, 0x0186 }, + { 0x0255, 0x0255 }, + { 0x0258, 0x0258 }, + { 0x0259, 0x018F }, + { 0x025A, 0x025A }, + { 0x025B, 0x0190 }, + { 0x025C, 0xA7AB }, + { 0x0260, 0x0193 }, + { 0x0261, 0xA7AC }, + { 0x0262, 0x0262 }, + { 0x0263, 0x0194 }, + { 0x0264, 0x0264 }, + { 0x0265, 0xA78D }, + { 0x0266, 0xA7AA }, + { 0x0267, 0x0267 }, + { 0x0268, 0x0197 }, + { 0x0269, 0x0196 }, + { 0x026A, 0x026A }, + { 0x026B, 0x2C62 }, + { 0x026C, 0xA7AD }, + { 0x026F, 0x019C }, + { 0x0270, 0x0270 }, + { 0x0271, 0x2C6E }, + { 0x0272, 0x019D }, + { 0x0275, 0x019F }, + { 0x027D, 0x2C64 }, + { 0x0280, 0x01A6 }, + { 0x0283, 0x01A9 }, + { 0x0287, 0xA7B1 }, + { 0x0288, 0x01AE }, + { 0x0289, 0x0244 }, + { 0x028C, 0x0245 }, + { 0x0292, 0x01B7 }, + { 0x0293, 0x0293 }, + { 0x029E, 0xA7B0 }, + { 0x0377, 0x0376 }, + { 0x0390, 0x0390 }, + { 0x03AC, 0x0386 }, + { 0x03B0, 0x03B0 }, + { 0x03C2, 0x03A3 }, + { 0x03CC, 0x038C }, + { 0x03D0, 0x0392 }, + { 0x03D1, 0x0398 }, + { 0x03D5, 0x03A6 }, + { 0x03D6, 0x03A0 }, + { 0x03D7, 0x03CF }, + { 0x03F0, 0x039A }, + { 0x03F1, 0x03A1 }, + { 0x03F2, 0x03F9 }, + { 0x03F3, 0x037F }, + { 0x03F5, 0x0395 }, + { 0x03F8, 0x03F7 }, + { 0x03FB, 0x03FA }, + { 0x03FC, 0x03FC }, + { 0x04CF, 0x04C0 }, + { 0x0587, 0x0587 }, + { 0x1D79, 0xA77D }, + { 0x1D7D, 0x2C63 }, + { 0x1E9B, 0x1E60 }, + { 0x1E9F, 0x1E9F }, + { 0x1F50, 0x1F50 }, + { 0x1F51, 0x1F59 }, + { 0x1F52, 0x1F52 }, + { 0x1F53, 0x1F5B }, + { 0x1F54, 0x1F54 }, + { 0x1F55, 0x1F5D }, + { 0x1F56, 0x1F56 }, + { 0x1F57, 0x1F5F }, + { 0x1FB2, 0x1FB2 }, + { 0x1FB3, 0x1FBC }, + { 0x1FB4, 0x1FB4 }, + { 0x1FBE, 0x0399 }, + { 0x1FC2, 0x1FC2 }, + { 0x1FC3, 0x1FCC }, + { 0x1FC4, 0x1FC4 }, + { 0x1FE5, 0x1FEC }, + { 0x1FF2, 0x1FF2 }, + { 0x1FF3, 0x1FFC }, + { 0x1FF4, 0x1FF4 }, + { 0x210A, 0x210A }, + { 0x2113, 0x2113 }, + { 0x212F, 0x212F }, + { 0x2134, 0x2134 }, + { 0x2139, 0x2139 }, + { 0x214E, 0x2132 }, + { 0x2184, 0x2183 }, + { 0x2C61, 0x2C60 }, + { 0x2C65, 0x023A }, + { 0x2C66, 0x023E }, + { 0x2C71, 0x2C71 }, + { 0x2C73, 0x2C72 }, + { 0x2C74, 0x2C74 }, + { 0x2C76, 0x2C75 }, + { 0x2CE4, 0x2CE4 }, + { 0x2CF3, 0x2CF2 }, + { 0x2D27, 0x10C7 }, + { 0x2D2D, 0x10CD }, + { 0xA78C, 0xA78B }, + { 0xA78E, 0xA78E }, + { 0xA7FA, 0xA7FA }, + { 0x1D4BB, 0x1D4BB }, + { 0x1D7CB, 0x1D7CB }, +}; + +int +islowerrune(Rune r) +{ + Rune *match; + + if((match = bsearch(&r, lower4, nelem(lower4), sizeof *lower4, &rune2cmp))) + return !((r - match[0]) % 2); + if(bsearch(&r, lower2, nelem(lower2), sizeof *lower2, &rune2cmp)) + return 1; + if(bsearch(&r, lower1, nelem(lower1), sizeof *lower1, &rune1cmp)) + return 1; + return 0; +} + +int +toupperrune(Rune r) +{ + Rune *match; + + match = bsearch(&r, lower4, nelem(lower4), sizeof *lower4, &rune2cmp); + if (match) + return ((r - match[0]) % 2) ? r : r - 1; + match = bsearch(&r, lower2, nelem(lower2), sizeof *lower2, &rune2cmp); + if (match) + return match[2] + (r - match[0]); + match = bsearch(&r, lower1, nelem(lower1), sizeof *lower1, &rune1cmp); + if (match) + return match[1]; + return r; +} diff --git a/source/sbase/libutf/mkrunetype.awk b/source/sbase/libutf/mkrunetype.awk new file mode 100644 index 00000000..3736e781 --- /dev/null +++ b/source/sbase/libutf/mkrunetype.awk @@ -0,0 +1,240 @@ +# See LICENSE file for copyright and license details. + +BEGIN { + FS = ";" + # set up hexadecimal lookup table + for(i = 0; i < 16; i++) + hex[sprintf("%X",i)] = i; + HEADER = "/* Automatically generated by mkrunetype.awk */\n#include \n\n#include \"../utf.h\"\n#include \"runetype.h\"\n" + HEADER_OTHER = "/* Automatically generated by mkrunetype.awk */\n#include \"../utf.h\"\n#include \"runetype.h\"\n" +} + +$3 ~ /^L/ { alphav[alphac++] = $1; } +($3 ~ /^Z/) || ($5 == "WS") || ($5 == "S") || ($5 == "B") { spacev[spacec++] = $1; } +$3 == "Cc" { cntrlv[cntrlc++] = $1; } +$3 == "Lu" { upperv[upperc++] = $1; tolowerv[uppercc++] = ($14 == "") ? $1 : $14; } +$3 == "Ll" { lowerv[lowerc++] = $1; toupperv[lowercc++] = ($13 == "") ? $1 : $13; } +$3 == "Lt" { titlev[titlec++] = $1; } +$3 == "Nd" { digitv[digitc++] = $1; } + +END { + system("rm -f isalpharune.c isspacerune.c iscntrlrune.c upperrune.c lowerrune.c istitlerune.c isdigitrune.c"); + + mkis("alpha", alphav, alphac, "isalpharune.c", q, ""); + mkis("space", spacev, spacec, "isspacerune.c", q, ""); + mkis("cntrl", cntrlv, cntrlc, "iscntrlrune.c", q, ""); + mkis("upper", upperv, upperc, "upperrune.c", tolowerv, "lower"); + mkis("lower", lowerv, lowerc, "lowerrune.c", toupperv, "upper"); + mkis("title", titlev, titlec, "istitlerune.c", q, ""); + mkis("digit", digitv, digitc, "isdigitrune.c", q, ""); + + system("rm -f isalnumrune.c isblankrune.c isprintrune.c isgraphrune.c ispunctrune.c isxdigitrune.c"); + + otheris(); +} + +# parse hexadecimal rune index to int +function code(s) { + x = 0; + for(i = 1; i <= length(s); i++) { + c = substr(s, i, 1); + x = (x*16) + hex[c]; + } + return x; +} + +# generate 'isrune' unicode lookup function +function mkis(name, runev, runec, file, casev, casename) { + rune1c = 0; + rune2c = 0; + rune3c = 0; + rune4c = 0; + mode = 1; + + #sort rune groups into singletons, ranges and laces + for(j = 0; j < runec; j++) { + # range + if(code(runev[j+1]) == code(runev[j])+1 && ((length(casev) == 0) || + code(casev[j+1]) == code(casev[j])+1) && j+1 < runec) { + if (mode == 2) { + continue; + } else if (mode == 3) { + rune3v1[rune3c] = runev[j]; + rune3c++; + } else if (mode == 4) { + rune4v1[rune4c] = runev[j]; + rune4c++; + } + mode = 2; + rune2v0[rune2c] = runev[j]; + if(length(casev) > 0) { + case2v[rune2c] = casev[j]; + } + continue; + } + # lace 1 + if(code(runev[j+1]) == code(runev[j])+2 && ((length(casev) == 0) || + (code(casev[j+1]) == code(runev[j+1])+1 && code(casev[j]) == code(runev[j])+1)) && + j+1 < runec) { + if (mode == 3) { + continue; + } else if (mode == 2) { + rune2v1[rune2c] = runev[j]; + rune2c++; + } else if (mode == 4) { + rune4v1[rune2c] = runev[j]; + rune4c++; + } + mode = 3; + rune3v0[rune3c] = runev[j]; + continue; + } + # lace 2 + if(code(runev[j+1]) == code(runev[j])+2 && ((length(casev) == 0) || + (code(casev[j+1]) == code(runev[j+1])-1 && code(casev[j]) == code(runev[j])-1)) && + j+1 < runec) { + if (mode == 4) { + continue; + } else if (mode == 2) { + rune2v1[rune2c] = runev[j]; + rune2c++; + } else if (mode == 3) { + rune3v1[rune2c] = runev[j]; + rune3c++; + } + mode = 4; + rune4v0[rune4c] = runev[j]; + continue; + } + # terminating case + if (mode == 1) { + rune1v[rune1c] = runev[j]; + if (length(casev) > 0) { + case1v[rune1c] = casev[j]; + } + rune1c++; + } else if (mode == 2) { + rune2v1[rune2c] = runev[j]; + rune2c++; + } else if (mode == 3) { + rune3v1[rune3c] = runev[j]; + rune3c++; + } else { #lace 2 + rune4v1[rune4c] = runev[j]; + rune4c++; + } + mode = 1; + } + print HEADER > file; + + #generate list of laces 1 + if(rune3c > 0) { + print "static Rune "name"3[][2] = {" > file; + for(j = 0; j < rune3c; j++) { + print "\t{ 0x"rune3v0[j]", 0x"rune3v1[j]" }," > file; + } + print "};\n" > file; + } + + #generate list of laces 2 + if(rune4c > 0) { + print "static Rune "name"4[][2] = {" > file; + for(j = 0; j < rune4c; j++) { + print "\t{ 0x"rune4v0[j]", 0x"rune4v1[j]" }," > file; + } + print "};\n" > file; + } + + # generate list of ranges + if(rune2c > 0) { + if(length(casev) > 0) { + print "static Rune "name"2[][3] = {" > file; + for(j = 0; j < rune2c; j++) { + print "\t{ 0x"rune2v0[j]", 0x"rune2v1[j]", 0x"case2v[j]" }," > file; + } + } else { + print "static Rune "name"2[][2] = {" > file + for(j = 0; j < rune2c; j++) { + print "\t{ 0x"rune2v0[j]", 0x"rune2v1[j]" }," > file; + } + } + print "};\n" > file; + } + + # generate list of singletons + if(rune1c > 0) { + if(length(casev) > 0) { + print "static Rune "name"1[][2] = {" > file; + for(j = 0; j < rune1c; j++) { + print "\t{ 0x"rune1v[j]", 0x"case1v[j]" }," > file; + } + } else { + print "static Rune "name"1[] = {" > file; + for(j = 0; j < rune1c; j++) { + print "\t0x"rune1v[j]"," > file; + } + } + print "};\n" > file; + } + # generate lookup function + print "int\nis"name"rune(Rune r)\n{" > file; + if(rune4c > 0 || rune3c > 0) + print "\tRune *match;\n" > file; + if(rune4c > 0) { + print "\tif((match = bsearch(&r, "name"4, nelem("name"4), sizeof *"name"4, &rune2cmp)))" > file; + print "\t\treturn !((r - match[0]) % 2);" > file; + } + if(rune3c > 0) { + print "\tif((match = bsearch(&r, "name"3, nelem("name"3), sizeof *"name"3, &rune2cmp)))" > file; + print "\t\treturn !((r - match[0]) % 2);" > file; + } + if(rune2c > 0) { + print "\tif(bsearch(&r, "name"2, nelem("name"2), sizeof *"name"2, &rune2cmp))\n\t\treturn 1;" > file; + } + if(rune1c > 0) { + print "\tif(bsearch(&r, "name"1, nelem("name"1), sizeof *"name"1, &rune1cmp))\n\t\treturn 1;" > file; + } + print "\treturn 0;\n}" > file; + + # generate case conversion function + if(length(casev) > 0) { + print "\nint\nto"casename"rune(Rune r)\n{\n\tRune *match;\n" > file; + if(rune4c > 0) { + print "\tmatch = bsearch(&r, "name"4, nelem("name"4), sizeof *"name"4, &rune2cmp);" > file; + print "\tif (match)" > file; + print "\t\treturn ((r - match[0]) % 2) ? r : r - 1;" > file; + } + if(rune3c > 0) { + print "\tmatch = bsearch(&r, "name"3, nelem("name"3), sizeof *"name"3, &rune2cmp);" > file; + print "\tif (match)" > file; + print "\t\treturn ((r - match[0]) % 2) ? r : r + 1;" > file; + } + if(rune2c > 0) { + print "\tmatch = bsearch(&r, "name"2, nelem("name"2), sizeof *"name"2, &rune2cmp);" > file; + print "\tif (match)" > file; + print "\t\treturn match[2] + (r - match[0]);" > file; + } + if(rune1c > 0) { + print "\tmatch = bsearch(&r, "name"1, nelem("name"1), sizeof *"name"1, &rune1cmp);" > file; + print "\tif (match)" > file; + print "\t\treturn match[1];" > file; + } + print "\treturn r;\n}" > file; + } +} + +function otheris() { + print HEADER_OTHER > "isalnumrune.c"; + print "int\nisalnumrune(Rune r)\n{\n\treturn isalpharune(r) || isdigitrune(r);\n}" > "isalnumrune.c"; + print HEADER_OTHER > "isblankrune.c"; + print "int\nisblankrune(Rune r)\n{\n\treturn r == ' ' || r == '\\t';\n}" > "isblankrune.c"; + print HEADER_OTHER > "isprintrune.c"; + print "int\nisprintrune(Rune r)\n{\n\treturn !iscntrlrune(r) && (r != 0x2028) && (r != 0x2029) &&" > "isprintrune.c"; + print "\t ((r < 0xFFF9) || (r > 0xFFFB));\n}" > "isprintrune.c"; + print HEADER_OTHER > "isgraphrune.c"; + print "int\nisgraphrune(Rune r)\n{\n\treturn !isspacerune(r) && isprintrune(r);\n}" > "isgraphrune.c"; + print HEADER_OTHER > "ispunctrune.c"; + print "int\nispunctrune(Rune r)\n{\n\treturn isgraphrune(r) && !isalnumrune(r);\n}" > "ispunctrune.c"; + print HEADER_OTHER > "isxdigitrune.c"; + print "int\nisxdigitrune(Rune r)\n{\n\treturn (r >= '0' && (r - '0') < 10) || (r >= 'a' && (r - 'a') < 6);\n}" > "isxdigitrune.c"; +} diff --git a/source/sbase/libutf/rune.c b/source/sbase/libutf/rune.c new file mode 100644 index 00000000..1273f451 --- /dev/null +++ b/source/sbase/libutf/rune.c @@ -0,0 +1,148 @@ +/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith + * + * 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/source/sbase/libutf/runetype.c b/source/sbase/libutf/runetype.c new file mode 100644 index 00000000..9e8ede8a --- /dev/null +++ b/source/sbase/libutf/runetype.c @@ -0,0 +1,41 @@ +/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith + * (c) 2015 Laslo Hunhold + * + * 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" + +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/source/sbase/libutf/runetype.h b/source/sbase/libutf/runetype.h new file mode 100644 index 00000000..8d09c347 --- /dev/null +++ b/source/sbase/libutf/runetype.h @@ -0,0 +1,26 @@ +/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith + * (c) 2015 Laslo Hunhold + * + * 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. + */ + +#define nelem(x) (sizeof (x) / sizeof *(x)) + +int rune1cmp(const void *, const void *); +int rune2cmp(const void *, const void *); diff --git a/source/sbase/libutf/upperrune.c b/source/sbase/libutf/upperrune.c new file mode 100644 index 00000000..3d40fb5f --- /dev/null +++ b/source/sbase/libutf/upperrune.c @@ -0,0 +1,242 @@ +/* Automatically generated by mkrunetype.awk */ +#include + +#include "../utf.h" +#include "runetype.h" + +static Rune upper3[][2] = { + { 0x0100, 0x012E }, + { 0x0132, 0x0136 }, + { 0x0139, 0x0147 }, + { 0x014A, 0x0176 }, + { 0x0179, 0x017D }, + { 0x0182, 0x0184 }, + { 0x01A0, 0x01A4 }, + { 0x01B3, 0x01B5 }, + { 0x01CD, 0x01DB }, + { 0x01DE, 0x01EE }, + { 0x01F8, 0x021E }, + { 0x0222, 0x0232 }, + { 0x0246, 0x024E }, + { 0x0370, 0x0372 }, + { 0x03D8, 0x03EE }, + { 0x0460, 0x0480 }, + { 0x048A, 0x04BE }, + { 0x04C1, 0x04CD }, + { 0x04D0, 0x052E }, + { 0x1E00, 0x1E94 }, + { 0x1EA0, 0x1EFE }, + { 0x2C67, 0x2C6B }, + { 0x2C80, 0x2CE2 }, + { 0x2CEB, 0x2CED }, + { 0xA640, 0xA66C }, + { 0xA680, 0xA69A }, + { 0xA722, 0xA72E }, + { 0xA732, 0xA76E }, + { 0xA779, 0xA77B }, + { 0xA77E, 0xA786 }, + { 0xA790, 0xA792 }, + { 0xA796, 0xA7A8 }, +}; + +static Rune upper2[][3] = { + { 0x0041, 0x005A, 0x0061 }, + { 0x00C0, 0x00D6, 0x00E0 }, + { 0x00D8, 0x00DE, 0x00F8 }, + { 0x0189, 0x018A, 0x0256 }, + { 0x01B1, 0x01B2, 0x028A }, + { 0x0388, 0x038A, 0x03AD }, + { 0x038E, 0x038F, 0x03CD }, + { 0x0391, 0x03A1, 0x03B1 }, + { 0x03A3, 0x03AB, 0x03C3 }, + { 0x03D2, 0x03D4, 0x03D2 }, + { 0x03FD, 0x03FF, 0x037B }, + { 0x0400, 0x040F, 0x0450 }, + { 0x0410, 0x042F, 0x0430 }, + { 0x0531, 0x0556, 0x0561 }, + { 0x10A0, 0x10C5, 0x2D00 }, + { 0x1F08, 0x1F0F, 0x1F00 }, + { 0x1F18, 0x1F1D, 0x1F10 }, + { 0x1F28, 0x1F2F, 0x1F20 }, + { 0x1F38, 0x1F3F, 0x1F30 }, + { 0x1F48, 0x1F4D, 0x1F40 }, + { 0x1F68, 0x1F6F, 0x1F60 }, + { 0x1FB8, 0x1FB9, 0x1FB0 }, + { 0x1FBA, 0x1FBB, 0x1F70 }, + { 0x1FC8, 0x1FCB, 0x1F72 }, + { 0x1FD8, 0x1FD9, 0x1FD0 }, + { 0x1FDA, 0x1FDB, 0x1F76 }, + { 0x1FE8, 0x1FE9, 0x1FE0 }, + { 0x1FEA, 0x1FEB, 0x1F7A }, + { 0x1FF8, 0x1FF9, 0x1F78 }, + { 0x1FFA, 0x1FFB, 0x1F7C }, + { 0x210B, 0x210D, 0x210B }, + { 0x2110, 0x2112, 0x2110 }, + { 0x2119, 0x211D, 0x2119 }, + { 0x212C, 0x212D, 0x212C }, + { 0x2130, 0x2131, 0x2130 }, + { 0x213E, 0x213F, 0x213E }, + { 0x2C00, 0x2C2E, 0x2C30 }, + { 0x2C7E, 0x2C7F, 0x023F }, + { 0xFF21, 0xFF3A, 0xFF41 }, + { 0x10400, 0x10427, 0x10428 }, + { 0x118A0, 0x118BF, 0x118C0 }, + { 0x1D400, 0x1D419, 0x1D400 }, + { 0x1D434, 0x1D44D, 0x1D434 }, + { 0x1D468, 0x1D481, 0x1D468 }, + { 0x1D49E, 0x1D49F, 0x1D49E }, + { 0x1D4A5, 0x1D4A6, 0x1D4A5 }, + { 0x1D4A9, 0x1D4AC, 0x1D4A9 }, + { 0x1D4AE, 0x1D4B5, 0x1D4AE }, + { 0x1D4D0, 0x1D4E9, 0x1D4D0 }, + { 0x1D504, 0x1D505, 0x1D504 }, + { 0x1D507, 0x1D50A, 0x1D507 }, + { 0x1D50D, 0x1D514, 0x1D50D }, + { 0x1D516, 0x1D51C, 0x1D516 }, + { 0x1D538, 0x1D539, 0x1D538 }, + { 0x1D53B, 0x1D53E, 0x1D53B }, + { 0x1D540, 0x1D544, 0x1D540 }, + { 0x1D54A, 0x1D550, 0x1D54A }, + { 0x1D56C, 0x1D585, 0x1D56C }, + { 0x1D5A0, 0x1D5B9, 0x1D5A0 }, + { 0x1D5D4, 0x1D5ED, 0x1D5D4 }, + { 0x1D608, 0x1D621, 0x1D608 }, + { 0x1D63C, 0x1D655, 0x1D63C }, + { 0x1D670, 0x1D689, 0x1D670 }, + { 0x1D6A8, 0x1D6C0, 0x1D6A8 }, + { 0x1D6E2, 0x1D6FA, 0x1D6E2 }, + { 0x1D71C, 0x1D734, 0x1D71C }, + { 0x1D756, 0x1D76E, 0x1D756 }, + { 0x1D790, 0x1D7A8, 0x1D790 }, +}; + +static Rune upper1[][2] = { + { 0x0130, 0x0069 }, + { 0x0178, 0x00FF }, + { 0x0181, 0x0253 }, + { 0x0186, 0x0254 }, + { 0x0187, 0x0188 }, + { 0x018B, 0x018C }, + { 0x018E, 0x01DD }, + { 0x018F, 0x0259 }, + { 0x0190, 0x025B }, + { 0x0191, 0x0192 }, + { 0x0193, 0x0260 }, + { 0x0194, 0x0263 }, + { 0x0196, 0x0269 }, + { 0x0197, 0x0268 }, + { 0x0198, 0x0199 }, + { 0x019C, 0x026F }, + { 0x019D, 0x0272 }, + { 0x019F, 0x0275 }, + { 0x01A6, 0x0280 }, + { 0x01A7, 0x01A8 }, + { 0x01A9, 0x0283 }, + { 0x01AC, 0x01AD }, + { 0x01AE, 0x0288 }, + { 0x01AF, 0x01B0 }, + { 0x01B7, 0x0292 }, + { 0x01B8, 0x01B9 }, + { 0x01BC, 0x01BD }, + { 0x01C4, 0x01C6 }, + { 0x01C7, 0x01C9 }, + { 0x01CA, 0x01CC }, + { 0x01F1, 0x01F3 }, + { 0x01F4, 0x01F5 }, + { 0x01F6, 0x0195 }, + { 0x01F7, 0x01BF }, + { 0x0220, 0x019E }, + { 0x023A, 0x2C65 }, + { 0x023B, 0x023C }, + { 0x023D, 0x019A }, + { 0x023E, 0x2C66 }, + { 0x0241, 0x0242 }, + { 0x0243, 0x0180 }, + { 0x0244, 0x0289 }, + { 0x0245, 0x028C }, + { 0x0376, 0x0377 }, + { 0x037F, 0x03F3 }, + { 0x0386, 0x03AC }, + { 0x038C, 0x03CC }, + { 0x03CF, 0x03D7 }, + { 0x03F4, 0x03B8 }, + { 0x03F7, 0x03F8 }, + { 0x03F9, 0x03F2 }, + { 0x03FA, 0x03FB }, + { 0x04C0, 0x04CF }, + { 0x10C7, 0x2D27 }, + { 0x10CD, 0x2D2D }, + { 0x1E9E, 0x00DF }, + { 0x1F59, 0x1F51 }, + { 0x1F5B, 0x1F53 }, + { 0x1F5D, 0x1F55 }, + { 0x1F5F, 0x1F57 }, + { 0x1FEC, 0x1FE5 }, + { 0x2102, 0x2102 }, + { 0x2107, 0x2107 }, + { 0x2115, 0x2115 }, + { 0x2124, 0x2124 }, + { 0x2126, 0x03C9 }, + { 0x2128, 0x2128 }, + { 0x212A, 0x006B }, + { 0x212B, 0x00E5 }, + { 0x2132, 0x214E }, + { 0x2133, 0x2133 }, + { 0x2145, 0x2145 }, + { 0x2183, 0x2184 }, + { 0x2C60, 0x2C61 }, + { 0x2C62, 0x026B }, + { 0x2C63, 0x1D7D }, + { 0x2C64, 0x027D }, + { 0x2C6D, 0x0251 }, + { 0x2C6E, 0x0271 }, + { 0x2C6F, 0x0250 }, + { 0x2C70, 0x0252 }, + { 0x2C72, 0x2C73 }, + { 0x2C75, 0x2C76 }, + { 0x2CF2, 0x2CF3 }, + { 0xA77D, 0x1D79 }, + { 0xA78B, 0xA78C }, + { 0xA78D, 0x0265 }, + { 0xA7AA, 0x0266 }, + { 0xA7AB, 0x025C }, + { 0xA7AC, 0x0261 }, + { 0xA7AD, 0x026C }, + { 0xA7B0, 0x029E }, + { 0xA7B1, 0x0287 }, + { 0x1D49C, 0x1D49C }, + { 0x1D4A2, 0x1D4A2 }, + { 0x1D546, 0x1D546 }, + { 0x1D7CA, 0x1D7CA }, +}; + +int +isupperrune(Rune r) +{ + Rune *match; + + if((match = bsearch(&r, upper3, nelem(upper3), sizeof *upper3, &rune2cmp))) + return !((r - match[0]) % 2); + if(bsearch(&r, upper2, nelem(upper2), sizeof *upper2, &rune2cmp)) + return 1; + if(bsearch(&r, upper1, nelem(upper1), sizeof *upper1, &rune1cmp)) + return 1; + return 0; +} + +int +tolowerrune(Rune r) +{ + Rune *match; + + match = bsearch(&r, upper3, nelem(upper3), sizeof *upper3, &rune2cmp); + if (match) + return ((r - match[0]) % 2) ? r : r + 1; + match = bsearch(&r, upper2, nelem(upper2), sizeof *upper2, &rune2cmp); + if (match) + return match[2] + (r - match[0]); + match = bsearch(&r, upper1, nelem(upper1), sizeof *upper1, &rune1cmp); + if (match) + return match[1]; + return r; +} diff --git a/source/sbase/libutf/utf.c b/source/sbase/libutf/utf.c new file mode 100644 index 00000000..897c5ef8 --- /dev/null +++ b/source/sbase/libutf/utf.c @@ -0,0 +1,129 @@ +/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith + * + * 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 +#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/source/sbase/libutf/utftorunestr.c b/source/sbase/libutf/utftorunestr.c new file mode 100644 index 00000000..005fe8ab --- /dev/null +++ b/source/sbase/libutf/utftorunestr.c @@ -0,0 +1,13 @@ +/* See LICENSE file for copyright and license details. */ +#include "../utf.h" + +int +utftorunestr(const char *str, Rune *r) +{ + int i, n; + + for(i = 0; (n = chartorune(&r[i], str)) && r[i]; i++) + str += n; + + return i; +} diff --git a/source/sbase/libutil/concat.c b/source/sbase/libutil/concat.c new file mode 100644 index 00000000..fad94712 --- /dev/null +++ b/source/sbase/libutil/concat.c @@ -0,0 +1,19 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "../text.h" +#include "../util.h" + +void +concat(FILE *fp1, const char *s1, FILE *fp2, const char *s2) +{ + char buf[BUFSIZ]; + size_t n; + + while ((n = fread(buf, 1, sizeof(buf), fp1))) { + fwrite(buf, 1, n, fp2); + + if (feof(fp1) || ferror(fp1) || ferror(fp2)) + break; + } +} diff --git a/source/sbase/libutil/cp.c b/source/sbase/libutil/cp.c new file mode 100644 index 00000000..c3989621 --- /dev/null +++ b/source/sbase/libutil/cp.c @@ -0,0 +1,176 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../fs.h" +#include "../text.h" +#include "../util.h" + +int cp_aflag = 0; +int cp_fflag = 0; +int cp_pflag = 0; +int cp_rflag = 0; +int cp_vflag = 0; +int cp_status = 0; +int cp_follow = 'L'; + +int +cp(const char *s1, const char *s2, int depth) +{ + DIR *dp; + FILE *f1, *f2; + struct dirent *d; + struct stat st; + struct timespec times[2]; + ssize_t r; + int (*statf)(const char *, struct stat *); + char target[PATH_MAX], ns1[PATH_MAX], ns2[PATH_MAX], *statf_name; + + if (cp_follow == 'P' || (cp_follow == 'H' && depth)) { + statf_name = "lstat"; + statf = lstat; + } else { + statf_name = "stat"; + statf = stat; + } + + if (statf(s1, &st) < 0) { + weprintf("%s %s:", statf_name, s1); + cp_status = 1; + return 0; + } + + if (cp_vflag) + printf("%s -> %s\n", s1, s2); + + if (S_ISLNK(st.st_mode)) { + if ((r = readlink(s1, target, sizeof(target) - 1)) >= 0) { + target[r] = '\0'; + if (cp_fflag && unlink(s2) < 0 && errno != ENOENT) { + weprintf("unlink %s:", s2); + cp_status = 1; + return 0; + } else if (symlink(target, s2) < 0) { + weprintf("symlink %s -> %s:", s2, target); + cp_status = 1; + return 0; + } + } + } else if (S_ISDIR(st.st_mode)) { + if (!cp_rflag) { + weprintf("%s is a directory\n", s1); + cp_status = 1; + return 0; + } + if (!(dp = opendir(s1))) { + weprintf("opendir %s:", s1); + cp_status = 1; + return 0; + } + if (mkdir(s2, st.st_mode) < 0 && errno != EEXIST) { + weprintf("mkdir %s:", s2); + cp_status = 1; + return 0; + } + + while ((d = readdir(dp))) { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + + estrlcpy(ns1, s1, sizeof(ns1)); + if (s1[strlen(s1) - 1] != '/') + estrlcat(ns1, "/", sizeof(ns1)); + estrlcat(ns1, d->d_name, sizeof(ns1)); + + estrlcpy(ns2, s2, sizeof(ns2)); + if (s2[strlen(s2) - 1] != '/') + estrlcat(ns2, "/", sizeof(ns2)); + estrlcat(ns2, d->d_name, sizeof(ns2)); + + fnck(ns1, ns2, cp, depth + 1); + } + + closedir(dp); + } else if (cp_aflag && (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode) || + S_ISSOCK(st.st_mode) || S_ISFIFO(st.st_mode))) { + if (cp_fflag && unlink(s2) < 0 && errno != ENOENT) { + weprintf("unlink %s:", s2); + cp_status = 1; + return 0; + } else if (mknod(s2, st.st_mode, st.st_rdev) < 0) { + weprintf("mknod %s:", s2); + cp_status = 1; + return 0; + } + } else { + if (!(f1 = fopen(s1, "r"))) { + weprintf("fopen %s:", s1); + cp_status = 1; + return 0; + } + if (!(f2 = fopen(s2, "w"))) { + if (cp_fflag) { + if (unlink(s2) < 0 && errno != ENOENT) { + weprintf("unlink %s:", s2); + cp_status = 1; + return 0; + } else 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); + + if (fclose(f2) == EOF) { + weprintf("fclose %s:", s2); + cp_status = 1; + return 0; + } + if (fclose(f1) == EOF) { + weprintf("fclose %s:", s1); + cp_status = 1; + return 0; + } + } + + if (cp_aflag || cp_pflag) { + /* timestamp and owner */ + if (!S_ISLNK(st.st_mode)) { + times[0] = st.st_atim; + times[1] = st.st_mtim; + utimensat(AT_FDCWD, s2, times, 0); + + if (chown(s2, st.st_uid, st.st_gid) < 0) { + weprintf("chown %s:", s2); + cp_status = 1; + return 0; + } + } else { + if (lchown(s2, st.st_uid, st.st_gid) < 0) { + weprintf("lchown %s:", s2); + cp_status = 1; + return 0; + } + } + } + + return 0; +} diff --git a/source/sbase/libutil/crypt.c b/source/sbase/libutil/crypt.c new file mode 100644 index 00000000..3f849bad --- /dev/null +++ b/source/sbase/libutil/crypt.c @@ -0,0 +1,179 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#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; +} + +static void +mdchecklist(FILE *listfp, struct crypt_ops *ops, uint8_t *md, size_t sz, + int *formatsucks, int *noread, int *nonmatch) +{ + FILE *fp; + size_t bufsiz = 0; + int r; + char *line = NULL, *file, *p; + + while (getline(&line, &bufsiz, listfp) > 0) { + 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); + } + free(line); +} + +int +cryptcheck(int argc, char *argv[], struct crypt_ops *ops, uint8_t *md, size_t sz) +{ + FILE *fp; + int formatsucks = 0, noread = 0, nonmatch = 0, ret = 0; + + if (argc == 0) { + mdchecklist(stdin, ops, md, sz, &formatsucks, &noread, &nonmatch); + } else { + for (; *argv; argc--, argv++) { + if ((*argv)[0] == '-' && !(*argv)[1]) { + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + ret = 1; + continue; + } + mdchecklist(fp, ops, md, sz, &formatsucks, &noread, &nonmatch); + if (fp != stdin) + fclose(fp); + } + } + + if (formatsucks) { + weprintf("%d lines are improperly formatted\n", formatsucks); + ret = 1; + } + if (noread) { + weprintf("%d listed file could not be read\n", noread); + ret = 1; + } + if (nonmatch) { + 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, "", md); + mdprint(md, "", sz); + } else { + for (; *argv; argc--, argv++) { + if ((*argv)[0] == '-' && !(*argv)[1]) { + *argv = ""; + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + ret = 1; + continue; + } + if (cryptsum(ops, fp, *argv, md)) { + ret = 1; + } else { + mdprint(md, *argv, sz); + } + if (fp != stdin && fshut(fp, *argv)) + ret = 1; + } + } + + 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/source/sbase/libutil/ealloc.c b/source/sbase/libutil/ealloc.c new file mode 100644 index 00000000..320865da --- /dev/null +++ b/source/sbase/libutil/ealloc.c @@ -0,0 +1,88 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../util.h" + +void * +ecalloc(size_t nmemb, size_t size) +{ + return encalloc(1, nmemb, size); +} + +void * +emalloc(size_t size) +{ + return enmalloc(1, size); +} + +void * +erealloc(void *p, size_t size) +{ + return enrealloc(1, p, size); +} + +char * +estrdup(const char *s) +{ + return enstrdup(1, s); +} + +char * +estrndup(const char *s, size_t n) +{ + return enstrndup(1, s, n); +} + +void * +encalloc(int status, size_t nmemb, size_t size) +{ + void *p; + + p = calloc(nmemb, size); + if (!p) + enprintf(status, "calloc: out of memory\n"); + return p; +} + +void * +enmalloc(int status, size_t size) +{ + void *p; + + p = malloc(size); + if (!p) + enprintf(status, "malloc: out of memory\n"); + return p; +} + +void * +enrealloc(int status, void *p, size_t size) +{ + p = realloc(p, size); + if (!p) + enprintf(status, "realloc: out of memory\n"); + return p; +} + +char * +enstrdup(int status, const char *s) +{ + char *p; + + p = strdup(s); + if (!p) + enprintf(status, "strdup: out of memory\n"); + return p; +} + +char * +enstrndup(int status, const char *s, size_t n) +{ + char *p; + + p = strndup(s, n); + if (!p) + enprintf(status, "strndup: out of memory\n"); + return p; +} diff --git a/source/sbase/libutil/enmasse.c b/source/sbase/libutil/enmasse.c new file mode 100644 index 00000000..a2e225ab --- /dev/null +++ b/source/sbase/libutil/enmasse.c @@ -0,0 +1,38 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include + +#include "../util.h" + +void +enmasse(int argc, char *argv[], int (*fn)(const char *, const char *, int)) +{ + struct stat st; + char buf[PATH_MAX], *dir; + int i, len; + size_t dlen; + + if (argc == 2 && !(stat(argv[1], &st) == 0 && S_ISDIR(st.st_mode))) { + fnck(argv[0], argv[1], fn, 0); + return; + } else { + dir = (argc == 1) ? "." : argv[--argc]; + } + + for (i = 0; i < argc; i++) { + dlen = strlen(dir); + if (dlen > 0 && dir[dlen - 1] == '/') + len = snprintf(buf, sizeof(buf), "%s%s", dir, basename(argv[i])); + else + len = snprintf(buf, sizeof(buf), "%s/%s", dir, basename(argv[i])); + if (len < 0 || len >= sizeof(buf)) { + eprintf("%s/%s: filename too long\n", dir, + basename(argv[i])); + } + fnck(argv[i], buf, fn, 0); + } +} diff --git a/source/sbase/libutil/eprintf.c b/source/sbase/libutil/eprintf.c new file mode 100644 index 00000000..673523e5 --- /dev/null +++ b/source/sbase/libutil/eprintf.c @@ -0,0 +1,59 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "../util.h" + +char *argv0; + +static void xvprintf(const char *, va_list); + +void +eprintf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + xvprintf(fmt, ap); + va_end(ap); + + exit(1); +} + +void +enprintf(int status, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + xvprintf(fmt, ap); + va_end(ap); + + exit(status); +} + +void +weprintf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + xvprintf(fmt, ap); + va_end(ap); +} + +void +xvprintf(const char *fmt, va_list ap) +{ + if (argv0 && strncmp(fmt, "usage", strlen("usage"))) + fprintf(stderr, "%s: ", argv0); + + vfprintf(stderr, fmt, ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } +} diff --git a/source/sbase/libutil/eregcomp.c b/source/sbase/libutil/eregcomp.c new file mode 100644 index 00000000..02c8698c --- /dev/null +++ b/source/sbase/libutil/eregcomp.c @@ -0,0 +1,27 @@ +#include + +#include +#include + +#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/source/sbase/libutil/estrtod.c b/source/sbase/libutil/estrtod.c new file mode 100644 index 00000000..24e4fdce --- /dev/null +++ b/source/sbase/libutil/estrtod.c @@ -0,0 +1,18 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#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/source/sbase/libutil/fnck.c b/source/sbase/libutil/fnck.c new file mode 100644 index 00000000..92da1f42 --- /dev/null +++ b/source/sbase/libutil/fnck.c @@ -0,0 +1,21 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "../util.h" + +void +fnck(const char *a, const char *b, + int (*fn)(const char *, const char *, int), int depth) +{ + 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, depth) < 0) + eprintf("%s -> %s:", a, b); +} diff --git a/source/sbase/libutil/fshut.c b/source/sbase/libutil/fshut.c new file mode 100644 index 00000000..e596f074 --- /dev/null +++ b/source/sbase/libutil/fshut.c @@ -0,0 +1,43 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../util.h" + +int +fshut(FILE *fp, const char *fname) +{ + int ret = 0; + + /* fflush() is undefined for input streams by ISO C, + * but not POSIX 2008 if you ignore ISO C overrides. + * Leave it unchecked and rely on the following + * functions to detect errors. + */ + fflush(fp); + + if (ferror(fp) && !ret) { + weprintf("ferror %s:", fname); + ret = 1; + } + + if (fclose(fp) && !ret) { + weprintf("fclose %s:", fname); + ret = 1; + } + + return ret; +} + +void +enfshut(int status, FILE *fp, const char *fname) +{ + if (fshut(fp, fname)) + exit(status); +} + +void +efshut(FILE *fp, const char *fname) +{ + enfshut(1, fp, fname); +} diff --git a/source/sbase/libutil/getlines.c b/source/sbase/libutil/getlines.c new file mode 100644 index 00000000..b9127696 --- /dev/null +++ b/source/sbase/libutil/getlines.c @@ -0,0 +1,32 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "../text.h" +#include "../util.h" + +void +getlines(FILE *fp, struct linebuf *b) +{ + char *line = NULL; + size_t size = 0, linelen = 0; + ssize_t len; + + while ((len = getline(&line, &size, fp)) > 0) { + if (++b->nlines > b->capacity) { + b->capacity += 512; + b->lines = erealloc(b->lines, b->capacity * sizeof(*b->lines)); + } + linelen = len; + b->lines[b->nlines - 1].data = memcpy(emalloc(linelen + 1), line, linelen + 1); + b->lines[b->nlines - 1].len = linelen; + } + free(line); + if (b->lines && b->nlines && linelen && b->lines[b->nlines - 1].data[linelen - 1] != '\n') { + b->lines[b->nlines - 1].data = erealloc(b->lines[b->nlines - 1].data, linelen + 2); + b->lines[b->nlines - 1].data[linelen] = '\n'; + b->lines[b->nlines - 1].data[linelen + 1] = '\0'; + b->lines[b->nlines - 1].len++; + } +} diff --git a/source/sbase/libutil/human.c b/source/sbase/libutil/human.c new file mode 100644 index 00000000..7e39ba5f --- /dev/null +++ b/source/sbase/libutil/human.c @@ -0,0 +1,25 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "../util.h" + +char * +humansize(off_t n) +{ + static char buf[16]; + const char postfixes[] = "BKMGTPE"; + double size; + int i; + + for (size = n, i = 0; size >= 1024 && i < strlen(postfixes); i++) + size /= 1024; + + if (!i) + snprintf(buf, sizeof(buf), "%ju", (uintmax_t)n); + else + snprintf(buf, sizeof(buf), "%.1f%c", size, postfixes[i]); + + return buf; +} diff --git a/source/sbase/libutil/linecmp.c b/source/sbase/libutil/linecmp.c new file mode 100644 index 00000000..f5a6cf90 --- /dev/null +++ b/source/sbase/libutil/linecmp.c @@ -0,0 +1,24 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../text.h" +#include "../util.h" + +int +linecmp(struct line *a, struct line *b) +{ + int res = 0; + + if (!(res = memcmp(a->data, b->data, MIN(a->len, b->len)))) { + if (a->len > b->len) { + res = a->data[b->len]; + } else if (b->len > a->len) { + res = -b->data[a->len]; + } else { + res = 0; + } + } + + return res; +} diff --git a/source/sbase/libutil/md5.c b/source/sbase/libutil/md5.c new file mode 100644 index 00000000..c7483ac6 --- /dev/null +++ b/source/sbase/libutil/md5.c @@ -0,0 +1,148 @@ +/* public domain md5 implementation based on rfc1321 and libtomcrypt */ +#include +#include + +#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/source/sbase/libutil/memmem.c b/source/sbase/libutil/memmem.c new file mode 100644 index 00000000..7dfef34b --- /dev/null +++ b/source/sbase/libutil/memmem.c @@ -0,0 +1,66 @@ +/* $OpenBSD: memmem.c,v 1.4 2015/08/31 02:53:57 guenther Exp $ */ + +/* + * Copyright (c) 2005 Pascal Gloor + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include "../util.h" + +/* + * Find the first occurrence of the byte string s in byte string l. + */ + +void * +memmem(const void *l, size_t l_len, const void *s, size_t s_len) +{ + const char *cur, *last; + const char *cl = l; + const char *cs = s; + + /* a zero length needle should just return the haystack */ + if (s_len == 0) + return (void *)cl; + + /* "s" must be smaller or equal to "l" */ + if (l_len < s_len) + return NULL; + + /* special case where s_len == 1 */ + if (s_len == 1) + return memchr(l, *cs, l_len); + + /* the last position where its possible to find "s" in "l" */ + last = cl + l_len - s_len; + + for (cur = cl; cur <= last; cur++) + if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) + return (void *)cur; + + return NULL; +} diff --git a/source/sbase/libutil/mkdirp.c b/source/sbase/libutil/mkdirp.c new file mode 100644 index 00000000..7796e24c --- /dev/null +++ b/source/sbase/libutil/mkdirp.c @@ -0,0 +1,30 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include + +#include "../util.h" + +int +mkdirp(const char *path) +{ + char tmp[PATH_MAX], *p; + + estrlcpy(tmp, path, sizeof(tmp)); + for (p = tmp + (tmp[0] == '/'); *p; p++) { + if (*p != '/') + continue; + *p = '\0'; + if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST) { + weprintf("mkdir %s:", tmp); + return -1; + } + *p = '/'; + } + if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST) { + weprintf("mkdir %s:", tmp); + return -1; + } + return 0; +} diff --git a/source/sbase/libutil/mode.c b/source/sbase/libutil/mode.c new file mode 100644 index 00000000..365b9ade --- /dev/null +++ b/source/sbase/libutil/mode.c @@ -0,0 +1,163 @@ +#include +#include +#include +#include + +#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/source/sbase/libutil/parseoffset.c b/source/sbase/libutil/parseoffset.c new file mode 100644 index 00000000..362a7829 --- /dev/null +++ b/source/sbase/libutil/parseoffset.c @@ -0,0 +1,61 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "../util.h" + +off_t +parseoffset(const char *str) +{ + off_t res, scale = 1; + char *end; + + /* strictly check what strtol() usually would let pass */ + if (!str || !*str || isspace(*str) || *str == '+' || *str == '-') { + weprintf("parseoffset %s: invalid value\n", str); + return -1; + } + + errno = 0; + res = strtol(str, &end, 0); + if (errno) { + weprintf("parseoffset %s: invalid value\n", str); + return -1; + } + if (res < 0) { + weprintf("parseoffset %s: negative value\n", str); + return -1; + } + + /* suffix */ + if (*end) { + switch (toupper((int)*end)) { + case 'B': + scale = 512L; + break; + case 'K': + scale = 1024L; + break; + case 'M': + scale = 1024L * 1024L; + break; + case 'G': + scale = 1024L * 1024L * 1024L; + break; + default: + weprintf("parseoffset %s: invalid suffix '%s'\n", str, end); + return -1; + } + } + + /* prevent overflow */ + if (res > (SSIZE_MAX / scale)) { + weprintf("parseoffset %s: out of range\n", str); + return -1; + } + + return res * scale; +} diff --git a/source/sbase/libutil/putword.c b/source/sbase/libutil/putword.c new file mode 100644 index 00000000..80a9860a --- /dev/null +++ b/source/sbase/libutil/putword.c @@ -0,0 +1,16 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "../util.h" + +void +putword(FILE *fp, const char *s) +{ + static int first = 1; + + if (!first) + fputc(' ', fp); + + fputs(s, fp); + first = 0; +} diff --git a/source/sbase/libutil/reallocarray.c b/source/sbase/libutil/reallocarray.c new file mode 100644 index 00000000..c6e5219e --- /dev/null +++ b/source/sbase/libutil/reallocarray.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2008 Otto Moerbeek + * + * 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 +#include +#include +#include + +#include "../util.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); +} + +void * +ereallocarray(void *optr, size_t nmemb, size_t size) +{ + void *p; + + if (!(p = reallocarray(optr, nmemb, size))) + eprintf("reallocarray: out of memory\n"); + + return p; +} diff --git a/source/sbase/libutil/recurse.c b/source/sbase/libutil/recurse.c new file mode 100644 index 00000000..e2b8a6eb --- /dev/null +++ b/source/sbase/libutil/recurse.c @@ -0,0 +1,107 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../fs.h" +#include "../util.h" + +int recurse_status = 0; + +void +recurse(const char *path, void *data, struct recursor *r) +{ + struct dirent *d; + struct history *new, *h; + struct stat st, dst; + DIR *dp; + int (*statf)(const char *, struct stat *); + char subpath[PATH_MAX], *statf_name; + + if (r->follow == 'P' || (r->follow == 'H' && r->depth)) { + statf_name = "lstat"; + statf = lstat; + } else { + statf_name = "stat"; + statf = stat; + } + + if (statf(path, &st) < 0) { + if (!(r->flags & SILENT)) { + weprintf("%s %s:", statf_name, path); + recurse_status = 1; + } + return; + } + if (!S_ISDIR(st.st_mode)) { + (r->fn)(path, &st, data, r); + return; + } + + new = emalloc(sizeof(struct history)); + new->prev = r->hist; + r->hist = new; + new->dev = st.st_dev; + new->ino = st.st_ino; + + for (h = new->prev; h; h = h->prev) + if (h->ino == st.st_ino && h->dev == st.st_dev) + return; + + if (!(dp = opendir(path))) { + if (!(r->flags & SILENT)) { + weprintf("opendir %s:", path); + recurse_status = 1; + } + return; + } + + if (!r->depth && (r->flags & DIRFIRST)) + (r->fn)(path, &st, data, r); + + if (!r->maxdepth || r->depth + 1 < r->maxdepth) { + while ((d = readdir(dp))) { + if (r->follow == 'H') { + statf_name = "lstat"; + statf = lstat; + } + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + estrlcpy(subpath, path, sizeof(subpath)); + if (path[strlen(path) - 1] != '/') + estrlcat(subpath, "/", sizeof(subpath)); + estrlcat(subpath, d->d_name, sizeof(subpath)); + if (statf(subpath, &dst) < 0) { + if (!(r->flags & SILENT)) { + weprintf("%s %s:", statf_name, subpath); + recurse_status = 1; + } + } else if ((r->flags & SAMEDEV) && dst.st_dev != st.st_dev) { + continue; + } else { + r->depth++; + (r->fn)(subpath, &dst, data, r); + r->depth--; + } + } + } + + if (!r->depth) { + if (!(r->flags & DIRFIRST)) + (r->fn)(path, &st, data, r); + + for (; r->hist; ) { + h = r->hist; + r->hist = r->hist->prev; + free(h); + } + } + + closedir(dp); +} diff --git a/source/sbase/libutil/rm.c b/source/sbase/libutil/rm.c new file mode 100644 index 00000000..f6a54b61 --- /dev/null +++ b/source/sbase/libutil/rm.c @@ -0,0 +1,31 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include + +#include "../fs.h" +#include "../util.h" + +int rm_status = 0; + +void +rm(const char *path, struct stat *st, void *data, struct recursor *r) +{ + if (!r->maxdepth && st && S_ISDIR(st->st_mode)) { + recurse(path, NULL, r); + + if (rmdir(path) < 0) { + if (!(r->flags & SILENT)) + weprintf("rmdir %s:", path); + if (!((r->flags & SILENT) && errno == ENOENT)) + rm_status = 1; + } + } else if (unlink(path) < 0) { + if (!(r->flags & SILENT)) + weprintf("unlink %s:", path); + if (!((r->flags & SILENT) && errno == ENOENT)) + rm_status = 1; + } +} diff --git a/source/sbase/libutil/sha1.c b/source/sbase/libutil/sha1.c new file mode 100644 index 00000000..3d76a1be --- /dev/null +++ b/source/sbase/libutil/sha1.c @@ -0,0 +1,144 @@ +/* public domain sha1 implementation based on rfc3174 and libtomcrypt */ +#include +#include + +#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/source/sbase/libutil/sha224.c b/source/sbase/libutil/sha224.c new file mode 100644 index 00000000..fce520f5 --- /dev/null +++ b/source/sbase/libutil/sha224.c @@ -0,0 +1,26 @@ +/* public domain sha224 implementation based on fips180-3 */ +#include +#include "../sha224.h" + +extern void sha256_sum_n(void *, uint8_t *, int n); + +void +sha224_init(void *ctx) +{ + struct sha224 *s = ctx; + s->len = 0; + s->h[0] = 0xc1059ed8; + s->h[1] = 0x367cd507; + s->h[2] = 0x3070dd17; + s->h[3] = 0xf70e5939; + s->h[4] = 0xffc00b31; + s->h[5] = 0x68581511; + s->h[6] = 0x64f98fa7; + s->h[7] = 0xbefa4fa4; +} + +void +sha224_sum(void *ctx, uint8_t md[SHA224_DIGEST_LENGTH]) +{ + sha256_sum_n(ctx, md, 8); +} diff --git a/source/sbase/libutil/sha256.c b/source/sbase/libutil/sha256.c new file mode 100644 index 00000000..266cfecb --- /dev/null +++ b/source/sbase/libutil/sha256.c @@ -0,0 +1,154 @@ +/* public domain sha256 implementation based on fips180-3 */ +#include +#include +#include +#include +#include + +#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_n(void *ctx, uint8_t *md, int n) +{ + struct sha256 *s = ctx; + int i; + + pad(s); + for (i = 0; i < n; 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_sum(void *ctx, uint8_t md[SHA256_DIGEST_LENGTH]) +{ + sha256_sum_n(ctx, md, 8); +} + +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/source/sbase/libutil/sha384.c b/source/sbase/libutil/sha384.c new file mode 100644 index 00000000..0a0e7777 --- /dev/null +++ b/source/sbase/libutil/sha384.c @@ -0,0 +1,26 @@ +/* public domain sha384 implementation based on fips180-3 */ +#include +#include "../sha384.h" + +extern void sha512_sum_n(void *, uint8_t *, int n); + +void +sha384_init(void *ctx) +{ + struct sha384 *s = ctx; + s->len = 0; + s->h[0] = 0xcbbb9d5dc1059ed8ULL; + s->h[1] = 0x629a292a367cd507ULL; + s->h[2] = 0x9159015a3070dd17ULL; + s->h[3] = 0x152fecd8f70e5939ULL; + s->h[4] = 0x67332667ffc00b31ULL; + s->h[5] = 0x8eb44a8768581511ULL; + s->h[6] = 0xdb0c2e0d64f98fa7ULL; + s->h[7] = 0x47b5481dbefa4fa4ULL; +} + +void +sha384_sum(void *ctx, uint8_t md[SHA384_DIGEST_LENGTH]) +{ + sha512_sum_n(ctx, md, 6); +} diff --git a/source/sbase/libutil/sha512-224.c b/source/sbase/libutil/sha512-224.c new file mode 100644 index 00000000..a5636c13 --- /dev/null +++ b/source/sbase/libutil/sha512-224.c @@ -0,0 +1,26 @@ +/* public domain sha512/224 implementation based on fips180-3 */ +#include +#include "../sha512-224.h" + +extern void sha512_sum_n(void *, uint8_t *, int n); + +void +sha512_224_init(void *ctx) +{ + struct sha512_224 *s = ctx; + s->len = 0; + s->h[0] = 0x8c3d37c819544da2ULL; + s->h[1] = 0x73e1996689dcd4d6ULL; + s->h[2] = 0x1dfab7ae32ff9c82ULL; + s->h[3] = 0x679dd514582f9fcfULL; + s->h[4] = 0x0f6d2b697bd44da8ULL; + s->h[5] = 0x77e36f7304c48942ULL; + s->h[6] = 0x3f9d85a86a1d36c8ULL; + s->h[7] = 0x1112e6ad91d692a1ULL; +} + +void +sha512_224_sum(void *ctx, uint8_t md[SHA512_224_DIGEST_LENGTH]) +{ + sha512_sum_n(ctx, md, 4); +} diff --git a/source/sbase/libutil/sha512-256.c b/source/sbase/libutil/sha512-256.c new file mode 100644 index 00000000..d4b84495 --- /dev/null +++ b/source/sbase/libutil/sha512-256.c @@ -0,0 +1,26 @@ +/* public domain sha512/256 implementation based on fips180-3 */ +#include +#include "../sha512-256.h" + +extern void sha512_sum_n(void *, uint8_t *, int n); + +void +sha512_256_init(void *ctx) +{ + struct sha512_256 *s = ctx; + s->len = 0; + s->h[0] = 0x22312194fc2bf72cULL; + s->h[1] = 0x9f555fa3c84c64c2ULL; + s->h[2] = 0x2393b86b6f53b151ULL; + s->h[3] = 0x963877195940eabdULL; + s->h[4] = 0x96283ee2a88effe3ULL; + s->h[5] = 0xbe5e1e2553863992ULL; + s->h[6] = 0x2b0199fc2c85b8aaULL; + s->h[7] = 0x0eb72ddc81c52ca2ULL; +} + +void +sha512_256_sum(void *ctx, uint8_t md[SHA512_256_DIGEST_LENGTH]) +{ + sha512_sum_n(ctx, md, 4); +} diff --git a/source/sbase/libutil/sha512.c b/source/sbase/libutil/sha512.c new file mode 100644 index 00000000..25264c78 --- /dev/null +++ b/source/sbase/libutil/sha512.c @@ -0,0 +1,175 @@ +/* public domain sha256 implementation based on fips180-3 */ + +#include +#include +#include +#include +#include + +#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_n(void *ctx, uint8_t *md, int n) +{ + struct sha512 *s = ctx; + int i; + + pad(s); + for (i = 0; i < n; 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_sum(void *ctx, uint8_t md[SHA512_DIGEST_LENGTH]) +{ + sha512_sum_n(ctx, md, 8); +} + +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/source/sbase/libutil/strcasestr.c b/source/sbase/libutil/strcasestr.c new file mode 100644 index 00000000..26eb6bbd --- /dev/null +++ b/source/sbase/libutil/strcasestr.c @@ -0,0 +1,38 @@ +/* + * 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 +#include + +#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/source/sbase/libutil/strlcat.c b/source/sbase/libutil/strlcat.c new file mode 100644 index 00000000..bf263b87 --- /dev/null +++ b/source/sbase/libutil/strlcat.c @@ -0,0 +1,63 @@ +/* + * Copyright (c) 1998 Todd C. Miller + * + * 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 +#include + +#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 */ +} + +size_t +estrlcat(char *dst, const char *src, size_t siz) +{ + size_t ret; + + if ((ret = strlcat(dst, src, siz)) >= siz) + eprintf("strlcat: input string too long\n"); + + return ret; +} diff --git a/source/sbase/libutil/strlcpy.c b/source/sbase/libutil/strlcpy.c new file mode 100644 index 00000000..44b618a0 --- /dev/null +++ b/source/sbase/libutil/strlcpy.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 1998 Todd C. Miller + * + * 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 +#include + +#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 */ +} + +size_t +estrlcpy(char *dst, const char *src, size_t siz) +{ + size_t ret; + + if ((ret = strlcpy(dst, src, siz)) >= siz) + eprintf("strlcpy: input string too long\n"); + + return ret; +} diff --git a/source/sbase/libutil/strsep.c b/source/sbase/libutil/strsep.c new file mode 100644 index 00000000..d9f06444 --- /dev/null +++ b/source/sbase/libutil/strsep.c @@ -0,0 +1,37 @@ +/* + * 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 + +#include "../util.h" + +char * +strsep(char **str, const char *sep) +{ + char *s = *str, *end; + if (!s) return NULL; + end = s + strcspn(s, sep); + if (*end) *end++ = 0; + else end = 0; + *str = end; + return s; +} diff --git a/source/sbase/libutil/strtonum.c b/source/sbase/libutil/strtonum.c new file mode 100644 index 00000000..c0ac401f --- /dev/null +++ b/source/sbase/libutil/strtonum.c @@ -0,0 +1,85 @@ +/* $OpenBSD: strtonum.c,v 1.7 2013/04/17 18:40:58 tedu Exp $ */ + +/* + * Copyright (c) 2004 Ted Unangst and Todd Miller + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include "../util.h" + +#define INVALID 1 +#define TOOSMALL 2 +#define TOOLARGE 3 + +long long +strtonum(const char *numstr, long long minval, long long maxval, + const char **errstrp) +{ + long long ll = 0; + int error = 0; + char *ep; + struct errval { + const char *errstr; + int err; + } ev[4] = { + { NULL, 0 }, + { "invalid", EINVAL }, + { "too small", ERANGE }, + { "too large", ERANGE }, + }; + + ev[0].err = errno; + errno = 0; + if (minval > maxval) { + error = INVALID; + } else { + ll = strtoll(numstr, &ep, 10); + if (numstr == ep || *ep != '\0') + error = INVALID; + else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) + error = TOOSMALL; + else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) + error = TOOLARGE; + } + if (errstrp != NULL) + *errstrp = ev[error].errstr; + errno = ev[error].err; + if (error) + ll = 0; + + return (ll); +} + +long long +enstrtonum(int status, const char *numstr, long long minval, long long maxval) +{ + const char *errstr; + long long ll; + + ll = strtonum(numstr, minval, maxval, &errstr); + if (errstr) + enprintf(status, "strtonum %s: %s\n", numstr, errstr); + return ll; +} + +long long +estrtonum(const char *numstr, long long minval, long long maxval) +{ + return enstrtonum(1, numstr, minval, maxval); +} diff --git a/source/sbase/libutil/unescape.c b/source/sbase/libutil/unescape.c new file mode 100644 index 00000000..90a62c3d --- /dev/null +++ b/source/sbase/libutil/unescape.c @@ -0,0 +1,74 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "../util.h" + +size_t +unescape(char *s) +{ + size_t len, i, off, m, factor, q; + + len = strlen(s); + + for (i = 0; i < len; i++) { + if (s[i] != '\\') + continue; + off = 0; + + switch (s[i + 1]) { + case '\\': s[i] = '\\'; off++; break; + case '\'': s[i] = '\'', off++; break; + case '"': s[i] = '"', off++; break; + case 'a': s[i] = '\a'; off++; break; + case 'b': s[i] = '\b'; off++; break; + case 'e': s[i] = 033; off++; break; + case 'f': s[i] = '\f'; off++; break; + case 'n': s[i] = '\n'; off++; break; + case 'r': s[i] = '\r'; off++; break; + case 't': s[i] = '\t'; off++; break; + case 'v': s[i] = '\v'; off++; break; + case 'x': + /* "\xH[H]" hexadecimal escape */ + for (m = i + 2; m < i + 1 + 3 && m < len; m++) + if ((s[m] < '0' && s[m] > '9') && + (s[m] < 'A' && s[m] > 'F') && + (s[m] < 'a' && s[m] > 'f')) + break; + if (m == i + 2) + eprintf("%s: invalid escape sequence '\\%c'\n", argv0, s[i + 1]); + off += m - i - 1; + for (--m, q = 0, factor = 1; m > i + 1; m--) { + if (s[m] >= '0' && s[m] <= '9') + q += (s[m] - '0') * factor; + else if (s[m] >= 'A' && s[m] <= 'F') + q += ((s[m] - 'A') + 10) * factor; + else if (s[m] >= 'a' && s[m] <= 'f') + q += ((s[m] - 'a') + 10) * factor; + factor *= 16; + } + s[i] = q; + break; + case '\0': + eprintf("%s: null escape sequence\n", argv0); + default: + /* "\O[OOO]" octal escape */ + for (m = i + 1; m < i + 1 + 4 && m < len; m++) + if (s[m] < '0' || s[m] > '7') + break; + if (m == i + 1) + eprintf("%s: invalid escape sequence '\\%c'\n", argv0, s[i + 1]); + off += m - i - 1; + for (--m, q = 0, factor = 1; m > i; m--) { + q += (s[m] - '0') * factor; + factor *= 8; + } + s[i] = (q > 255) ? 255 : q; + } + + for (m = i + 1; m <= len - off; m++) + s[m] = s[m + off]; + len -= off; + } + + return len; +} diff --git a/source/sbase/limits_l.h b/source/sbase/limits_l.h new file mode 100644 index 00000000..eaf4242d --- /dev/null +++ b/source/sbase/limits_l.h @@ -0,0 +1,129 @@ +#ifdef _POSIX_CLOCKRES_MIN + {"_POSIX_CLOCKRES_MIN", _POSIX_CLOCKRES_MIN}, +#endif +#ifdef _POSIX_AIO_LISTIO_MAX + {"_POSIX_AIO_LISTIO_MAX", _POSIX_AIO_LISTIO_MAX}, +#endif +#ifdef _POSIX_AIO_MAX + {"_POSIX_AIO_MAX", _POSIX_AIO_MAX}, +#endif +#ifdef _POSIX_ARG_MAX + {"_POSIX_ARG_MAX", _POSIX_ARG_MAX}, +#endif +#ifdef _POSIX_CHILD_MAX + {"_POSIX_CHILD_MAX", _POSIX_CHILD_MAX}, +#endif +#ifdef _POSIX_DELAYTIMER_MAX + {"_POSIX_DELAYTIMER_MAX", _POSIX_DELAYTIMER_MAX}, +#endif +#ifdef _POSIX_HOST_NAME_MAX + {"_POSIX_HOST_NAME_MAX", _POSIX_HOST_NAME_MAX}, +#endif +#ifdef _POSIX_LINK_MAX + {"_POSIX_LINK_MAX", _POSIX_LINK_MAX}, +#endif +#ifdef _POSIX_LOGIN_NAME_MAX + {"_POSIX_LOGIN_NAME_MAX", _POSIX_LOGIN_NAME_MAX}, +#endif +#ifdef _POSIX_MAX_CANON + {"_POSIX_MAX_CANON", _POSIX_MAX_CANON}, +#endif +#ifdef _POSIX_MAX_INPUT + {"_POSIX_MAX_INPUT", _POSIX_MAX_INPUT}, +#endif +#ifdef _POSIX_MQ_OPEN_MAX + {"_POSIX_MQ_OPEN_MAX", _POSIX_MQ_OPEN_MAX}, +#endif +#ifdef _POSIX_MQ_PRIO_MAX + {"_POSIX_MQ_PRIO_MAX", _POSIX_MQ_PRIO_MAX}, +#endif +#ifdef _POSIX_NAME_MAX + {"_POSIX_NAME_MAX", _POSIX_NAME_MAX}, +#endif +#ifdef _POSIX_NGROUPS_MAX + {"_POSIX_NGROUPS_MAX", _POSIX_NGROUPS_MAX}, +#endif +#ifdef _POSIX_OPEN_MAX + {"_POSIX_OPEN_MAX", _POSIX_OPEN_MAX}, +#endif +#ifdef _POSIX_PATH_MAX + {"_POSIX_PATH_MAX", _POSIX_PATH_MAX}, +#endif +#ifdef _POSIX_PIPE_BUF + {"_POSIX_PIPE_BUF", _POSIX_PIPE_BUF}, +#endif +#ifdef _POSIX_RE_DUP_MAX + {"_POSIX_RE_DUP_MAX", _POSIX_RE_DUP_MAX}, +#endif +#ifdef _POSIX_RTSIG_MAX + {"_POSIX_RTSIG_MAX", _POSIX_RTSIG_MAX}, +#endif +#ifdef _POSIX_SEM_NSEMS_MAX + {"_POSIX_SEM_NSEMS_MAX", _POSIX_SEM_NSEMS_MAX}, +#endif +#ifdef _POSIX_SEM_VALUE_MAX + {"_POSIX_SEM_VALUE_MAX", _POSIX_SEM_VALUE_MAX}, +#endif +#ifdef _POSIX_SIGQUEUE_MAX + {"_POSIX_SIGQUEUE_MAX", _POSIX_SIGQUEUE_MAX}, +#endif +#ifdef _POSIX_SSIZE_MAX + {"_POSIX_SSIZE_MAX", _POSIX_SSIZE_MAX}, +#endif +#ifdef _POSIX_SS_REPL_MAX + {"_POSIX_SS_REPL_MAX", _POSIX_SS_REPL_MAX}, +#endif +#ifdef _POSIX_STREAM_MAX + {"_POSIX_STREAM_MAX", _POSIX_STREAM_MAX}, +#endif +#ifdef _POSIX_SYMLINK_MAX + {"_POSIX_SYMLINK_MAX", _POSIX_SYMLINK_MAX}, +#endif +#ifdef _POSIX_SYMLOOP_MAX + {"_POSIX_SYMLOOP_MAX", _POSIX_SYMLOOP_MAX}, +#endif +#ifdef _POSIX_THREAD_DESTRUCTOR_ITERATIONS + {"_POSIX_THREAD_DESTRUCTOR_ITERATIONS", _POSIX_THREAD_DESTRUCTOR_ITERATIONS}, +#endif +#ifdef _POSIX_THREAD_KEYS_MAX + {"_POSIX_THREAD_KEYS_MAX", _POSIX_THREAD_KEYS_MAX}, +#endif +#ifdef _POSIX_THREAD_THREADS_MAX + {"_POSIX_THREAD_THREADS_MAX", _POSIX_THREAD_THREADS_MAX}, +#endif +#ifdef _POSIX_TIMER_MAX + {"_POSIX_TIMER_MAX", _POSIX_TIMER_MAX}, +#endif +#ifdef _POSIX_TTY_NAME_MAX + {"_POSIX_TTY_NAME_MAX", _POSIX_TTY_NAME_MAX}, +#endif +#ifdef _POSIX_TZNAME_MAX + {"_POSIX_TZNAME_MAX", _POSIX_TZNAME_MAX}, +#endif +#ifdef _POSIX2_BC_BASE_MAX + {"_POSIX2_BC_BASE_MAX", _POSIX2_BC_BASE_MAX}, +#endif +#ifdef _POSIX2_BC_DIM_MAX + {"_POSIX2_BC_DIM_MAX", _POSIX2_BC_DIM_MAX}, +#endif +#ifdef _POSIX2_BC_SCALE_MAX + {"_POSIX2_BC_SCALE_MAX", _POSIX2_BC_SCALE_MAX}, +#endif +#ifdef _POSIX2_BC_STRING_MAX + {"_POSIX2_BC_STRING_MAX", _POSIX2_BC_STRING_MAX}, +#endif +#ifdef _POSIX2_CHARCLASS_NAME_MAX + {"_POSIX2_CHARCLASS_NAME_MAX", _POSIX2_CHARCLASS_NAME_MAX}, +#endif +#ifdef _POSIX2_COLL_WEIGHTS_MAX + {"_POSIX2_COLL_WEIGHTS_MAX", _POSIX2_COLL_WEIGHTS_MAX}, +#endif +#ifdef _POSIX2_EXPR_NEST_MAX + {"_POSIX2_EXPR_NEST_MAX", _POSIX2_EXPR_NEST_MAX}, +#endif +#ifdef _POSIX2_LINE_MAX + {"_POSIX2_LINE_MAX", _POSIX2_LINE_MAX}, +#endif +#ifdef _POSIX2_RE_DUP_MAX + {"_POSIX2_RE_DUP_MAX", _POSIX2_RE_DUP_MAX}, +#endif diff --git a/source/sbase/link.1 b/source/sbase/link.1 new file mode 100644 index 00000000..dc07ea60 --- /dev/null +++ b/source/sbase/link.1 @@ -0,0 +1,20 @@ +.Dd 2015-10-08 +.Dt LINK 1 +.Os sbase +.Sh NAME +.Nm link +.Nd call the link function +.Ar target +.Ar name +.Sh DESCRIPTION +.Nm +creates a hard link +.Ar name +to +.Ar target . +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/link.c b/source/sbase/link.c new file mode 100644 index 00000000..e169f0ab --- /dev/null +++ b/source/sbase/link.c @@ -0,0 +1,24 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s target name\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + argv0 = argv[0], argc--, argv++; + + if (argc != 2) + usage(); + + if (link(argv[0], argv[1]) < 0) + eprintf("link:"); + + return 0; +} diff --git a/source/sbase/ln.1 b/source/sbase/ln.1 new file mode 100644 index 00000000..8d299da6 --- /dev/null +++ b/source/sbase/ln.1 @@ -0,0 +1,65 @@ +.Dd 2015-10-08 +.Dt LN 1 +.Os sbase +.Sh NAME +.Nm ln +.Nd link files +.Sh SYNOPSIS +.Nm +.Op Fl f +.Op Fl L | Fl P | Fl s +.Ar target +.Op Ar name +.Nm +.Op Fl f +.Op Fl L | Fl P | Fl s +.Ar target ... +.Ar directory +.Sh DESCRIPTION +.Nm +creates a hard link +.Ar name +to +.Ar target . +If no +.Ar name +is given, a hard link to +.Ar target +is created in the current directory. +If more than one +.Ar target +is given, +.Nm +hardlinks them in the existing +.Ar directory . +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl f +If +.Ar name +exists and is not a +.Ar target , +remove it to allow the link. +.It Fl L | Fl P +If +.Ar target +is a symbolic link, create a hard link to the (referenced file) | +(symbolic link itself). The former is the default. +.It Fl s +Create symbolic links instead of hard links. +Disables +.Fl L +and +.Fl P , +because their purpose does not apply to symbolic links. +.El +.Sh SEE ALSO +.Xr cp 1 , +.Xr link 2 , +.Xr symlink 2 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/ln.c b/source/sbase/ln.c new file mode 100644 index 00000000..ab1ec4e0 --- /dev/null +++ b/source/sbase/ln.c @@ -0,0 +1,101 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-f] [-L | -P | -s] target [name]\n" + " %s [-f] [-L | -P | -s] target ... dir\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + char *targetdir = ".", *target = NULL; + int ret = 0, sflag = 0, fflag = 0, dirfd = AT_FDCWD, + hastarget = 0, flags = AT_SYMLINK_FOLLOW; + struct stat st, tst; + + ARGBEGIN { + case 'f': + fflag = 1; + break; + case 'L': + flags |= AT_SYMLINK_FOLLOW; + break; + case 'P': + flags &= ~AT_SYMLINK_FOLLOW; + break; + case 's': + sflag = 1; + break; + default: + usage(); + } ARGEND + + if (!argc) + usage(); + + if (argc > 1) { + if (!stat(argv[argc - 1], &st) && S_ISDIR(st.st_mode)) { + if ((dirfd = open(argv[argc - 1], O_RDONLY)) < 0) + eprintf("open %s:", argv[argc - 1]); + targetdir = argv[argc - 1]; + if (targetdir[strlen(targetdir) - 1] == '/') + targetdir[strlen(targetdir) - 1] = '\0'; + } else if (argc == 2) { + hastarget = 1; + target = argv[argc - 1]; + } else { + eprintf("%s: not a directory\n", argv[argc - 1]); + } + argv[argc - 1] = NULL; + argc--; + } + + for (; *argv; argc--, argv++) { + if (!hastarget) + target = basename(*argv); + + if (!sflag) { + if (stat(*argv, &st) < 0) { + weprintf("stat %s:", *argv); + ret = 1; + continue; + } else if (fstatat(dirfd, target, &tst, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno != ENOENT) { + weprintf("fstatat %s %s:", targetdir, target); + ret = 1; + continue; + } + } else if (st.st_dev == tst.st_dev && st.st_ino == tst.st_ino) { + weprintf("%s and %s/%s are the same file\n", + *argv, targetdir, target); + ret = 1; + continue; + } + } + + if (fflag && unlinkat(dirfd, target, 0) < 0 && errno != ENOENT) { + weprintf("unlinkat %s %s:", targetdir, target); + ret = 1; + continue; + } + if ((sflag ? symlinkat(*argv, dirfd, target) : + linkat(AT_FDCWD, *argv, dirfd, target, flags)) < 0) { + weprintf("%s %s <- %s/%s:", sflag ? "symlinkat" : "linkat", + *argv, targetdir, target); + ret = 1; + } + } + + return ret; +} diff --git a/source/sbase/logger.1 b/source/sbase/logger.1 new file mode 100644 index 00000000..fee55950 --- /dev/null +++ b/source/sbase/logger.1 @@ -0,0 +1,55 @@ +.Dd 2015-10-08 +.Dt LOGGER 1 +.Os sbase +.Sh NAME +.Nm logger +.Nd make entries in the system log +.Sh SYNOPSIS +.Nm +.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 and writes each +.Ar message +to the log. +If no +.Ar message +is given, +.Nm +logs stdin. +.Sh OPTIONS +.Bl -tag -width xxxxxxxxxxxx +.It Fl i +Add the logger process ID to each line in the log. +.It Fl p Ar priority +Set the message +.Ar priority +given symbolically as a +.Dq facility.level +pair. The default is +.Dq user.notice . +.It Fl s +Also log to stderr. +.It Fl t Ar tag +Add +.Ar tag +to each line in the log. +.El +.Sh SEE ALSO +.Xr syslogd 1 , +.Xr syslog 3 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The +.Op Fl ipst +flags are an extensions to that specification. diff --git a/source/sbase/logger.c b/source/sbase/logger.c new file mode 100644 index 00000000..603da04f --- /dev/null +++ b/source/sbase/logger.c @@ -0,0 +1,91 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#define SYSLOG_NAMES +#include +#include + +#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); + + return -1; /* not reached */ +} + +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[]) +{ + size_t sz; + int logflags = 0, priority = LOG_NOTICE, i; + char *buf = NULL, *tag = NULL; + + 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) { + while (getline(&buf, &sz, stdin) > 0) + syslog(priority, "%s", buf); + } else { + for (i = 0, sz = 0; i < argc; i++) + sz += strlen(argv[i]); + sz += argc; + buf = ecalloc(1, sz); + for (i = 0; i < argc; i++) { + estrlcat(buf, argv[i], sz); + if (i + 1 < argc) + estrlcat(buf, " ", sz); + } + syslog(priority, "%s", buf); + } + + closelog(); + + return fshut(stdin, ""); +} diff --git a/source/sbase/logname.1 b/source/sbase/logname.1 new file mode 100644 index 00000000..cac088e9 --- /dev/null +++ b/source/sbase/logname.1 @@ -0,0 +1,17 @@ +.Dd 2015-10-08 +.Dt LOGNAME 1 +.Os sbase +.Sh NAME +.Nm logname +.Nd show login name +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +.Nm +writes the login name of the current user to stdout. +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/logname.c b/source/sbase/logname.c new file mode 100644 index 00000000..2a591cdc --- /dev/null +++ b/source/sbase/logname.c @@ -0,0 +1,29 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *login; + + argv0 = argv[0], argc--, argv++; + + if (argc) + usage(); + + if ((login = getlogin())) + puts(login); + else + eprintf("no login name\n"); + + return fshut(stdout, ""); +} diff --git a/source/sbase/ls.1 b/source/sbase/ls.1 new file mode 100644 index 00000000..2e2586ec --- /dev/null +++ b/source/sbase/ls.1 @@ -0,0 +1,98 @@ +.Dd 2015-10-08 +.Dt LS 1 +.Os sbase +.Sh NAME +.Nm ls +.Nd list directory contents +.Sh SYNOPSIS +.Nm +.Op Fl iqr +.Op Fl ln +.Op Fl A | a +.Op Fl 1 +.Op Fl h | F | p +.Op Fl H | L +.Op Fl R | d +.Op Fl S | f | t | U +.Op Fl c | u +.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 +List all entries except for '.' and '..'. +.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 all special files. +.It Fl f +Like +.Fl U +but turns on +.Fl a +and disables +.Fl r , +.Fl S +and +.Fl t . +.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 or major and minor numbers if the file is a +character/block device, and last file status/modification time. +.It Fl n +List detailed information about each file, including their type, permissions, +links, owner, group, size or major and minor numbers if the file is a +character/block device, and last file status/modification time, but with +numeric IDs. +.It Fl p +Append a file type indicator to directories. +.It Fl q +Replace non-printable characters in filenames with '?'. +.It Fl R +List directory content recursively. The +.Fl 1 +flag is set implicitly. +.It Fl r +Reverse the sort order. +.It Fl S +Sort files by size (in decreasing order). +.It Fl t +Sort files by last file status/modification time instead of by name. +.It Fl U +Keep the list unsorted. +.It Fl u +Use file's last access time instead of last modification time for +sorting or printing. +.El +.Sh SEE ALSO +.Xr stat 2 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification except from the +.Op Fl Ckmpsx +flags. +.Pp +The +.Op Fl hU +flags are an extension to that specification. diff --git a/source/sbase/ls.c b/source/sbase/ls.c new file mode 100644 index 00000000..3268a995 --- /dev/null +++ b/source/sbase/ls.c @@ -0,0 +1,483 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utf.h" +#include "util.h" + +struct entry { + char *name; + mode_t mode, tmode; + nlink_t nlink; + uid_t uid; + gid_t gid; + off_t size; + struct timespec t; + dev_t dev; + dev_t rdev; + ino_t ino, tino; +}; + +static struct { + dev_t dev; + ino_t ino; +} tree[PATH_MAX]; + +static int ret = 0; +static int Aflag = 0; +static int aflag = 0; +static int cflag = 0; +static int dflag = 0; +static int Fflag = 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 nflag = 0; +static int pflag = 0; +static int qflag = 0; +static int Rflag = 0; +static int rflag = 0; +static int Uflag = 0; +static int uflag = 0; +static int first = 1; +static char sort = 0; +static size_t ds = 0; + +static void ls(const char *, const struct entry *, int); + +static void +mkent(struct 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; + if (cflag) + ent->t = st.st_ctim; + else if (uflag) + ent->t = st.st_atim; + else + ent->t = st.st_mtim; + ent->dev = st.st_dev; + ent->rdev = st.st_rdev; + ent->ino = st.st_ino; + if (S_ISLNK(ent->mode)) { + if (stat(path, &st) == 0) { + ent->tmode = st.st_mode; + ent->dev = st.st_dev; + ent->tino = st.st_ino; + } else { + ent->tmode = ent->tino = 0; + } + } +} + +static char * +indicator(mode_t mode) +{ + if (pflag || Fflag) + if (S_ISDIR(mode)) + return "/"; + + if (Fflag) { + if (S_ISLNK(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 "*"; + } + + return ""; +} + +static void +output(const struct entry *ent) +{ + struct group *gr; + struct passwd *pw; + ssize_t len; + size_t l; + char *name, *c, *u, *fmt, buf[BUFSIZ], + pwname[_SC_LOGIN_NAME_MAX], grname[_SC_LOGIN_NAME_MAX], + mode[] = "----------"; + Rune r; + + if (qflag) { + name = emalloc(strlen(ent->name) + 1); + + for (c = name, u = ent->name; *u; u += l) { + l = chartorune(&r, u); + if (isprintrune(r)) { + memcpy(c, u, l); + c += l; + } else { + *c++ = '?'; + } + } + *c = '\0'; + } else { + name = ent->name; + } + + if (iflag) + printf("%lu ", (unsigned long)ent->ino); + if (!lflag) { + printf("%s%s\n", name, indicator(ent->mode)); + goto cleanup; + } + 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'; + + if (!nflag && (pw = getpwuid(ent->uid))) + snprintf(pwname, sizeof(pwname), "%s", pw->pw_name); + else + snprintf(pwname, sizeof(pwname), "%d", ent->uid); + + if (!nflag && (gr = getgrgid(ent->gid))) + snprintf(grname, sizeof(grname), "%s", gr->gr_name); + else + snprintf(grname, sizeof(grname), "%d", ent->gid); + + if (time(NULL) > ent->t.tv_sec + (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.tv_sec)); + printf("%s %4ld %-8.8s %-8.8s ", mode, (long)ent->nlink, pwname, grname); + + if (S_ISBLK(ent->mode) || S_ISCHR(ent->mode)) + printf("%4u, %4u ", major(ent->rdev), minor(ent->rdev)); + else if (hflag) + printf("%10s ", humansize(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'); + +cleanup: + if (qflag) + free(name); +} + +static int +entcmp(const void *va, const void *vb) +{ + int cmp = 0; + const struct entry *a = va, *b = vb; + + switch (sort) { + case 'S': + cmp = b->size - a->size; + break; + case 't': + if (!(cmp = b->t.tv_sec - a->t.tv_sec)) + cmp = b->t.tv_nsec - a->t.tv_nsec; + break; + } + + if (!cmp) + cmp = strcmp(a->name, b->name); + + return rflag ? 0 - cmp : cmp; +} + +static void +lsdir(const char *path, const struct entry *dir) +{ + DIR *dp; + struct entry *ent, *ents = NULL; + struct dirent *d; + size_t i, n = 0; + char prefix[PATH_MAX]; + + if (!(dp = opendir(dir->name))) { + ret = 1; + weprintf("opendir %s%s:", path, dir->name); + return; + } + if (chdir(dir->name) < 0) + eprintf("chdir %s:", dir->name); + + while ((d = readdir(dp))) { + if (d->d_name[0] == '.' && !aflag && !Aflag) + continue; + else if (Aflag) + if (strcmp(d->d_name, ".") == 0 || + strcmp(d->d_name, "..") == 0) + continue; + + ents = ereallocarray(ents, ++n, sizeof(*ents)); + mkent(&ents[n - 1], estrdup(d->d_name), Fflag || iflag || + lflag || pflag || Rflag || sort, Lflag); + } + + closedir(dp); + + if (!Uflag) + qsort(ents, n, sizeof(*ents), entcmp); + + if (ds++) + printf("%s:\n", dir->name); + for (i = 0; i < n; i++) + output(&ents[i]); + + if (Rflag) { + if (snprintf(prefix, PATH_MAX, "%s%s/", path, dir->name) >= + PATH_MAX) + eprintf("path too long: %s%s\n", path, dir->name); + + for (i = 0; i < n; i++) { + ent = &ents[i]; + if (strcmp(ent->name, ".") == 0 || + strcmp(ent->name, "..") == 0) + continue; + if (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode) && !Lflag) + continue; + + ls(prefix, ent, Rflag); + } + } + + for (i = 0; i < n; ++i) + free(ents[i].name); + free(ents); +} + +static int +visit(const struct entry *ent) +{ + dev_t dev; + ino_t ino; + int i; + + dev = ent->dev; + ino = S_ISLNK(ent->mode) ? ent->tino : ent->ino; + + for (i = 0; i < PATH_MAX && tree[i].ino; ++i) { + if (ino == tree[i].ino && dev == tree[i].dev) + return -1; + } + + tree[i].ino = ino; + tree[i].dev = dev; + + return i; +} + +static void +ls(const char *path, const struct entry *ent, int listdir) +{ + int treeind; + char cwd[PATH_MAX]; + + if (!listdir) { + output(ent); + } else if (S_ISDIR(ent->mode) || + (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode))) { + if ((treeind = visit(ent)) < 0) { + ret = 1; + weprintf("%s%s: Already visited\n", path, ent->name); + return; + } + + if (!getcwd(cwd, PATH_MAX)) + eprintf("getcwd:"); + + if (first) + first = !first; + else + putchar('\n'); + + fputs(path, stdout); + lsdir(path, ent); + tree[treeind].ino = 0; + + if (chdir(cwd) < 0) + eprintf("chdir %s:", cwd); + } +} + +static void +usage(void) +{ + eprintf("usage: %s [-1AacdFfHhiLlnpqRrtUu] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct entry ent, *dents, *fents; + size_t i, fs; + + ARGBEGIN { + case '1': + /* force output to 1 entry per line */ + qflag = 1; + break; + case 'A': + Aflag = 1; + break; + case 'a': + aflag = 1; + break; + case 'c': + cflag = 1; + uflag = 0; + break; + case 'd': + dflag = 1; + break; + case 'f': + aflag = 1; + fflag = 1; + Uflag = 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 'n': + lflag = 1; + nflag = 1; + break; + case 'p': + pflag = 1; + break; + case 'q': + qflag = 1; + break; + case 'R': + Rflag = 1; + break; + case 'r': + rflag = 1; + break; + case 'S': + sort = 'S'; + break; + case 't': + sort = 't'; + break; + case 'U': + Uflag = 1; + break; + case 'u': + uflag = 1; + cflag = 0; + break; + default: + usage(); + } ARGEND + + switch (argc) { + case 0: /* fallthrough */ + *--argv = ".", ++argc; + case 1: + mkent(&ent, argv[0], 1, Hflag || Lflag); + ls("", &ent, (!dflag && S_ISDIR(ent.mode)) || + ((S_ISLNK(ent.mode) && S_ISDIR(ent.tmode)) && + ((Hflag || Lflag) || !(dflag || Fflag || lflag)))); + + break; + default: + for (i = ds = fs = 0, fents = dents = NULL; i < argc; ++i) { + mkent(&ent, argv[i], 1, Hflag || Lflag); + + if ((!dflag && S_ISDIR(ent.mode)) || + ((S_ISLNK(ent.mode) && S_ISDIR(ent.tmode)) && + ((Hflag || Lflag) || !(dflag || Fflag || lflag)))) { + dents = ereallocarray(dents, ++ds, sizeof(*dents)); + memcpy(&dents[ds - 1], &ent, sizeof(ent)); + } else { + fents = ereallocarray(fents, ++fs, sizeof(*fents)); + memcpy(&fents[fs - 1], &ent, sizeof(ent)); + } + } + + qsort(fents, fs, sizeof(ent), entcmp); + qsort(dents, ds, sizeof(ent), entcmp); + + for (i = 0; i < fs; ++i) + ls("", &fents[i], 0); + free(fents); + if (fs && ds) + putchar('\n'); + for (i = 0; i < ds; ++i) + ls("", &dents[i], 1); + free(dents); + } + + return (fshut(stdout, "") | ret); +} diff --git a/source/sbase/md5.h b/source/sbase/md5.h new file mode 100644 index 00000000..0b5005e9 --- /dev/null +++ b/source/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/source/sbase/md5sum.1 b/source/sbase/md5sum.1 new file mode 100644 index 00000000..bd02bb79 --- /dev/null +++ b/source/sbase/md5sum.1 @@ -0,0 +1,32 @@ +.Dd 2015-10-08 +.Dt MD5SUM 1 +.Os sbase +.Sh NAME +.Nm md5sum +.Nd compute or check MD5 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes MD5 (128-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of MD5 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/source/sbase/md5sum.c b/source/sbase/md5sum.c new file mode 100644 index 00000000..86fb40ff --- /dev/null +++ b/source/sbase/md5sum.c @@ -0,0 +1,42 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#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[]) +{ + int ret = 0, (*cryptfunc)(int, char **, struct crypt_ops *, uint8_t *, size_t) = cryptmain; + uint8_t md[MD5_DIGEST_LENGTH]; + + ARGBEGIN { + case 'c': + cryptfunc = cryptcheck; + break; + default: + usage(); + } ARGEND + + ret |= cryptfunc(argc, argv, &md5_ops, md, sizeof(md)); + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/mkdir.1 b/source/sbase/mkdir.1 new file mode 100644 index 00000000..da6198e0 --- /dev/null +++ b/source/sbase/mkdir.1 @@ -0,0 +1,37 @@ +.Dd 2015-10-08 +.Dt MKDIR 1 +.Os sbase +.Sh NAME +.Nm mkdir +.Nd create directories +.Sh SYNOPSIS +.Nm +.Op Fl p +.Op Fl m Ar mode +.Ar name ... +.Sh DESCRIPTION +.Nm +creates a directory for each +.Ar name +if it does not already exist. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl m +Set the file +.Ar mode +of newly created directories. See +.Xr chmod 1 . +.It Fl p +Also create necessary parent directories and +do not fail if +.Ar name +already exists. +.El +.Sh SEE ALSO +.Xr mkdir 2 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/mkdir.c b/source/sbase/mkdir.c new file mode 100644 index 00000000..f5da5279 --- /dev/null +++ b/source/sbase/mkdir.c @@ -0,0 +1,53 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-p] [-m mode] name ...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + mode_t mode = 0, mask; + int pflag = 0, mflag = 0, ret = 0; + + ARGBEGIN { + case 'p': + pflag = 1; + break; + case 'm': + mflag = 1; + mask = getumask(); + mode = parsemode(EARGF(usage()), mode, mask); + break; + default: + usage(); + } ARGEND + + if (!argc) + usage(); + + for (; *argv; argc--, argv++) { + if (pflag) { + if (mkdirp(*argv) < 0) + ret = 1; + } else if (mkdir(*argv, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && + errno != EEXIST) { + weprintf("mkdir %s:", *argv); + ret = 1; + } + if (mflag && chmod(*argv, mode) < 0) { + weprintf("chmod %s:", *argv); + ret = 1; + } + } + + return ret; +} diff --git a/source/sbase/mkfifo.1 b/source/sbase/mkfifo.1 new file mode 100644 index 00000000..0c6ef946 --- /dev/null +++ b/source/sbase/mkfifo.1 @@ -0,0 +1,31 @@ +.Dd 2015-10-08 +.Dt MKFIFO 1 +.Os sbase +.Sh NAME +.Nm mkfifo +.Nd create named pipes +.Sh SYNOPSIS +.Nm +.Op Fl m Ar mode +.Ar name ... +.Sh DESCRIPTION +.Nm +creates a named pipe for each +.Ar name +if it does not already exist. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl m +Set the file +.Ar mode +of newly created named pipes. See +.Xr chmod 1 . +.El +.Sh SEE ALSO +.Xr mkfifo 3 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/mkfifo.c b/source/sbase/mkfifo.c new file mode 100644 index 00000000..a09f4f5f --- /dev/null +++ b/source/sbase/mkfifo.c @@ -0,0 +1,47 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-m mode] name ...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + mode_t mode = 0, mask; + int mflag = 0, ret = 0; + + ARGBEGIN { + case 'm': + mflag = 1; + mask = getumask(); + mode = parsemode(EARGF(usage()), mode, mask); + break; + default: + usage(); + } ARGEND + + if (!argc) + usage(); + + for (; *argv; argc--, argv++) { + if (mkfifo(*argv, S_IRUSR | S_IWUSR | S_IRGRP | + S_IWGRP | S_IROTH | S_IWOTH) < 0) { + weprintf("mkfifo %s:", *argv); + ret = 1; + } else if (mflag) { + if (chmod(*argv, mode) < 0) { + weprintf("chmod %s:", *argv); + ret = 1; + } + } + } + + return ret; +} diff --git a/source/sbase/mktemp.1 b/source/sbase/mktemp.1 new file mode 100644 index 00000000..77a0ae85 --- /dev/null +++ b/source/sbase/mktemp.1 @@ -0,0 +1,46 @@ +.Dd 2015-10-08 +.Dt MKTEMP 1 +.Os sbase +.Sh NAME +.Nm mktemp +.Nd create temporary file or directory +.Sh SYNOPSIS +.Nm +.Op Fl dqtu +.Op Fl p Ar directory +.Op Ar template +.Sh DESCRIPTION +.Nm +creates a temporary file by generating a unique filename with +.Ar template , +which has to have at least six 'X's appended to it. If no +.Ar template +is specified, a default of 'tmp.XXXXXXXXXX' is used and the +tmpdir set to '/tmp' or, if set, the TMPDIR environment variable. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl d +Create a temporary directory instead of a file. +.It Fl p Ar directory +Use the specified +.Ar directory +as a prefix when generating the temporary filename. The directory will be +overridden by the user's +.Ev TMPDIR +environment variable if it is set. This option implies the +.Fl t +flag (see below). +.It Fl q +Fail silently if an error occurs. +.It Fl t +Generate a path rooted in a temporary directory. +.It Fl u +Unlink file before +.Nm +exits. This is slightly better than +.Xr mktemp 3 +but still introduces a race condition. Use of this option is not encouraged. +.El +.Sh SEE ALSO +.Xr mkdtemp 3 , +.Xr mkstemp 3 diff --git a/source/sbase/mktemp.c b/source/sbase/mktemp.c new file mode 100644 index 00000000..a3076ba2 --- /dev/null +++ b/source/sbase/mktemp.c @@ -0,0 +1,92 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-dqtu] [-p directory] [template]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int dflag = 0, pflag = 0, qflag = 0, tflag = 0, uflag = 0, fd; + char *template = "tmp.XXXXXXXXXX", *tmpdir = "", *pdir, + *p, path[PATH_MAX], tmp[PATH_MAX]; + size_t len; + + ARGBEGIN { + case 'd': + dflag = 1; + break; + case 'p': + pflag = 1; + pdir = EARGF(usage()); + break; + case 'q': + qflag = 1; + break; + case 't': + tflag = 1; + break; + case 'u': + uflag = 1; + break; + default: + usage(); + } ARGEND + + if (argc > 1) + usage(); + else if (argc == 1) + template = argv[0]; + + if (!argc || pflag || tflag) { + if ((p = getenv("TMPDIR"))) + tmpdir = p; + else if (pflag) + tmpdir = pdir; + else + tmpdir = "/tmp"; + } + + len = estrlcpy(path, tmpdir, sizeof(path)); + if (path[0] && path[len - 1] != '/') + estrlcat(path, "/", sizeof(path)); + + estrlcpy(tmp, template, sizeof(tmp)); + p = dirname(tmp); + if (!(p[0] == '.' && p[1] == '\0')) { + if (tflag && !pflag) + eprintf("template must not contain directory separators in -t mode\n"); + } + estrlcat(path, template, sizeof(path)); + + if (dflag) { + if (!mkdtemp(path)) { + if (!qflag) + eprintf("mkdtemp %s:", path); + return 1; + } + } else { + if ((fd = mkstemp(path)) < 0) { + if (!qflag) + eprintf("mkstemp %s:", path); + return 1; + } + if (close(fd)) + eprintf("close %s:", path); + } + if (uflag) + unlink(path); + puts(path); + + efshut(stdout, ""); + return 0; +} diff --git a/source/sbase/mv.1 b/source/sbase/mv.1 new file mode 100644 index 00000000..c69c3677 --- /dev/null +++ b/source/sbase/mv.1 @@ -0,0 +1,39 @@ +.Dd 2015-10-08 +.Dt MV 1 +.Os sbase +.Sh NAME +.Nm mv +.Nd move files and directories +.Sh SYNOPSIS +.Nm +.Op Fl f +.Ar source ... +.Ar dest +.Sh DESCRIPTION +.Nm +moves each +.Ar source +to +.Ar dest . +If only one +.Ar source +is given and +.Ar dest +is not a directory, +.Nm +overwrites the latter with the former. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl f +Do not prompt before overwriting. +.Ar dest . +Prompting has not been implemented yet. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification except from the +.Op Fl i +flag. diff --git a/source/sbase/mv.c b/source/sbase/mv.c new file mode 100644 index 00000000..2cf98d12 --- /dev/null +++ b/source/sbase/mv.c @@ -0,0 +1,62 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include + +#include "fs.h" +#include "util.h" + +static int mv_status = 0; + +static int +mv(const char *s1, const char *s2, int depth) +{ + struct recursor r = { .fn = rm, .hist = NULL, .depth = 0, .maxdepth = 0, + .follow = 'P', .flags = 0 }; + + if (!rename(s1, s2)) + return (mv_status = 0); + if (errno == EXDEV) { + cp_aflag = cp_rflag = cp_pflag = 1; + cp_follow = 'P'; + cp(s1, s2, depth); + recurse(s1, NULL, &r); + return (mv_status = cp_status || rm_status); + } + mv_status = 1; + + return -1; +} + +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 > 2) { + if (stat(argv[argc - 1], &st) < 0) + eprintf("stat %s:", argv[argc - 1]); + if (!S_ISDIR(st.st_mode)) + eprintf("%s: not a directory\n", argv[argc - 1]); + } + enmasse(argc, argv, mv); + + return mv_status; +} diff --git a/source/sbase/nice.1 b/source/sbase/nice.1 new file mode 100644 index 00000000..4be627ae --- /dev/null +++ b/source/sbase/nice.1 @@ -0,0 +1,40 @@ +.Dd 2015-10-08 +.Dt NICE 1 +.Os sbase +.Sh NAME +.Nm nice +.Nd run command with altered niceness +.Sh SYNOPSIS +.Nm +.Op Fl n Ar inc +.Ar cmd +.Op Ar arg ... +.Sh DESCRIPTION +.Nm +runs +.Ar cmd +with the current niceness plus +.Ar inc . +A negative niceness is reserved to the superuser. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl n Ar inc +Change niceness by +.Ar inc , +ranging from +.Sy -20 +(highest priority) +to +.Sy +20 +(lowest priority). +Default is 10. +.El +.Sh SEE ALSO +.Xr nice 2 , +.Xr renice 2 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/nice.c b/source/sbase/nice.c new file mode 100644 index 00000000..d036e26c --- /dev/null +++ b/source/sbase/nice.c @@ -0,0 +1,56 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include + +#include "util.h" + +#ifndef PRIO_MIN +#define PRIO_MIN -NZERO +#endif + +#ifndef PRIO_MAX +#define PRIO_MAX (NZERO-1) +#endif + +static void +usage(void) +{ + eprintf("usage: %s [-n inc] cmd [arg ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int val = 10, r, savederrno; + + ARGBEGIN { + case 'n': + val = estrtonum(EARGF(usage()), PRIO_MIN, PRIO_MAX); + break; + default: + usage(); + break; + } ARGEND + + if (!argc) + usage(); + + errno = 0; + r = getpriority(PRIO_PROCESS, 0); + if (errno) + weprintf("getpriority:"); + else + val += r; + LIMIT(val, PRIO_MIN, PRIO_MAX); + if (setpriority(PRIO_PROCESS, 0, val) < 0) + weprintf("setpriority:"); + + execvp(argv[0], argv); + savederrno = errno; + weprintf("execvp %s:", argv[0]); + + _exit(126 + (savederrno == ENOENT)); +} diff --git a/source/sbase/nl.1 b/source/sbase/nl.1 new file mode 100644 index 00000000..183ada61 --- /dev/null +++ b/source/sbase/nl.1 @@ -0,0 +1,97 @@ +.Dd 2015-10-08 +.Dt NL 1 +.Os sbase +.Sh NAME +.Nm nl +.Nd line numbering filter +.Sh SYNOPSIS +.Nm +.Op Fl p +.Op Fl b Ar type +.Op Fl d Ar delim +.Op Fl f Ar type +.Op Fl h Ar type +.Op Fl i Ar num +.Op Fl l Ar num +.Op Fl n Ar format +.Op Fl s Ar sep +.Op Fl v Ar num +.Op Fl w Ar num +.Op Ar file +.Sh DESCRIPTION +.Nm +reads lines from +.Ar file +and writes them to stdout, numbering non-empty lines. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl p +Do not reset line number for logical pages. +.It Fl h Ar type | Fl b Ar type | Fl f Ar type +Define which lines to number in the head | body | footer section: +.Bl -tag -width pstringXX +.It a +All lines. +.It n +No lines. +.It t +Only non-empty lines. This is the default. +.It p Ns Ar expr +Only lines matching +.Ar expr +according to +.Xr regex 7 . +.El +.It Fl d Ar delim +Set +.Ar delim +as the delimiter for logical pages. If +.Ar delim +is only one character, +.Nm +appends ":" to it. The default is "\e:". +.It Fl i Ar num +Set the increment between numbered lines to +.Ar num . +.It Fl l Ar num +Set the number of adjacent blank lines to be considered as one to +.Ar num . +The default is 1. +.It Fl n Ar format +Set the line number output +.Ar format +to one of: +.Bl -tag -width pstringXX +.It ln +Left justified. +.It rn +Right justified. This is the default. +.It rz +Right justified with leading zeroes. +.El +.It Fl s Ar sep +Use +.Ar sep +to separate line numbers and lines. The default is "\et". +.It Fl v Ar num +Start counting lines from +.Ar num . +The default is 1. +.It Fl w Ar num +Set the width of the line number to +.Ar num . +The default is 6. +.El +.Sh SEE ALSO +.Xr pr 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/nl.c b/source/sbase/nl.c new file mode 100644 index 00000000..9a289b02 --- /dev/null +++ b/source/sbase/nl.c @@ -0,0 +1,212 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "text.h" +#include "utf.h" +#include "util.h" + +static size_t startnum = 1; +static size_t incr = 1; +static size_t blines = 1; +static size_t delimlen = 2; +static size_t seplen = 1; +static int width = 6; +static int pflag = 0; +static char type[] = { 'n', 't', 'n' }; /* footer, body, header */ +static char *delim = "\\:"; +static char format[6] = "%*ld"; +static char *sep = "\t"; +static regex_t preg[3]; + +static int +getsection(struct line *l, int *section) +{ + size_t i; + int sectionchanged = 0, newsection = *section; + + for (i = 0; (l->len - i) >= delimlen && + !memcmp(l->data + i, delim, delimlen); i += delimlen) { + if (!sectionchanged) { + sectionchanged = 1; + newsection = 0; + } else { + newsection = (newsection + 1) % 3; + } + } + + if (!(l->len - i) || l->data[i] == '\n') + *section = newsection; + else + sectionchanged = 0; + + return sectionchanged; +} + +static void +nl(const char *fname, FILE *fp) +{ + static struct line line; + static size_t size; + size_t number = startnum, bl = 1; + ssize_t len; + int donumber, oldsection, section = 1; + + while ((len = getline(&line.data, &size, fp)) > 0) { + line.len = len; + donumber = 0; + oldsection = section; + + if (getsection(&line, §ion)) { + if ((section >= oldsection) && !pflag) + number = startnum; + continue; + } + + switch (type[section]) { + case 't': + if (line.data[0] != '\n') + donumber = 1; + break; + case 'p': + if (!regexec(preg + section, line.data, 0, NULL, 0)) + donumber = 1; + break; + case 'a': + if (line.data[0] == '\n' && bl < blines) { + ++bl; + } else { + donumber = 1; + bl = 1; + } + } + + if (donumber) { + printf(format, width, number); + fwrite(sep, 1, seplen, stdout); + number += incr; + } + fwrite(line.data, 1, line.len, stdout); + } + free(line.data); + if (ferror(fp)) + eprintf("getline %s:", fname); +} + +static void +usage(void) +{ + eprintf("usage: %s [-p] [-b type] [-d delim] [-f type]\n" + " [-h type] [-i num] [-l num] [-n format]\n" + " [-s sep] [-v num] [-w num] [file]\n", argv0); +} + +static char +getlinetype(char *type, regex_t *preg) +{ + if (type[0] == 'p') + eregcomp(preg, type + 1, REG_NOSUB); + else if (!type[0] || !strchr("ant", type[0])) + usage(); + + return type[0]; +} + +int +main(int argc, char *argv[]) +{ + FILE *fp = NULL; + size_t s; + int ret = 0; + char *d, *formattype, *formatblit; + + ARGBEGIN { + case 'd': + switch (utflen((d = EARGF(usage())))) { + case 0: + eprintf("empty logical page delimiter\n"); + case 1: + s = strlen(d); + delim = emalloc(s + 1 + 1); + estrlcpy(delim, d, s + 1 + 1); + estrlcat(delim, ":", s + 1 + 1); + delimlen = s + 1; + break; + default: + delim = d; + delimlen = strlen(delim); + break; + } + break; + case 'f': + type[0] = getlinetype(EARGF(usage()), preg); + break; + case 'b': + type[1] = getlinetype(EARGF(usage()), preg + 1); + break; + case 'h': + type[2] = getlinetype(EARGF(usage()), preg + 2); + break; + case 'i': + incr = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, SIZE_MAX)); + break; + case 'l': + blines = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, SIZE_MAX)); + break; + case 'n': + formattype = EARGF(usage()); + estrlcpy(format, "%", sizeof(format)); + + if (!strcmp(formattype, "ln")) { + formatblit = "-"; + } else if (!strcmp(formattype, "rn")) { + formatblit = ""; + } else if (!strcmp(formattype, "rz")) { + formatblit = "0"; + } else { + eprintf("%s: bad format\n", formattype); + } + + estrlcat(format, formatblit, sizeof(format)); + estrlcat(format, "*ld", sizeof(format)); + break; + case 'p': + pflag = 1; + break; + case 's': + sep = EARGF(usage()); + seplen = unescape(sep); + break; + case 'v': + startnum = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, SIZE_MAX)); + break; + case 'w': + width = estrtonum(EARGF(usage()), 1, INT_MAX); + break; + default: + usage(); + } ARGEND + + if (argc > 1) + usage(); + + if (!argc) { + nl("", stdin); + } else { + if (!strcmp(argv[0], "-")) { + argv[0] = ""; + fp = stdin; + } else if (!(fp = fopen(argv[0], "r"))) { + eprintf("fopen %s:", argv[0]); + } + nl(argv[0], fp); + } + + ret |= fp && fp != stdin && fshut(fp, argv[0]); + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/nohup.1 b/source/sbase/nohup.1 new file mode 100644 index 00000000..78930a0e --- /dev/null +++ b/source/sbase/nohup.1 @@ -0,0 +1,44 @@ +.Dd 2015-10-08 +.Dt NOHUP 1 +.Os sbase +.Sh NAME +.Nm nohup +.Nd run command immune to hangups +.Sh SYNOPSIS +.Nm +.Ar cmd +.Op Ar arg ... +.Sh DESCRIPTION +.Nm +runs +.Ar cmd +with the +.Em HUP +signal set to be ignored. +.Pp +If stdout is a terminal, it is appended to +.Em nohup.out +in the current working directory. +If stderr is a terminal, it is redirected to stdout. +.Sh EXIT STATUS +.Bl -tag -width Ds +.It 0 +.Ar cmd +executed successfully. +.It 1 +Internal error. +.It 126 +.Ar cmd +was found but could not be executed. +.It 127 +.Ar cmd +could not be found. +.El +.Sh SEE ALSO +.Xr signal 7 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/nohup.c b/source/sbase/nohup.c new file mode 100644 index 00000000..080b2108 --- /dev/null +++ b/source/sbase/nohup.c @@ -0,0 +1,45 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s cmd [arg ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int fd, savederrno; + + argv0 = argv[0], argc--, argv++; + + if (!argc) + usage(); + + if (signal(SIGHUP, SIG_IGN) == SIG_ERR) + enprintf(127, "signal HUP:"); + + if (isatty(STDOUT_FILENO)) { + if ((fd = open("nohup.out", O_APPEND | O_CREAT, S_IRUSR | S_IWUSR)) < 0) + enprintf(127, "open nohup.out:"); + if (dup2(fd, STDOUT_FILENO) < 0) + enprintf(127, "dup2:"); + close(fd); + } + if (isatty(STDERR_FILENO) && dup2(STDOUT_FILENO, STDERR_FILENO) < 0) + enprintf(127, "dup2:"); + + execvp(argv[0], argv); + savederrno = errno; + weprintf("execvp %s:", argv[0]); + + _exit(126 + (savederrno == ENOENT)); +} diff --git a/source/sbase/od.1 b/source/sbase/od.1 new file mode 100644 index 00000000..99f3e326 --- /dev/null +++ b/source/sbase/od.1 @@ -0,0 +1,65 @@ +.Dd 2015-10-25 +.Dt OD 1 +.Os sbase +.Sh NAME +.Nm od +.Nd octal dump +.Sh SYNOPSIS +.Nm +.Op Fl A Ar addrformat +.Op Fl E | e +.Op Fl j Ar skip +.Op Fl t Ar outputformat... +.Op Fl v +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes an octal dump of each +.Ar file +to stdout. If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl A Ar addressformat +.Ar addressformat +is one of d|o|x|n and sets the address to be +either in \fId\fRecimal, \fIo\fRctal, he\fIx\fRadecimal or \fIn\fRot +printed at all. The default is octal. +.It Fl E | e +Force Little Endian +.Fl ( e ) +or Big Endian +.Fl ( E ) +system-independently. +.It Fl j Ar skip +Ignore the first +.Ar skip +bytes of input. +.It Fl t Ar outputformat +.Ar outputformat +is a list of a|c|d|o|u|x followed by a digit or C|S|I|L and sets +the content to be in n\fIa\fRmed character, \fIc\fRharacter, signed +\fId\fRecimal, \fIo\fRctal, \fIu\fRnsigned decimal, or +he\fIx\fRadecimal format, processing the given amount of bytes or the length +of \fIC\fRhar, \fIS\fRhort, \fII\fRnteger or \fIL\fRong. +The default is octal with 4 bytes. +.It Fl v +Always set. Write all input data, including duplicate lines. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification except that the +.Op Fl v +flag is always enabled and the 'd' parameter for the +.Op Fl t +flag is interpreted as 'u'. +.Pp +The +.Op Ee +flags are an extension to that specification. diff --git a/source/sbase/od.c b/source/sbase/od.c new file mode 100644 index 00000000..5a625b62 --- /dev/null +++ b/source/sbase/od.c @@ -0,0 +1,293 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "queue.h" +#include "util.h" + +struct type { + unsigned char format; + unsigned int len; + TAILQ_ENTRY(type) entry; +}; + +static TAILQ_HEAD(head, type) head = TAILQ_HEAD_INITIALIZER(head); +static unsigned char addr_format = 'o'; +static off_t skip = 0; +static off_t max = -1; +static size_t linelen = 1; +static int big_endian; + +static void +printaddress(off_t addr) +{ + char fmt[] = "%07j#"; + + if (addr_format == 'n') { + fputc(' ', stdout); + } else { + fmt[4] = addr_format; + printf(fmt, (intmax_t)addr); + } +} + +static void +printchunk(unsigned char *s, unsigned char format, size_t len) +{ + long long res, basefac; + size_t i; + char fmt[] = " %#*ll#"; + + const char *namedict[] = { + "nul", "soh", "stx", "etx", "eot", "enq", "ack", + "bel", "bs", "ht", "nl", "vt", "ff", "cr", + "so", "si", "dle", "dc1", "dc2", "dc3", "dc4", + "nak", "syn", "etb", "can", "em", "sub", "esc", + "fs", "gs", "rs", "us", "sp", + }; + const char *escdict[] = { + ['\0'] = "\\0", ['\a'] = "\\a", + ['\b'] = "\\b", ['\t'] = "\\t", + ['\n'] = "\\n", ['\v'] = "\\v", + ['\f'] = "\\f", ['\r'] = "\\r", + }; + + switch (format) { + case 'a': + *s &= ~128; /* clear high bit as required by standard */ + if (*s < LEN(namedict) || *s == 127) { + printf(" %3s", (*s == 127) ? "del" : namedict[*s]); + } else { + printf(" %3c", *s); + } + break; + case 'c': + if (strchr("\a\b\t\n\v\f\r\0", *s)) { + printf(" %3s", escdict[*s]); + } else { + printf(" %3c", *s); + } + break; + default: + if (big_endian) { + for (res = 0, basefac = 1, i = len; i; i--) { + res += s[i - 1] * basefac; + basefac <<= 8; + } + } else { + for (res = 0, basefac = 1, i = 0; i < len; i++) { + res += s[i] * basefac; + basefac <<= 8; + } + } + fmt[2] = big_endian ? '-' : ' '; + fmt[6] = format; + printf(fmt, (int)(3 * len + len - 1), res); + } +} + +static void +printline(unsigned char *line, size_t len, off_t addr) +{ + struct type *t = NULL; + size_t i; + int first = 1; + unsigned char *tmp; + + if (TAILQ_EMPTY(&head)) + goto once; + TAILQ_FOREACH(t, &head, entry) { +once: + if (first) { + printaddress(addr); + first = 0; + } else { + printf("%*c", (addr_format == 'n') ? 1 : 7, ' '); + } + for (i = 0; i < len; i += MIN(len - i, t ? t->len : 4)) { + if (len - i < (t ? t->len : 4)) { + tmp = ecalloc(t ? t->len : 4, 1); + memcpy(tmp, line + i, len - i); + printchunk(tmp, t ? t->format : 'o', + t ? t->len : 4); + free(tmp); + } else { + printchunk(line + i, t ? t->format : 'o', + t ? t->len : 4); + } + } + fputc('\n', stdout); + if (TAILQ_EMPTY(&head) || (!len && !first)) + break; + } +} + +static void +od(FILE *fp, char *fname, int last) +{ + static unsigned char *line; + static size_t lineoff; + size_t i; + unsigned char buf[BUFSIZ]; + static off_t addr; + size_t buflen; + + while (skip - addr > 0) { + buflen = fread(buf, 1, MIN(skip - addr, BUFSIZ), fp); + addr += buflen; + if (feof(fp) || ferror(fp)) + return; + } + if (!line) + line = emalloc(linelen); + + while ((buflen = fread(buf, 1, max >= 0 ? + max - (addr - skip) : BUFSIZ, fp))) { + for (i = 0; i < buflen; i++, addr++) { + line[lineoff++] = buf[i]; + if (lineoff == linelen) { + printline(line, lineoff, addr - lineoff + 1); + lineoff = 0; + } + } + } + if (lineoff && last) + printline(line, lineoff, addr - lineoff); + if (last) + printline((unsigned char *)"", 0, addr); +} + +static int +lcm(unsigned int a, unsigned int b) +{ + unsigned int c, d, e; + + for (c = a, d = b; c ;) { + e = c; + c = d % c; + d = e; + } + + return a / d * b; +} + +static void +usage(void) +{ + eprintf("usage: %s [-A addressformat] [-E | -e] [-j skip] " + "[-t outputformat] [-v] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + struct type *t; + int ret = 0; + char *s; + + big_endian = (*(uint16_t *)"\0\xff" == 0xff); + + ARGBEGIN { + case 'A': + s = EARGF(usage()); + if (strlen(s) != 1 || !strchr("doxn", s[0])) + usage(); + addr_format = s[0]; + break; + case 'E': + case 'e': + big_endian = (ARGC() == 'E'); + break; + case 'j': + if ((skip = parseoffset(EARGF(usage()))) < 0) + usage(); + break; + case 'N': + if ((max = parseoffset(EARGF(usage()))) < 0) + usage(); + break; + case 't': + s = EARGF(usage()); + for (; *s; s++) { + t = emalloc(sizeof(struct type)); + switch (*s) { + case 'a': + case 'c': + t->format = *s; + t->len = 1; + TAILQ_INSERT_TAIL(&head, t, entry); + break; + case 'd': + case 'o': + case 'u': + case 'x': + t->format = *s; + /* todo: allow multiple digits */ + if (*(s+1) > '0' && *(s+1) <= '9') { + t->len = *(++s) - '0'; + } else { + switch (*(++s)) { + case 'C': + t->len = sizeof(char); + break; + case 'S': + t->len = sizeof(short); + break; + case 'I': + t->len = sizeof(int); + break; + case 'L': + t->len = sizeof(long); + break; + default: + t->len = 4; + } + } + TAILQ_INSERT_TAIL(&head, t, entry); + break; + default: + usage(); + } + } + break; + case 'v': + /* always set - use uniq(1) to handle duplicate lines */ + break; + default: + usage(); + } ARGEND + + /* line length is lcm of type lengths and >= 16 by doubling */ + TAILQ_FOREACH(t, &head, entry) + linelen = lcm(linelen, t->len); + if (TAILQ_EMPTY(&head)) + linelen = 16; + while (linelen < 16) + linelen *= 2; + + if (!argc) { + od(stdin, "", 1); + } else { + for (; *argv; argc--, argv++) { + if (!strcmp(*argv, "-")) { + *argv = ""; + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + ret = 1; + continue; + } + od(fp, *argv, (!*(argv + 1))); + if (fp != stdin && fshut(fp, *argv)) + ret = 1; + } + } + + ret |= fshut(stdin, "") | fshut(stdout, "") | + fshut(stderr, ""); + + return ret; +} diff --git a/source/sbase/paste.1 b/source/sbase/paste.1 new file mode 100644 index 00000000..4ca36511 --- /dev/null +++ b/source/sbase/paste.1 @@ -0,0 +1,51 @@ +.Dd 2015-10-08 +.Dt PASTE 1 +.Os sbase +.Sh NAME +.Nm paste +.Nd merge lines of files in parallel or sequentially +.Sh SYNOPSIS +.Nm +.Op Fl s +.Op Fl d Ar list +.Ar file ... +.Sh DESCRIPTION +.Nm +reads single lines from each +.Ar file +and writes them into one line, replacing +.Sy \en +with +.Sy \et +except from the last +.Ar file . +This process is repeated until each +.Ar file +is starved, treating zero-reads as empty lines along the way. +.Pp +If +.Ar file +is '-', +.Nm +interprets it as stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl d Ar list +Replace +.Sy \en +with escaped characters from +.Ar list +by cycling through it. +.It Fl s +Read each +.Ar file +sequentially instead of in parallel. +.El +.Sh SEE ALSO +.Xr cut 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/paste.c b/source/sbase/paste.c new file mode 100644 index 00000000..77fc090a --- /dev/null +++ b/source/sbase/paste.c @@ -0,0 +1,141 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "utf.h" +#include "util.h" + +struct fdescr { + FILE *fp; + const char *name; +}; + +static void +sequential(struct fdescr *dsc, int fdescrlen, Rune *delim, size_t delimlen) +{ + Rune c, last; + size_t i, d; + + for (i = 0; i < fdescrlen; i++) { + d = 0; + last = 0; + + while (efgetrune(&c, dsc[i].fp, dsc[i].name)) { + if (last == '\n') { + if (delim[d] != '\0') + efputrune(&delim[d], stdout, ""); + d = (d + 1) % delimlen; + } + + if (c != '\n') + efputrune(&c, stdout, ""); + last = c; + } + + if (last == '\n') + efputrune(&last, stdout, ""); + } +} + +static void +parallel(struct fdescr *dsc, int fdescrlen, Rune *delim, size_t delimlen) +{ + Rune c, d; + size_t i, m; + ssize_t last; + +nextline: + last = -1; + + for (i = 0; i < fdescrlen; i++) { + d = delim[i % delimlen]; + c = 0; + + for (; efgetrune(&c, dsc[i].fp, dsc[i].name) ;) { + for (m = last + 1; m < i; m++) + efputrune(&(delim[m % delimlen]), stdout, ""); + last = i; + if (c == '\n') { + if (i != fdescrlen - 1) + c = d; + efputrune(&c, stdout, ""); + break; + } + efputrune(&c, stdout, ""); + } + + if (c == 0 && last != -1) { + if (i == fdescrlen - 1) + putchar('\n'); + else + efputrune(&d, stdout, ""); + last++; + } + } + if (last != -1) + goto nextline; +} + +static void +usage(void) +{ + eprintf("usage: %s [-s] [-d list] file ...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct fdescr *dsc; + Rune *delim; + size_t delimlen, i; + int seq = 0, ret = 0; + char *adelim = "\t"; + + ARGBEGIN { + case 's': + seq = 1; + break; + case 'd': + adelim = EARGF(usage()); + break; + default: + usage(); + } ARGEND + + if (!argc) + usage(); + + /* populate delimiters */ + /* TODO: fix libutf to accept sizes */ + unescape(adelim); + delim = ereallocarray(NULL, utflen(adelim) + 1, sizeof(*delim)); + if (!(delimlen = utftorunestr(adelim, delim))) + usage(); + + /* populate file list */ + dsc = ereallocarray(NULL, argc, sizeof(*dsc)); + + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "-")) { + argv[i] = ""; + dsc[i].fp = stdin; + } else if (!(dsc[i].fp = fopen(argv[i], "r"))) { + eprintf("fopen %s:", argv[i]); + } + dsc[i].name = argv[i]; + } + + if (seq) { + sequential(dsc, argc, delim, delimlen); + } else { + parallel(dsc, argc, delim, delimlen); + } + + for (i = 0; i < argc; i++) + if (dsc[i].fp != stdin && fshut(dsc[i].fp, argv[i])) + ret |= fshut(dsc[i].fp, argv[i]); + + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/pathchk.1 b/source/sbase/pathchk.1 new file mode 100644 index 00000000..4cd0e80c --- /dev/null +++ b/source/sbase/pathchk.1 @@ -0,0 +1,35 @@ +.Dd 2016-02-03 +.Dt PATHCHK 1 +.Os sbase +.Sh NAME +.Nm pathchk +.Nd validate filename validity or portability +.Sh SYNOPSIS +.Nm +.Op Fl p +.Op Fl P +.Ar file Ar ... +.Sh DESCRIPTION +.Nm +checks that filenames are valid for the system, +and optional on other POSIX systems. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl p +Check for most POSIX systems. +.It Fl P +Check for empty pathnames and leading hythens. +.El +.Sh EXIT STATUS +.Bl -tag -width Ds +.It 0 +All pathname operands passed all of the checks. +.It > 0 +An error occurred. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/pathchk.c b/source/sbase/pathchk.c new file mode 100644 index 00000000..a72e8fdb --- /dev/null +++ b/source/sbase/pathchk.c @@ -0,0 +1,114 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +#define PORTABLE_CHARACTER_SET "0123456789._-qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM" +/* If your system supports more other characters, but not all non-NUL characters, define SYSTEM_CHARACTER_SET. */ + +static int most = 0; +static int extra = 0; + +static int +pathchk(char *filename) +{ + char *invalid, *invalid_end, *p, *q; + const char *character_set; + size_t len, maxlen; + struct stat st; + + /* Empty? */ + if (extra && !*filename) { + weprintf("%s: empty filename\n", argv0); + return 1; + } + + /* Leading hyphen? */ + if (extra && ((*filename == '-') || strstr(filename, "/-"))) { + weprintf("%s: %s: leading '-' in component of filename\n", argv0, filename); + return 1; + } + + /* Nonportable character? */ +#ifdef SYSTEM_CHARACTER_SET + character_set = "/"SYSTEM_CHARACTER_SET; +#else + character_set = 0; +#endif + if (most) + character_set = "/"PORTABLE_CHARACTER_SET; + if (character_set && *(invalid = filename + strspn(filename, character_set))) { + for (invalid_end = invalid + 1; *invalid_end & 0x80; invalid_end++); + weprintf("%s: %s: ", argv0, filename); + *invalid_end = 0; + weprintf("nonportable character '%s'\n", invalid); + return 1; + } + + /* Symlink error? Non-searchable directory? */ + if (lstat(filename, &st) && errno != ENOENT) { + /* lstat rather than stat, so that if filename is a bad symlink, but + * all parents are OK, no error will be detected. */ + weprintf("%s: %s:", argv0, filename); + return 1; + } + + /* Too long pathname? */ + maxlen = most ? _POSIX_PATH_MAX : PATH_MAX; + if (strlen(filename) >= maxlen) { + weprintf("%s: %s: is longer than %zu bytes\n", + argv0, filename, maxlen); + return 1; + } + + /* Too long component? */ + maxlen = most ? _POSIX_NAME_MAX : NAME_MAX; + for (p = filename; p; p = q) { + q = strchr(p, '/'); + len = q ? (size_t)(q++ - p) : strlen(p); + if (len > maxlen) { + weprintf("%s: %s: includes component longer than %zu bytes\n", + argv0, filename, maxlen); + return 1; + } + } + + return 0; +} + +static void +usage(void) +{ + eprintf("usage: %s [-pP] filename...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int ret = 0; + + ARGBEGIN { + case 'p': + most = 1; + break; + case 'P': + extra = 1; + break; + default: + usage(); + } ARGEND; + + if (!argc) + usage(); + + for (; argc--; argv++) + ret |= pathchk(*argv); + + return ret; +} diff --git a/source/sbase/pathconf_l.h b/source/sbase/pathconf_l.h new file mode 100644 index 00000000..0cd3f85f --- /dev/null +++ b/source/sbase/pathconf_l.h @@ -0,0 +1,63 @@ +#ifdef _PC_FILESIZEBITS + {"FILESIZEBITS", _PC_FILESIZEBITS}, +#endif +#ifdef _PC_LINK_MAX + {"LINK_MAX", _PC_LINK_MAX}, +#endif +#ifdef _PC_MAX_CANON + {"MAX_CANON", _PC_MAX_CANON}, +#endif +#ifdef _PC_MAX_INPUT + {"MAX_INPUT", _PC_MAX_INPUT}, +#endif +#ifdef _PC_NAME_MAX + {"NAME_MAX", _PC_NAME_MAX}, +#endif +#ifdef _PC_PATH_MAX + {"PATH_MAX", _PC_PATH_MAX}, +#endif +#ifdef _PC_PIPE_BUF + {"PIPE_BUF", _PC_PIPE_BUF}, +#endif +#ifdef _PC_2_SYMLINKS + {"POSIX2_SYMLINKS", _PC_2_SYMLINKS}, +#endif +#ifdef _PC_ALLOC_SIZE_MIN + {"POSIX_ALLOC_SIZE_MIN", _PC_ALLOC_SIZE_MIN}, +#endif +#ifdef _PC_REC_INCR_XFER_SIZE + {"POSIX_REC_INCR_XFER_SIZE", _PC_REC_INCR_XFER_SIZE}, +#endif +#ifdef _PC_REC_MAX_XFER_SIZE + {"POSIX_REC_MAX_XFER_SIZE", _PC_REC_MAX_XFER_SIZE}, +#endif +#ifdef _PC_REC_MIN_XFER_SIZE + {"POSIX_REC_MIN_XFER_SIZE", _PC_REC_MIN_XFER_SIZE}, +#endif +#ifdef _PC_REC_XFER_ALIGN + {"POSIX_REC_XFER_ALIGN", _PC_REC_XFER_ALIGN}, +#endif +#ifdef _PC_SYMLINK_MAX + {"SYMLINK_MAX", _PC_SYMLINK_MAX}, +#endif +#ifdef _PC_CHOWN_RESTRICTED + {"_POSIX_CHOWN_RESTRICTED", _PC_CHOWN_RESTRICTED}, +#endif +#ifdef _PC_NO_TRUNC + {"_POSIX_NO_TRUNC", _PC_NO_TRUNC}, +#endif +#ifdef _PC_VDISABLE + {"_POSIX_VDISABLE", _PC_VDISABLE}, +#endif +#ifdef _PC_ASYNC_IO + {"_POSIX_ASYNC_IO", _PC_ASYNC_IO}, +#endif +#ifdef _PC_PRIO_IO + {"_POSIX_PRIO_IO", _PC_PRIO_IO}, +#endif +#ifdef _PC_SYNC_IO + {"_POSIX_SYNC_IO", _PC_SYNC_IO}, +#endif +#ifdef _PC_TIMESTAMP_RESOLUTION + {"_POSIX_TIMESTAMP_RESOLUTION", _PC_TIMESTAMP_RESOLUTION}, +#endif diff --git a/source/sbase/printenv.1 b/source/sbase/printenv.1 new file mode 100644 index 00000000..a61efe96 --- /dev/null +++ b/source/sbase/printenv.1 @@ -0,0 +1,20 @@ +.Dd 2015-10-08 +.Dt PRINTENV 1 +.Os sbase +.Sh NAME +.Nm printenv +.Nd print the environment or values of variables +.Sh SYNOPSIS +.Nm +.Op Ar var ... +.Sh DESCRIPTION +.Nm +prints the entire environment as key=value pairs if no +.Ar var +is given. Otherwise, +.Nm +prints only the value of each +.Ar var +one per line in the order specified. +.Sh SEE ALSO +.Xr env 1 diff --git a/source/sbase/printenv.c b/source/sbase/printenv.c new file mode 100644 index 00000000..2c1e711d --- /dev/null +++ b/source/sbase/printenv.c @@ -0,0 +1,30 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "util.h" + +extern char **environ; + +int +main(int argc, char *argv[]) +{ + char *var; + int ret = 0; + + argv0 = argv[0], argc--, argv++; + + if (!argc) { + for (; *environ; environ++) + puts(*environ); + } else { + for (; *argv; argc--, argv++) { + if ((var = getenv(*argv))) + puts(var); + else + ret = 1; + } + } + + return fshut(stdout, "") || ret; +} diff --git a/source/sbase/printf.1 b/source/sbase/printf.1 new file mode 100644 index 00000000..78ffb1e6 --- /dev/null +++ b/source/sbase/printf.1 @@ -0,0 +1,34 @@ +.Dd 2015-10-08 +.Dt PRINTF 1 +.Os sbase +.Sh NAME +.Nm printf +.Nd print formatted data +.Sh SYNOPSIS +.Nm +.Ar format +.Op Ar arg ... +.Sh DESCRIPTION +.Nm +writes formatted data according to +.Ar format +using each +.Ar arg +until drained. +.Pp +.Nm +interprets the standard escape sequences \e\e, \e', \e", \ea, \eb, \ee, +\ef, \en, \er, \et, \ev, \exH[H], \eO[OOO], the sequence \ec, which +terminates further output if it's found inside +.Ar format +or a %b format string, the format specification %b for an unescaped string and all C +.Xr printf 3 +format specifications ending with csdiouxXaAeEfFgG, including variable width and precision. +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The possibility of specifying 4-digit octals is an extension to that specification. diff --git a/source/sbase/printf.c b/source/sbase/printf.c new file mode 100644 index 00000000..2e248177 --- /dev/null +++ b/source/sbase/printf.c @@ -0,0 +1,182 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include + +#include "utf.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s format [arg ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + Rune *rarg; + size_t i, j, argi, lastargi, formatlen; + long long num; + double dou; + int cooldown = 0, width, precision, ret = 0; + char *format, *tmp, *arg, *fmt, flag; + + argv0 = argv[0]; + if (argc < 2) + usage(); + + format = argv[1]; + if ((tmp = strstr(format, "\\c"))) { + *tmp = 0; + cooldown = 1; + } + formatlen = unescape(format); + if (formatlen == 0) + return 0; + lastargi = 0; + for (i = 0, argi = 2; !cooldown || i < formatlen; i++, i = cooldown ? i : (i % formatlen)) { + if (i == 0) { + if (lastargi == argi) + break; + lastargi = argi; + } + if (format[i] != '%') { + putchar(format[i]); + continue; + } + + /* flag */ + for (flag = '\0', i++; strchr("#-+ 0", format[i]); i++) { + flag = format[i]; + } + + /* field width */ + width = -1; + if (format[i] == '*') { + if (argi < argc) + width = estrtonum(argv[argi++], 0, INT_MAX); + else + cooldown = 1; + i++; + } else { + j = i; + for (; strchr("+-0123456789", format[i]); i++); + if (j != i) { + tmp = estrndup(format + j, i - j); + width = estrtonum(tmp, 0, INT_MAX); + free(tmp); + } else { + width = 0; + } + } + + /* field precision */ + precision = -1; + if (format[i] == '.') { + if (format[++i] == '*') { + if (argi < argc) + precision = estrtonum(argv[argi++], 0, INT_MAX); + else + cooldown = 1; + i++; + } else { + j = i; + for (; strchr("+-0123456789", format[i]); i++); + if (j != i) { + tmp = estrndup(format + j, i - j); + precision = estrtonum(tmp, 0, INT_MAX); + free(tmp); + } else { + precision = 0; + } + } + } + + if (format[i] != '%') { + if (argi < argc) + arg = argv[argi++]; + else { + arg = ""; + cooldown = 1; + } + } else + putchar('%'); + + switch (format[i]) { + case 'b': + if ((tmp = strstr(arg, "\\c"))) { + *tmp = 0; + unescape(arg); + fputs(arg, stdout); + return 0; + } + unescape(arg); + fputs(arg, stdout); + break; + case 'c': + unescape(arg); + rarg = ereallocarray(NULL, utflen(arg) + 1, sizeof(*rarg)); + utftorunestr(arg, rarg); + efputrune(rarg, stdout, ""); + free(rarg); + break; + case 's': + printf("%*.*s", width, precision, arg); + break; + case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': + for (j = 0; isspace(arg[j]); j++); + if (arg[j] == '\'' || arg[j] == '\"') { + arg += j + 1; + unescape(arg); + rarg = ereallocarray(NULL, utflen(arg) + 1, sizeof(*rarg)); + utftorunestr(arg, rarg); + num = rarg[0]; + } else if (arg[0]) { + errno = 0; + if (format[i] == 'd' || format[i] == 'i') + num = strtol(arg, &tmp, 0); + else + num = strtoul(arg, &tmp, 0); + + if (tmp == arg || *tmp != '\0') { + ret = 1; + weprintf("%%%c %s: conversion error\n", + format[i], arg); + } + if (errno == ERANGE) { + ret = 1; + weprintf("%%%c %s: out of range\n", + format[i], arg); + } + } else { + num = 0; + } + fmt = estrdup(flag ? "%#*.*ll#" : "%*.*ll#"); + if (flag) + fmt[1] = flag; + fmt[flag ? 7 : 6] = format[i]; + printf(fmt, width, precision, num); + free(fmt); + break; + case 'a': case 'A': case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': + fmt = estrdup(flag ? "%#*.*#" : "%*.*#"); + if (flag) + fmt[1] = flag; + fmt[flag ? 5 : 4] = format[i]; + dou = (strlen(arg) > 0) ? estrtod(arg) : 0; + printf(fmt, width, precision, dou); + free(fmt); + break; + default: + eprintf("Invalid format specifier '%c'.\n", format[i]); + } + if (argi >= argc) + cooldown = 1; + } + + return fshut(stdout, "") | ret; +} diff --git a/source/sbase/pwd.1 b/source/sbase/pwd.1 new file mode 100644 index 00000000..921a0e1c --- /dev/null +++ b/source/sbase/pwd.1 @@ -0,0 +1,32 @@ +.Dd 2015-10-08 +.Dt PWD 1 +.Os sbase +.Sh NAME +.Nm pwd +.Nd print working directory +.Sh SYNOPSIS +.Nm +.Op Fl L | Fl P +.Sh DESCRIPTION +.Nm +prints the path of the current working directory. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl L +Logical path, uses $PWD. This is the 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-2013 +specification. diff --git a/source/sbase/pwd.c b/source/sbase/pwd.c new file mode 100644 index 00000000..c6a4497f --- /dev/null +++ b/source/sbase/pwd.c @@ -0,0 +1,50 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include + +#include "util.h" + +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); + + return (pst.st_dev == cst.st_dev && pst.st_ino == cst.st_ino) ? pwd : cwd; +} + +static void +usage(void) +{ + eprintf("usage: %s [-LP]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char cwd[PATH_MAX]; + char mode = 'L'; + + ARGBEGIN { + case 'L': + case 'P': + mode = ARGC(); + break; + default: + usage(); + } ARGEND + + if (!getcwd(cwd, sizeof(cwd))) + eprintf("getcwd:"); + puts((mode == 'L') ? getpwd(cwd) : cwd); + + return fshut(stdout, ""); +} diff --git a/source/sbase/queue.h b/source/sbase/queue.h new file mode 100644 index 00000000..f8f09bf1 --- /dev/null +++ b/source/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/source/sbase/readlink.1 b/source/sbase/readlink.1 new file mode 100644 index 00000000..46b4cadb --- /dev/null +++ b/source/sbase/readlink.1 @@ -0,0 +1,32 @@ +.Dd 2015-11-16 +.Dt READLINK 1 +.Os sbase +.Sh NAME +.Nm readlink +.Nd print symbolic link target or canonical file name +.Sh SYNOPSIS +.Nm +.Op Fl f +.Op Fl n +.Ar path +.Sh DESCRIPTION +.Nm +writes the target of +.Ar path , +if it is a symbolic link, to stdout. +If not, +.Nm +exits with a non-zero return value. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl f +Canonicalize +.Ar path , +which needn't be a symlink, +by recursively following every symlink in its path components. +.It Fl n +Do not print the terminating newline. +.El +.Sh SEE ALSO +.Xr readlink 2 , +.Xr realpath 3 diff --git a/source/sbase/readlink.c b/source/sbase/readlink.c new file mode 100644 index 00000000..d059584f --- /dev/null +++ b/source/sbase/readlink.c @@ -0,0 +1,54 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-fn] path\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char buf[PATH_MAX]; + ssize_t n; + int nflag = 0, fflag = 0; + + ARGBEGIN { + case 'f': + fflag = ARGC(); + break; + case 'n': + nflag = 1; + break; + default: + usage(); + } ARGEND + + if (argc != 1) + usage(); + + if (strlen(argv[0]) >= PATH_MAX) + eprintf("path too long\n"); + + if (fflag) { + if (!realpath(argv[0], buf)) + eprintf("realpath %s:", argv[0]); + } else { + if ((n = readlink(argv[0], buf, PATH_MAX - 1)) < 0) + eprintf("readlink %s:", argv[0]); + buf[n] = '\0'; + } + + fputs(buf, stdout); + if (!nflag) + putchar('\n'); + + return fshut(stdout, ""); +} diff --git a/source/sbase/renice.1 b/source/sbase/renice.1 new file mode 100644 index 00000000..ad37b438 --- /dev/null +++ b/source/sbase/renice.1 @@ -0,0 +1,42 @@ +.Dd 2015-10-08 +.Dt RENICE 1 +.Os sbase +.Sh NAME +.Nm renice +.Nd change niceness of processes +.Sh SYNOPSIS +.Nm +.Fl n Ar num +.Op Fl g | Fl p | Fl u +.Ar id ... +.Sh DESCRIPTION +.Nm +changes the niceness of each process with the given +.Ar id . +Only the superuser can lower the niceness. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl g | Fl p | Fl u +Interpret each +.Ar id +as a process group ID | process ID | user name or ID. +The middle option is default. +.It Fl n Ar num +Change niceness by +.Ar num , +with niceness ranging from +.Sy -20 +(highest priority) +to +.Sy +20 +(lowest priority). +.El +.Sh SEE ALSO +.Xr nice 2 , +.Xr renice 2 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/renice.c b/source/sbase/renice.c new file mode 100644 index 00000000..358c5604 --- /dev/null +++ b/source/sbase/renice.c @@ -0,0 +1,93 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include + +#include "util.h" + +#ifndef PRIO_MIN +#define PRIO_MIN -NZERO +#endif + +#ifndef PRIO_MAX +#define PRIO_MAX (NZERO-1) +#endif + +static int +renice(int which, int who, long adj) +{ + errno = 0; + adj += getpriority(which, who); + if (errno) { + 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; +} + +static void +usage(void) +{ + eprintf("usage: %s -n num [-g | -p | -u] id ...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + const char *adj = NULL; + long val; + int which = PRIO_PROCESS, ret = 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(); + } ARGEND + + if (!argc || !adj) + usage(); + + val = estrtonum(adj, PRIO_MIN, PRIO_MAX); + for (; *argv; argc--, argv++) { + if (which == PRIO_USER) { + errno = 0; + if (!(pw = getpwnam(*argv))) { + if (errno) + weprintf("getpwnam %s:", *argv); + else + weprintf("getpwnam %s: no user found\n", *argv); + ret = 1; + continue; + } + who = pw->pw_uid; + } else { + who = estrtonum(*argv, 1, INT_MAX); + } + if (!renice(which, who, val)) + ret = 1; + } + + return ret; +} diff --git a/source/sbase/rm.1 b/source/sbase/rm.1 new file mode 100644 index 00000000..831a6e46 --- /dev/null +++ b/source/sbase/rm.1 @@ -0,0 +1,43 @@ +.Dd 2015-10-08 +.Dt RM 1 +.Os sbase +.Sh NAME +.Nm rm +.Nd remove directory entries +.Sh SYNOPSIS +.Nm +.Op Fl f +.Op Fl Rr +.Ar file ... +.Sh DESCRIPTION +.Nm +removes each +.Ar file . +If +.Ar file +is a directory, it has to be empty unless +.Fl R +or +.Fl r +is specified. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl f +Do not prompt before removing +.Ar file . +Do not report when +.Ar file +doesn't exist or couldn't be removed. +.It Fl Rr +Remove directories recursively. +.El +.Sh SEE ALSO +.Xr remove 3 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification except from the +.Op Fl i +flag. diff --git a/source/sbase/rm.c b/source/sbase/rm.c new file mode 100644 index 00000000..42bc022f --- /dev/null +++ b/source/sbase/rm.c @@ -0,0 +1,40 @@ +/* See LICENSE file for copyright and license details. */ +#include "fs.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-f] [-Rr] file ...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct recursor r = { .fn = rm, .hist = NULL, .depth = 0, .maxdepth = 1, + .follow = 'P', .flags = 0 }; + + ARGBEGIN { + case 'f': + r.flags |= SILENT; + break; + case 'R': + case 'r': + r.maxdepth = 0; + break; + default: + usage(); + } ARGEND + + if (!argc) { + if (!(r.flags & SILENT)) + usage(); + else + return 0; + } + + for (; *argv; argc--, argv++) + recurse(*argv, NULL, &r); + + return rm_status || recurse_status; +} diff --git a/source/sbase/rmdir.1 b/source/sbase/rmdir.1 new file mode 100644 index 00000000..0cbb5a3c --- /dev/null +++ b/source/sbase/rmdir.1 @@ -0,0 +1,33 @@ +.Dd 2015-10-08 +.Dt RMDIR 1 +.Os sbase +.Sh NAME +.Nm rmdir +.Nd remove empty directories +.Sh SYNOPSIS +.Nm +.Op Fl p +.Ar dir ... +.Sh DESCRIPTION +.Nm +removes each empty +.Ar dir . +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl p +Remove +.Ar dir +and its parents in the pathname +.Ar dir . +.El +.Sh SEE ALSO +.Xr rm 1 , +.Xr unlink 1 , +.Xr rmdir 2 , +.Xr unlink 2 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/rmdir.c b/source/sbase/rmdir.c new file mode 100644 index 00000000..44224547 --- /dev/null +++ b/source/sbase/rmdir.c @@ -0,0 +1,49 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-p] dir ...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int pflag = 0, ret = 0; + char *d; + + ARGBEGIN { + case 'p': + pflag = 1; + break; + default: + usage(); + } ARGEND + + if (!argc) + usage(); + + for (; *argv; argc--, argv++) { + if (rmdir(*argv) < 0) { + weprintf("rmdir %s:", *argv); + ret = 1; + } else if (pflag) { + d = dirname(*argv); + for (; strcmp(d, "/") && strcmp(d, ".") ;) { + if (rmdir(d) < 0) { + weprintf("rmdir %s:", d); + ret = 1; + break; + } + d = dirname(d); + } + } + } + + return ret; +} diff --git a/source/sbase/sed.1 b/source/sbase/sed.1 new file mode 100644 index 00000000..94e7f9fc --- /dev/null +++ b/source/sbase/sed.1 @@ -0,0 +1,142 @@ +.Dd 2015-10-08 +.Dt SED 1 +.Os sbase +.Sh NAME +.Nm sed +.Nd stream editor +.Sh SYNOPSIS +.Nm +.Op Fl nrE +.Ar script +.Op Ar file ... +.Nm +.Op Fl nrE +.Fl e Ar script +.Op Fl e Ar script +.Ar ... +.Op Fl f Ar scriptfile +.Ar ... +.Op Ar file ... +.Nm +.Op Fl nrE +.Op Fl e Ar script +.Ar ... +.Fl f Ar scriptfile +.Op Fl f Ar scriptfile +.Ar ... +.Op Ar file ... +.Sh DESCRIPTION +.Nm +reads line oriented output from +.Ar file +or stdin, applies the editing commands supplied by +.Ar script +or +.Ar scriptfile +and writes the edited stream to stdout. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl n +Suppress default printing at the end of each cycle. +.It Fl r E +Use extended regular expressions +.It Fl e Ar script +Append +.Ar script +to the list of editing commands. +.It Fl f Ar scriptfile +Append the commands from +.Ar scriptfile +to the list of editing commands. +.El +.Sh EXTENDED DESCRIPTION +Editing commands take the form +.Pp +[address[,address]]function +.Ss Addresses +Addresses are either blank, a positive decimal integer denoting a line +number, the character '$' denoting the last line of input, or a regular +expression. A command with no addresses matches every line, one address +matches individual lines, and two addresses matches a range of lines +from the first to the second address inclusive. +.Ss Functions +.Bl -tag -width Ds +.It Ar a Op Ar text +Append text to output after end of current cycle. +.It Ar b Op Ar label +Branch to label. If no label is provided branch to end of script. +.It Ar c Op Ar text +Change. Delete addressed range and output text after end of current cycle. +.It Ar d +Delete pattern space and begin next cycle. +.It Ar D +Delete pattern space up to and including first newline and begin new +cycle without reading input. If there is no newline, behave like d. +.It Ar g +Get. Replace the pattern space with the hold space. +.It Ar G +Get. Append a newline and the hold space to the pattern space. +.It Ar h +Hold. Replace the hold space with the pattern space. +.It Ar H +Hold. Append a newline and the pattern space to the hold space. +.It Ar i Op Ar text +Insert text in output. +.It Ar l +List? Write the pattern space replacing known non printing characters with +backslash escaped versions (\\\\, \\a, \\b, \\f, \\r, \\t, \\v). Print +bad UTF-8 sequences as \\ooo where ooo is a three digit octal number. Mark +end of lines with '$'. +.It Ar n +Next. Write pattern space (unless +.Fl n ) , +read next line into pattern space, and continue current cycle. If there +is no next line, quit. +.It Ar N +Next. Read next line, append newline and next line to pattern space, +and continue cycle. If there is no next line, quit without printing +current pattern space. +.It Ar p +Print current pattern space. +.It Ar P +Print current pattern space up to first newline. +.It Ar q +Quit. +.It Ar r file +Read file and write contents to output. +.It Ar s/re/text/flags +Find occurences of regular expression re in the pattern space and replace +with text. A '&' in text is replaced with the entire match. A \\d where +d is a decimal digit 1-9 is replaced with the corresponding match group +from the regular expression. \\n represents a newline in both the regular +expression and replacement text. A literal newline in the replacement +text must be preceded by a \\. +.Pp +Flags are +.Bl -tag -width Ds +.It Ar n +A positive decimal number denoting which match in the pattern space +to replace. +.It Ar g +Global. Replace all matches in the pattern space. +.It Ar p +Print the pattern if a replacement was made. +.It Ar w file +Write the pattern space to file if a replacement was made. +.El +.It Ar t Op Ar label +Test. Branch to corresponding labelif a substitution has been made since +the last line was read or last t command was executed. If no label is +provided branch to end of script. +.It Ar w file +Write pattern space to file. +.It Ar x +Exchange hold space and pattern space. +.It Ar y/set1/set2/ +Replace each occurrence of a character from set 1 with the corresponding +character from set 2. +.It Ar :label +Create a label for b and t commands. +.It Ar = +Write current input line number to output. +.El diff --git a/source/sbase/sed.c b/source/sbase/sed.c new file mode 100644 index 00000000..6cfd121c --- /dev/null +++ b/source/sbase/sed.c @@ -0,0 +1,1731 @@ +/* FIXME: summary + * decide whether we enforce valid UTF-8, right now it's enforced in certain + * parts of the script, but not the input... + * nul bytes cause explosions due to use of libc string functions. thoughts? + * lack of newline at end of file, currently we add one. what should we do? + * allow "\\t" for "\t" etc. in regex? in replacement text? + * POSIX says don't flush on N when out of input, but GNU and busybox do. + */ + +#include +#include +#include +#include +#include + +#include "utf.h" +#include "util.h" + +/* Types */ + +/* used as queue for writes and stack for {,:,b,t */ +typedef struct { + void **data; + size_t size; + size_t cap; +} Vec; + +/* used for arbitrary growth, str is a C string + * FIXME: does it make sense to keep track of length? or just rely on libc + * string functions? If we want to support nul bytes everything changes + */ +typedef struct { + char *str; + size_t cap; +} String; + +typedef struct Cmd Cmd; +typedef struct { + void (*fn)(Cmd *); + char *(*getarg)(Cmd *, char *); + void (*freearg)(Cmd *); + unsigned char naddr; +} Fninfo; + +typedef struct { + union { + size_t lineno; + regex_t *re; + } u; + enum { + IGNORE, /* empty address, ignore */ + EVERY , /* every line */ + LINE , /* ilne number */ + LAST , /* last line ($) */ + REGEX , /* use included regex */ + LASTRE, /* use most recently used regex */ + } type; +} Addr; + +/* DISCUSS: naddr is not strictly necessary, but very helpful + * naddr == 0 iff beg.type == EVERY && end.type == IGNORE + * naddr == 1 iff beg.type != IGNORE && end.type == IGNORE + * naddr == 2 iff beg.type != IGNORE && end.type != IGNORE + */ +typedef struct { + Addr beg; + Addr end; + unsigned char naddr; +} Range; + +typedef struct { + regex_t *re; /* if NULL use last regex */ + String repl; + FILE *file; + size_t occurrence; /* 0 for all (g flag) */ + Rune delim; + unsigned int p:1; +} Sarg; + +typedef struct { + Rune *set1; + Rune *set2; +} Yarg; + +typedef struct { + String str; /* a,c,i text. r file path */ + void (*print)(char *, FILE *); /* check_puts for a, write_file for r, unused for c,i */ +} ACIRarg; + +struct Cmd { + Range range; + Fninfo *fninfo; + union { + Cmd *jump; /* used for b,t when running */ + char *label; /* used for :,b,t when building */ + ptrdiff_t offset; /* used for { (pointers break during realloc) */ + FILE *file; /* used for w */ + + /* FIXME: Should the following be in the union? or pointers and malloc? */ + Sarg s; + Yarg y; + ACIRarg acir; + } u; /* I find your lack of anonymous unions disturbing */ + unsigned int in_match:1; + unsigned int negate :1; +}; + +/* Files for w command (and s' w flag) */ +typedef struct { + char *path; + FILE *file; +} Wfile; + +/* + * Function Declarations + */ + +/* Dynamically allocated arrays and strings */ +static void resize(void **ptr, size_t *nmemb, size_t size, size_t new_nmemb, void **next); +static void *pop(Vec *v); +static void push(Vec *v, void *p); +static void stracat(String *dst, char *src); +static void strnacat(String *dst, char *src, size_t n); +static void stracpy(String *dst, char *src); + +/* Cleanup and errors */ +static void usage(void); + +/* Parsing functions and related utilities */ +static void compile(char *s, int isfile); +static int read_line(FILE *f, String *s); +static char *make_range(Range *range, char *s); +static char *make_addr(Addr *addr, char *s); +static char *find_delim(char *s, Rune delim, int do_brackets); +static char *chompr(char *s, Rune rune); +static char *chomp(char *s); +static Rune *strtorunes(char *s, size_t nrunes); +static long stol(char *s, char **endp); +static size_t escapes(char *beg, char *end, Rune delim, int n_newline); +static size_t echarntorune(Rune *r, char *s, size_t n); +static void insert_labels(void); + +/* Get and Free arg and related utilities */ +static char *get_aci_arg(Cmd *c, char *s); +static void aci_append(Cmd *c, char *s); +static void free_acir_arg(Cmd *c); +static char *get_bt_arg(Cmd *c, char *s); +static char *get_r_arg(Cmd *c, char *s); +static char *get_s_arg(Cmd *c, char *s); +static void free_s_arg(Cmd *c); +static char *get_w_arg(Cmd *c, char *s); +static char *get_y_arg(Cmd *c, char *s); +static void free_y_arg(Cmd *c); +static char *get_colon_arg(Cmd *c, char *s); +static char *get_lbrace_arg(Cmd *c, char *s); +static char *get_rbrace_arg(Cmd *c, char *s); +static char *semicolon_arg(char *s); + +/* Running */ +static void run(void); +static int in_range(Cmd *c); +static int match_addr(Addr *a); +static int next_file(void); +static int is_eof(FILE *f); +static void do_writes(void); +static void write_file(char *path, FILE *out); +static void check_puts(char *s, FILE *f); +static void update_ranges(Cmd *beg, Cmd *end); + +/* Sed functions */ +static void cmd_y(Cmd *c); +static void cmd_x(Cmd *c); +static void cmd_w(Cmd *c); +static void cmd_t(Cmd *c); +static void cmd_s(Cmd *c); +static void cmd_r(Cmd *c); +static void cmd_q(Cmd *c); +static void cmd_P(Cmd *c); +static void cmd_p(Cmd *c); +static void cmd_N(Cmd *c); +static void cmd_n(Cmd *c); +static void cmd_l(Cmd *c); +static void cmd_i(Cmd *c); +static void cmd_H(Cmd *c); +static void cmd_h(Cmd *c); +static void cmd_G(Cmd *c); +static void cmd_g(Cmd *c); +static void cmd_D(Cmd *c); +static void cmd_d(Cmd *c); +static void cmd_c(Cmd *c); +static void cmd_b(Cmd *c); +static void cmd_a(Cmd *c); +static void cmd_colon(Cmd *c); +static void cmd_equal(Cmd *c); +static void cmd_lbrace(Cmd *c); +static void cmd_rbrace(Cmd *c); +static void cmd_last(Cmd *c); + +/* Actions */ +static void new_line(void); +static void app_line(void); +static void new_next(void); +static void old_next(void); + +/* + * Globals + */ +static Vec braces, labels, branches; /* holds ptrdiff_t. addrs of {, :, bt */ +static Vec writes; /* holds cmd*. writes scheduled by a and r commands */ +static Vec wfiles; /* holds Wfile*. files for w and s///w commands */ + +static Cmd *prog, *pc; /* Program, program counter */ +static size_t pcap; +static size_t lineno; + +static regex_t *lastre; /* last used regex for empty regex search */ +static char **files; /* list of file names from argv */ +static FILE *file; /* current file we are reading */ + +static String patt, hold, genbuf; + +static struct { + unsigned int n :1; /* -n (no print) */ + unsigned int E :1; /* -E (extended re) */ + unsigned int s :1; /* s/// replacement happened */ + unsigned int aci_cont:1; /* a,c,i text continuation */ + unsigned int s_cont :1; /* s/// replacement text continuation */ + unsigned int halt :1; /* halt execution */ +} gflags; + +/* FIXME: move character inside Fninfo and only use 26*sizeof(Fninfo) instead of 127*sizeof(Fninfo) bytes */ +static Fninfo fns[] = { + ['a'] = { cmd_a , get_aci_arg , free_acir_arg , 1 }, /* schedule write of text for later */ + ['b'] = { cmd_b , get_bt_arg , NULL , 2 }, /* branch to label char *label when building, Cmd *jump when running */ + ['c'] = { cmd_c , get_aci_arg , free_acir_arg , 2 }, /* delete pattern space. at 0 or 1 addr or end of 2 addr, write text */ + ['d'] = { cmd_d , NULL , NULL , 2 }, /* delete pattern space */ + ['D'] = { cmd_D , NULL , NULL , 2 }, /* delete to first newline and start new cycle without reading (if no newline, d) */ + ['g'] = { cmd_g , NULL , NULL , 2 }, /* replace pattern space with hold space */ + ['G'] = { cmd_G , NULL , NULL , 2 }, /* append newline and hold space to pattern space */ + ['h'] = { cmd_h , NULL , NULL , 2 }, /* replace hold space with pattern space */ + ['H'] = { cmd_H , NULL , NULL , 2 }, /* append newline and pattern space to hold space */ + ['i'] = { cmd_i , get_aci_arg , free_acir_arg , 1 }, /* write text */ + ['l'] = { cmd_l , NULL , NULL , 2 }, /* write pattern space in 'visually unambiguous form' */ + ['n'] = { cmd_n , NULL , NULL , 2 }, /* write pattern space (unless -n) read to replace pattern space (if no input, quit) */ + ['N'] = { cmd_N , NULL , NULL , 2 }, /* append to pattern space separated by newline, line number changes (if no input, quit) */ + ['p'] = { cmd_p , NULL , NULL , 2 }, /* write pattern space */ + ['P'] = { cmd_P , NULL , NULL , 2 }, /* write pattern space up to first newline */ + ['q'] = { cmd_q , NULL , NULL , 1 }, /* quit */ + ['r'] = { cmd_r , get_r_arg , free_acir_arg , 1 }, /* write contents of file (unable to open/read treated as empty file) */ + ['s'] = { cmd_s , get_s_arg , free_s_arg , 2 }, /* find/replace/all that crazy s stuff */ + ['t'] = { cmd_t , get_bt_arg , NULL , 2 }, /* if s/// succeeded (since input or last t) branch to label (branch to end if no label) */ + ['w'] = { cmd_w , get_w_arg , NULL , 2 }, /* append pattern space to file */ + ['x'] = { cmd_x , NULL , NULL , 2 }, /* exchange pattern and hold spaces */ + ['y'] = { cmd_y , get_y_arg , free_y_arg , 2 }, /* replace runes in set1 with runes in set2 */ + [':'] = { cmd_colon , get_colon_arg , NULL , 0 }, /* defines label for later b and t commands */ + ['='] = { cmd_equal , NULL , NULL , 1 }, /* printf("%d\n", line_number); */ + ['{'] = { cmd_lbrace, get_lbrace_arg, NULL , 2 }, /* if we match, run commands, otherwise jump to close */ + ['}'] = { cmd_rbrace, get_rbrace_arg, NULL , 0 }, /* noop, hold onto open for ease of building scripts */ + + [0x7f] = { NULL, NULL, NULL, 0 }, /* index is checked with isascii(3p). fill out rest of array */ +}; + +/* + * Function Definitions + */ + +/* given memory pointed to by *ptr that currently holds *nmemb members of size + * size, realloc to hold new_nmemb members, return new_nmemb in *memb and one + * past old end in *next. if realloc fails...explode + */ +static void +resize(void **ptr, size_t *nmemb, size_t size, size_t new_nmemb, void **next) +{ + void *n, *tmp; + + if (new_nmemb) { + tmp = ereallocarray(*ptr, new_nmemb, size); + } else { /* turns out realloc(*ptr, 0) != free(*ptr) */ + free(*ptr); + tmp = NULL; + } + n = (char *)tmp + *nmemb * size; + *nmemb = new_nmemb; + *ptr = tmp; + if (next) + *next = n; +} + +static void * +pop(Vec *v) +{ + if (!v->size) + return NULL; + return v->data[--v->size]; +} + +static void +push(Vec *v, void *p) +{ + if (v->size == v->cap) + resize((void **)&v->data, &v->cap, sizeof(*v->data), v->cap * 2 + 1, NULL); + v->data[v->size++] = p; +} + +static void +stracat(String *dst, char *src) +{ + int new = !dst->cap; + size_t len; + + len = (new ? 0 : strlen(dst->str)) + strlen(src) + 1; + if (dst->cap < len) + resize((void **)&dst->str, &dst->cap, 1, len * 2, NULL); + if (new) + *dst->str = '\0'; + strcat(dst->str, src); +} + +static void +strnacat(String *dst, char *src, size_t n) +{ + int new = !dst->cap; + size_t len; + + len = strlen(src); + len = (new ? 0 : strlen(dst->str)) + MIN(n, len) + 1; + if (dst->cap < len) + resize((void **)&dst->str, &dst->cap, 1, len * 2, NULL); + if (new) + *dst->str = '\0'; + strlcat(dst->str, src, len); +} + +static void +stracpy(String *dst, char *src) +{ + size_t len; + + len = strlen(src) + 1; + if (dst->cap < len) + resize((void **)&dst->str, &dst->cap, 1, len * 2, NULL); + strcpy(dst->str, src); +} + +static void +leprintf(char *s) +{ + if (errno) + eprintf("%zu: %s: %s\n", lineno, s, strerror(errno)); + else + eprintf("%zu: %s\n", lineno, s); +} + +/* FIXME: write usage message */ +static void +usage(void) +{ + eprintf("USAGE\n"); +} + +/* Differences from POSIX + * we allows semicolons and trailing blanks inside {} + * we allow spaces after ! (and in between !s) + * we allow extended regular expressions (-E) + */ +static void +compile(char *s, int isfile) +{ + FILE *f; + + if (!isfile && !*s) /* empty string script */ + return; + + f = isfile ? fopen(s, "r") : fmemopen(s, strlen(s), "r"); + if (!f) + eprintf("fopen/fmemopen:"); + + /* NOTE: get arg functions can't use genbuf */ + while (read_line(f, &genbuf) != EOF) { + s = genbuf.str; + + /* if the first two characters of the script are "#n" default output shall be suppressed */ + if (++lineno == 1 && *s == '#' && s[1] == 'n') { + gflags.n = 1; + continue; + } + + if (gflags.aci_cont) { + aci_append(pc - 1, s); + continue; + } + if (gflags.s_cont) + s = (pc - 1)->fninfo->getarg(pc - 1, s); + + while (*s) { + s = chompr(s, ';'); + if (!*s || *s == '#') + break; + + if ((size_t)(pc - prog) == pcap) + resize((void **)&prog, &pcap, sizeof(*prog), pcap * 2 + 1, (void **)&pc); + + pc->range.beg.type = pc->range.end.type = IGNORE; + pc->fninfo = NULL; + pc->in_match = 0; + + s = make_range(&pc->range, s); + s = chomp(s); + pc->negate = *s == '!'; + s = chompr(s, '!'); + + if (!isascii(*s) || !(pc->fninfo = &fns[(unsigned)*s])->fn) + leprintf("bad sed function"); + if (pc->range.naddr > pc->fninfo->naddr) + leprintf("wrong number of addresses"); + s++; + + if (pc->fninfo->getarg) + s = pc->fninfo->getarg(pc, s); + + pc++; + } + } + + fshut(f, s); +} + +/* FIXME: if we decide to honor lack of trailing newline, set/clear a global + * flag when reading a line + */ +static int +read_line(FILE *f, String *s) +{ + ssize_t len; + + if (!f) + return EOF; + + if ((len = getline(&s->str, &s->cap, f)) < 0) { + if (ferror(f)) + eprintf("getline:"); + return EOF; + } + if (s->str[--len] == '\n') + s->str[len] = '\0'; + return 0; +} + +/* read first range from s, return pointer to one past end of range */ +static char * +make_range(Range *range, char *s) +{ + s = make_addr(&range->beg, s); + + if (*s == ',') + s = make_addr(&range->end, s + 1); + else + range->end.type = IGNORE; + + if (range->beg.type == EVERY && range->end.type == IGNORE) range->naddr = 0; + else if (range->beg.type != IGNORE && range->end.type == IGNORE) range->naddr = 1; + else if (range->beg.type != IGNORE && range->end.type != IGNORE) range->naddr = 2; + else leprintf("this is impossible..."); + + return s; +} + +/* read first addr from s, return pointer to one past end of addr */ +static char * +make_addr(Addr *addr, char *s) +{ + Rune r; + char *p = s + strlen(s); + size_t rlen = echarntorune(&r, s, p - s); + + if (r == '$') { + addr->type = LAST; + s += rlen; + } else if (isdigitrune(r)) { + addr->type = LINE; + addr->u.lineno = stol(s, &s); + } else if (r == '/' || r == '\\') { + Rune delim; + if (r == '\\') { + s += rlen; + rlen = echarntorune(&r, s, p - s); + } + if (r == '\\') + leprintf("bad delimiter '\\'"); + delim = r; + s += rlen; + rlen = echarntorune(&r, s, p - s); + if (r == delim) { + addr->type = LASTRE; + s += rlen; + } else { + addr->type = REGEX; + p = find_delim(s, delim, 1); + if (!*p) + leprintf("unclosed regex"); + p -= escapes(s, p, delim, 0); + *p++ = '\0'; + addr->u.re = emalloc(sizeof(*addr->u.re)); + eregcomp(addr->u.re, s, gflags.E ? REG_EXTENDED : 0); + s = p; + } + } else { + addr->type = EVERY; + } + + return s; +} + +/* return pointer to first delim in s that is not escaped + * and if do_brackets is set, not in [] (note possible [::], [..], [==], inside []) + * return pointer to trailing nul byte if no delim found + * + * any escaped character that is not special is just itself (POSIX undefined) + * FIXME: pull out into some util thing, will be useful for ed as well + */ +static char * +find_delim(char *s, Rune delim, int do_brackets) +{ + enum { + OUTSIDE , /* not in brackets */ + BRACKETS_OPENING, /* last char was first [ or last two were first [^ */ + BRACKETS_INSIDE , /* inside [] */ + INSIDE_OPENING , /* inside [] and last char was [ */ + CLASS_INSIDE , /* inside class [::], or colating element [..] or [==], inside [] */ + CLASS_CLOSING , /* inside class [::], or colating element [..] or [==], and last character was the respective : . or = */ + } state = OUTSIDE; + + Rune r, c = 0; /* no c won't be used uninitialized, shutup -Wall */ + size_t rlen; + int escape = 0; + char *end = s + strlen(s); + + for (; *s; s += rlen) { + rlen = echarntorune(&r, s, end - s); + + if (state == BRACKETS_OPENING && r == '^' ) { continue; } + else if (state == BRACKETS_OPENING && r == ']' ) { state = BRACKETS_INSIDE ; continue; } + else if (state == BRACKETS_OPENING ) { state = BRACKETS_INSIDE ; } + + if (state == CLASS_CLOSING && r == ']' ) { state = BRACKETS_INSIDE ; } + else if (state == CLASS_CLOSING ) { state = CLASS_INSIDE ; } + else if (state == CLASS_INSIDE && r == c ) { state = CLASS_CLOSING ; } + else if (state == INSIDE_OPENING && (r == ':' || + r == '.' || + r == '=') ) { state = CLASS_INSIDE ; c = r; } + else if (state == INSIDE_OPENING && r == ']' ) { state = OUTSIDE ; } + else if (state == BRACKETS_INSIDE && r == '[' ) { state = INSIDE_OPENING ; } + else if (state == BRACKETS_INSIDE && r == ']' ) { state = OUTSIDE ; } + else if (state == OUTSIDE && escape ) { escape = 0 ; } + else if (state == OUTSIDE && r == '\\' ) { escape = 1 ; } + else if (state == OUTSIDE && r == delim) return s; + else if (state == OUTSIDE && do_brackets && r == '[' ) { state = BRACKETS_OPENING; } + } + return s; +} + +static char * +chomp(char *s) +{ + return chompr(s, 0); +} + +/* eat all leading whitespace and occurrences of rune */ +static char * +chompr(char *s, Rune rune) +{ + Rune r; + size_t rlen; + char *end = s + strlen(s); + + while (*s && (rlen = echarntorune(&r, s, end - s)) && (isspacerune(r) || r == rune)) + s += rlen; + return s; +} + +/* convert first nrunes Runes from UTF-8 string s in allocated Rune* + * NOTE: sequence must be valid UTF-8, check first */ +static Rune * +strtorunes(char *s, size_t nrunes) +{ + Rune *rs, *rp; + + rp = rs = ereallocarray(NULL, nrunes + 1, sizeof(*rs)); + + while (nrunes--) + s += chartorune(rp++, s); + + *rp = '\0'; + return rs; +} + +static long +stol(char *s, char **endp) +{ + long n; + errno = 0; + n = strtol(s, endp, 10); + + if (errno) + leprintf("strtol:"); + if (*endp == s) + leprintf("strtol: invalid number"); + + return n; +} + +/* from beg to end replace "\\d" with "d" and "\\n" with "\n" (where d is delim) + * if delim is 'n' and n_newline is 0 then "\\n" is replaced with "n" (normal) + * if delim is 'n' and n_newline is 1 then "\\n" is replaced with "\n" (y command) + * if delim is 0 all escaped characters represent themselves (aci text) + * memmove rest of string (beyond end) into place + * return the number of converted escapes (backslashes removed) + * FIXME: this has had too many corner cases slapped on and is ugly. rewrite better + */ +static size_t +escapes(char *beg, char *end, Rune delim, int n_newline) +{ + size_t num = 0; + char *src = beg, *dst = beg; + + while (src < end) { + /* handle escaped backslash specially so we don't think the second + * backslash is escaping something */ + if (*src == '\\' && src[1] == '\\') { + *dst++ = *src++; + if (delim) + *dst++ = *src++; + else + src++; + } else if (*src == '\\' && !delim) { + src++; + } else if (*src == '\\' && src[1]) { + Rune r; + size_t rlen; + num++; + src++; + rlen = echarntorune(&r, src, end - src); + + if (r == 'n' && delim == 'n') { + *src = n_newline ? '\n' : 'n'; /* src so we can still memmove() */ + } else if (r == 'n') { + *src = '\n'; + } else if (r != delim) { + *dst++ = '\\'; + num--; + } + + memmove(dst, src, rlen); + dst += rlen; + src += rlen; + } else { + *dst++ = *src++; + } + } + memmove(dst, src, strlen(src) + 1); + return num; +} + +static size_t +echarntorune(Rune *r, char *s, size_t n) +{ + size_t rlen = charntorune(r, s, n); + if (!rlen || *r == Runeerror) + leprintf("invalid UTF-8"); + return rlen; +} + +static void +insert_labels(void) +{ + size_t i; + Cmd *from, *to; + + while (branches.size) { + from = prog + (ptrdiff_t)pop(&branches); + + if (!from->u.label) {/* no label branch to end of script */ + from->u.jump = pc - 1; + } else { + for (i = 0; i < labels.size; i++) { + to = prog + (ptrdiff_t)labels.data[i]; + if (!strcmp(from->u.label, to->u.label)) { + from->u.jump = to; + break; + } + } + if (i == labels.size) + leprintf("bad label"); + } + } +} + +/* + * Getargs / Freeargs + * Read argument from s, return pointer to one past last character of argument + */ + +/* POSIX compliant + * i\ + * foobar + * + * also allow the following non POSIX compliant + * i # empty line + * ifoobar + * ifoobar\ + * baz + * + * FIXME: GNU and busybox discard leading spaces + * i foobar + * i foobar + * ifoobar + * are equivalent in GNU and busybox. We don't. Should we? + */ +static char * +get_aci_arg(Cmd *c, char *s) +{ + c->u.acir.print = check_puts; + c->u.acir.str = (String){ NULL, 0 }; + + gflags.aci_cont = !!*s; /* no continue flag if empty string */ + + /* neither empty string nor POSIX compliant */ + if (*s && !(*s == '\\' && !s[1])) + aci_append(c, s); + + return s + strlen(s); +} + +static void +aci_append(Cmd *c, char *s) +{ + char *end = s + strlen(s), *p = end; + + gflags.aci_cont = 0; + while (--p >= s && *p == '\\') + gflags.aci_cont = !gflags.aci_cont; + + if (gflags.aci_cont) + *--end = '\n'; + + escapes(s, end, 0, 0); + stracat(&c->u.acir.str, s); +} + +static void +free_acir_arg(Cmd *c) +{ + free(c->u.acir.str.str); +} + +/* POSIX dictates that label is rest of line, including semicolons, trailing + * whitespace, closing braces, etc. and can be limited to 8 bytes + * + * I allow a semicolon or closing brace to terminate a label name, it's not + * POSIX compliant, but it's useful and every sed version I've tried to date + * does the same. + * + * FIXME: POSIX dictates that leading whitespace is ignored but trailing + * whitespace is not. This is annoying and we should probably get rid of it. + */ +static char * +get_bt_arg(Cmd *c, char *s) +{ + char *p = semicolon_arg(s = chomp(s)); + + if (p != s) { + c->u.label = estrndup(s, p - s); + } else { + c->u.label = NULL; + } + + push(&branches, (void *)(c - prog)); + + return p; +} + +/* POSIX dictates file name is rest of line including semicolons, trailing + * whitespace, closing braces, etc. and file name must be preceded by a space + * + * I allow a semicolon or closing brace to terminate a file name and don't + * enforce leading space. + * + * FIXME: decide whether trailing whitespace should be included and fix + * accordingly + */ +static char * +get_r_arg(Cmd *c, char *s) +{ + char *p = semicolon_arg(s = chomp(s)); + + if (p == s) + leprintf("no file name"); + + c->u.acir.str.str = estrndup(s, p - s); + c->u.acir.print = write_file; + + return p; +} + +/* we allow "\\n" in replacement text to mean "\n" (undefined in POSIX) + * + * FIXME: allow other escapes in regex and replacement? if so change escapes() + */ +static char * +get_s_arg(Cmd *c, char *s) +{ + Rune delim, r; + Cmd buf; + char *p; + int esc, lastre; + + /* s/Find/Replace/Flags */ + + /* Find */ + if (!gflags.s_cont) { /* NOT continuing from literal newline in replacement text */ + lastre = 0; + c->u.s.repl = (String){ NULL, 0 }; + c->u.s.occurrence = 1; + c->u.s.file = NULL; + c->u.s.p = 0; + + if (!*s || *s == '\\') + leprintf("bad delimiter"); + + p = s + strlen(s); + s += echarntorune(&delim, s, p - s); + c->u.s.delim = delim; + + echarntorune(&r, s, p - s); + if (r == delim) /* empty regex */ + lastre = 1; + + p = find_delim(s, delim, 1); + if (!*p) + leprintf("missing second delimiter"); + p -= escapes(s, p, delim, 0); + *p = '\0'; + + if (lastre) { + c->u.s.re = NULL; + } else { + c->u.s.re = emalloc(sizeof(*c->u.s.re)); + /* FIXME: different eregcomp that calls fatal */ + eregcomp(c->u.s.re, s, gflags.E ? REG_EXTENDED : 0); + } + s = p + runelen(delim); + } + + /* Replace */ + delim = c->u.s.delim; + + p = find_delim(s, delim, 0); + p -= escapes(s, p, delim, 0); + if (!*p) { /* no third delimiter */ + /* FIXME: same backslash counting as aci_append() */ + if (p[-1] != '\\') + leprintf("missing third delimiter or "); + p[-1] = '\n'; + gflags.s_cont = 1; + } else { + gflags.s_cont = 0; + } + + /* check for bad references in replacement text */ + *p = '\0'; + for (esc = 0, p = s; *p; p++) { + if (esc) { + esc = 0; + if (isdigit(*p) && c->u.s.re && (size_t)(*p - '0') > c->u.s.re->re_nsub) + leprintf("back reference number greater than number of groups"); + } else if (*p == '\\') { + esc = 1; + } + } + stracat(&c->u.s.repl, s); + + if (gflags.s_cont) + return p; + + s = p + runelen(delim); + + /* Flags */ + p = semicolon_arg(s = chomp(s)); + + /* FIXME: currently for simplicity take last of g or occurrence flags and + * ignore multiple p flags. need to fix that */ + for (; s < p; s++) { + if (isdigit(*s)) { + c->u.s.occurrence = stol(s, &s); + s--; /* for loop will advance pointer */ + } else { + switch (*s) { + case 'g': c->u.s.occurrence = 0; break; + case 'p': c->u.s.p = 1; break; + case 'w': + /* must be last flag, take everything up to newline/semicolon + * s == p after this */ + s = get_w_arg(&buf, chomp(s+1)); + c->u.s.file = buf.u.file; + break; + } + } + } + return p; +} + +static void +free_s_arg(Cmd *c) +{ + if (c->u.s.re) + regfree(c->u.s.re); + free(c->u.s.re); + free(c->u.s.repl.str); +} + +/* see get_r_arg notes */ +static char * +get_w_arg(Cmd *c, char *s) +{ + char *p = semicolon_arg(s = chomp(s)); + Wfile *w, **wp; + + if (p == s) + leprintf("no file name"); + + for (wp = (Wfile **)wfiles.data; (size_t)(wp - (Wfile **)wfiles.data) < wfiles.size; wp++) { + if (strlen((*wp)->path) == (size_t)(p - s) && !strncmp(s, (*wp)->path, p - s)) { + c->u.file = (*wp)->file; + return p; + } + } + + w = emalloc(sizeof(*w)); + w->path = estrndup(s, p - s); + + if (!(w->file = fopen(w->path, "w"))) + leprintf("fopen failed"); + + c->u.file = w->file; + + push(&wfiles, w); + return p; +} + +static char * +get_y_arg(Cmd *c, char *s) +{ + Rune delim; + char *p = s + strlen(s); + size_t rlen = echarntorune(&delim, s, p - s); + size_t nrunes1, nrunes2; + + c->u.y.set1 = c->u.y.set2 = NULL; + + s += rlen; + p = find_delim(s, delim, 0); + p -= escapes(s, p, delim, 1); + nrunes1 = utfnlen(s, p - s); + c->u.y.set1 = strtorunes(s, nrunes1); + + s = p + rlen; + p = find_delim(s, delim, 0); + p -= escapes(s, p, delim, 1); + nrunes2 = utfnlen(s, p - s); + + if (nrunes1 != nrunes2) + leprintf("different set lengths"); + + c->u.y.set2 = strtorunes(s, utfnlen(s, p - s)); + + return p + rlen; +} + +static void +free_y_arg(Cmd *c) +{ + free(c->u.y.set1); + free(c->u.y.set2); +} + +/* see get_bt_arg notes */ +static char * +get_colon_arg(Cmd *c, char *s) +{ + char *p = semicolon_arg(s = chomp(s)); + + if (p == s) + leprintf("no label name"); + + c->u.label = estrndup(s, p - s); + push(&labels, (void *)(c - prog)); + return p; +} + +static char * +get_lbrace_arg(Cmd *c, char *s) +{ + push(&braces, (void *)(c - prog)); + return s; +} + +static char * +get_rbrace_arg(Cmd *c, char *s) +{ + Cmd *lbrace; + + if (!braces.size) + leprintf("extra }"); + + lbrace = prog + (ptrdiff_t)pop(&braces); + lbrace->u.offset = c - prog; + return s; +} + +/* s points to beginning of an argument that may be semicolon terminated + * return pointer to semicolon or nul byte after string + * or closing brace as to not force ; before } + * FIXME: decide whether or not to eat trailing whitespace for arguments that + * we allow semicolon/brace termination that POSIX doesn't + * b, r, t, w, : + * POSIX says trailing whitespace is part of label name, file name, etc. + * we should probably eat it + */ +static char * +semicolon_arg(char *s) +{ + char *p = strpbrk(s, ";}"); + if (!p) + p = s + strlen(s); + return p; +} + +static void +run(void) +{ + lineno = 0; + if (braces.size) + leprintf("extra {"); + + /* genbuf has already been initialized, patt will be in new_line + * (or we'll halt) */ + stracpy(&hold, ""); + + insert_labels(); + next_file(); + new_line(); + + for (pc = prog; !gflags.halt; pc++) + pc->fninfo->fn(pc); +} + +/* return true if we are in range for c, set c->in_match appropriately */ +static int +in_range(Cmd *c) +{ + if (match_addr(&c->range.beg)) { + if (c->range.naddr == 2) { + if (c->range.end.type == LINE && c->range.end.u.lineno <= lineno) + c->in_match = 0; + else + c->in_match = 1; + } + return !c->negate; + } + if (c->in_match && match_addr(&c->range.end)) { + c->in_match = 0; + return !c->negate; + } + return c->in_match ^ c->negate; +} + +/* return true if addr matches current line */ +static int +match_addr(Addr *a) +{ + switch (a->type) { + default: + case IGNORE: return 0; + case EVERY: return 1; + case LINE: return lineno == a->u.lineno; + case LAST: + while (is_eof(file) && !next_file()) + ; + return !file; + case REGEX: + lastre = a->u.re; + return !regexec(a->u.re, patt.str, 0, NULL, 0); + case LASTRE: + if (!lastre) + leprintf("no previous regex"); + return !regexec(lastre, patt.str, 0, NULL, 0); + } +} + +/* move to next input file + * stdin if first call and no files + * return 0 for success and 1 for no more files + */ +static int +next_file(void) +{ + static unsigned char first = 1; + + if (file == stdin) + clearerr(file); + else if (file) + fshut(file, ""); + file = NULL; + + do { + if (!*files) { + if (first) /* given no files, default to stdin */ + file = stdin; + /* else we've used all our files, leave file = NULL */ + } else if (!strcmp(*files, "-")) { + file = stdin; + files++; + } else if (!(file = fopen(*files++, "r"))) { + /* warn this file didn't open, but move on to next */ + weprintf("fopen:"); + } + } while (!file && *files); + first = 0; + + return !file; +} + +/* test if stream is at EOF */ +static int +is_eof(FILE *f) +{ + int c; + + if (!f || feof(f)) + return 1; + + c = fgetc(f); + if (c == EOF && ferror(f)) + eprintf("fgetc:"); + if (c != EOF && ungetc(c, f) == EOF) + eprintf("ungetc EOF\n"); + + return c == EOF; +} + +/* perform writes that were scheduled + * for aci this is check_puts(string, stdout) + * for r this is write_file(path, stdout) + */ +static void +do_writes(void) +{ + Cmd *c; + size_t i; + + for (i = 0; i < writes.size; i++) { + c = writes.data[i]; + c->u.acir.print(c->u.acir.str.str, stdout); + } + writes.size = 0; +} + +/* used for r's u.acir.print() + * FIXME: something like util's concat() would be better + */ +static void +write_file(char *path, FILE *out) +{ + FILE *in = fopen(path, "r"); + if (!in) /* no file is treated as empty file */ + return; + + while (read_line(in, &genbuf) != EOF) + check_puts(genbuf.str, out); + + fshut(in, path); +} + +static void +check_puts(char *s, FILE *f) +{ + if (s && fputs(s, f) == EOF) + eprintf("fputs:"); + if (fputs("\n", f) == EOF) + eprintf("fputs:"); +} + +/* iterate from beg to end updating ranges so we don't miss any commands + * e.g. sed -n '1d;1,3p' should still print lines 2 and 3 + */ +static void +update_ranges(Cmd *beg, Cmd *end) +{ + while (beg < end) + in_range(beg++); +} + +/* + * Sed functions + */ +static void +cmd_a(Cmd *c) +{ + if (in_range(c)) + push(&writes, c); +} + +static void +cmd_b(Cmd *c) +{ + if (!in_range(c)) + return; + + /* if we jump backwards update to end, otherwise update to destination */ + update_ranges(c + 1, c->u.jump > c ? c->u.jump : prog + pcap); + pc = c->u.jump; +} + +static void +cmd_c(Cmd *c) +{ + if (!in_range(c)) + return; + + /* write the text on the last line of the match */ + if (!c->in_match) + check_puts(c->u.acir.str.str, stdout); + /* otherwise start the next cycle without printing pattern space + * effectively deleting the text */ + new_next(); +} + +static void +cmd_d(Cmd *c) +{ + if (!in_range(c)) + return; + + new_next(); +} + +static void +cmd_D(Cmd *c) +{ + char *p; + + if (!in_range(c)) + return; + + if ((p = strchr(patt.str, '\n'))) { + p++; + memmove(patt.str, p, strlen(p) + 1); + old_next(); + } else { + new_next(); + } +} + +static void +cmd_g(Cmd *c) +{ + if (in_range(c)) + stracpy(&patt, hold.str); +} + +static void +cmd_G(Cmd *c) +{ + if (!in_range(c)) + return; + + stracat(&patt, "\n"); + stracat(&patt, hold.str); +} + +static void +cmd_h(Cmd *c) +{ + if (in_range(c)) + stracpy(&hold, patt.str); +} + +static void +cmd_H(Cmd *c) +{ + if (!in_range(c)) + return; + + stracat(&hold, "\n"); + stracat(&hold, patt.str); +} + +static void +cmd_i(Cmd *c) +{ + if (in_range(c)) + check_puts(c->u.acir.str.str, stdout); +} + +/* I think it makes sense to print invalid UTF-8 sequences in octal to satisfy + * the "visually unambiguous form" sed(1p) + */ +static void +cmd_l(Cmd *c) +{ + Rune r; + char *p, *end; + size_t rlen; + + char *escapes[] = { /* FIXME: 7 entries and search instead of 127 */ + ['\\'] = "\\\\", ['\a'] = "\\a", ['\b'] = "\\b", + ['\f'] = "\\f" , ['\r'] = "\\r", ['\t'] = "\\t", + ['\v'] = "\\v" , [0x7f] = NULL, /* fill out the table */ + }; + + if (!in_range(c)) + return; + + /* FIXME: line wrapping. sed(1p) says "length at which folding occurs is + * unspecified, but should be appropraite for the output device" + * just wrap at 80 Runes? + */ + for (p = patt.str, end = p + strlen(p); p < end; p += rlen) { + if (isascii(*p) && escapes[(unsigned int)*p]) { + fputs(escapes[(unsigned int)*p], stdout); + rlen = 1; + } else if (!(rlen = charntorune(&r, p, end - p))) { + /* ran out of chars, print the bytes of the short sequence */ + for (; p < end; p++) + printf("\\%03hho", (unsigned char)*p); + break; + } else if (r == Runeerror) { + for (; rlen; rlen--, p++) + printf("\\%03hho", (unsigned char)*p); + } else { + while (fwrite(p, rlen, 1, stdout) < 1 && errno == EINTR) + ; + if (ferror(stdout)) + eprintf("fwrite:"); + } + } + check_puts("$", stdout); +} + +static void +cmd_n(Cmd *c) +{ + if (!in_range(c)) + return; + + if (!gflags.n) + check_puts(patt.str, stdout); + do_writes(); + new_line(); +} + +static void +cmd_N(Cmd *c) +{ + if (!in_range(c)) + return; + do_writes(); + app_line(); +} + +static void +cmd_p(Cmd *c) +{ + if (in_range(c)) + check_puts(patt.str, stdout); +} + +static void +cmd_P(Cmd *c) +{ + char *p; + + if (!in_range(c)) + return; + + if ((p = strchr(patt.str, '\n'))) + *p = '\0'; + + check_puts(patt.str, stdout); + + if (p) + *p = '\n'; +} + +static void +cmd_q(Cmd *c) +{ + if (!in_range(c)) + return; + + if (!gflags.n) + check_puts(patt.str, stdout); + do_writes(); + gflags.halt = 1; +} + +static void +cmd_r(Cmd *c) +{ + if (in_range(c)) + push(&writes, c); +} + +static void +cmd_s(Cmd *c) +{ + String tmp; + Rune r; + size_t plen, rlen, len; + char *p, *s, *end; + unsigned int matches = 0, last_empty = 1, qflag = 0, cflags = 0; + regex_t *re; + regmatch_t *rm, *pmatch = NULL; + + if (!in_range(c)) + return; + + if (!c->u.s.re && !lastre) + leprintf("no previous regex"); + + re = c->u.s.re ? c->u.s.re : lastre; + lastre = re; + + plen = re->re_nsub + 1; + pmatch = ereallocarray(NULL, plen, sizeof(regmatch_t)); + + *genbuf.str = '\0'; + s = patt.str; + + while (!qflag && !regexec(re, s, plen, pmatch, cflags)) { + cflags = REG_NOTBOL; /* match against beginning of line first time, but not again */ + if (!*s) /* match against empty string first time, but not again */ + qflag = 1; + + /* don't substitute if last match was not empty but this one is. + * s_a*_._g + * foobar -> .f.o.o.b.r. + */ + if ((last_empty || pmatch[0].rm_eo) && + (++matches == c->u.s.occurrence || !c->u.s.occurrence)) { + /* copy over everything before the match */ + strnacat(&genbuf, s, pmatch[0].rm_so); + + /* copy over replacement text, taking into account &, backreferences, and \ escapes */ + for (p = c->u.s.repl.str, len = strcspn(p, "\\&"); *p; len = strcspn(++p, "\\&")) { + strnacat(&genbuf, p, len); + p += len; + switch (*p) { + default: leprintf("this shouldn't be possible"); + case '\0': + /* we're at the end, back up one so the ++p will put us on + * the null byte to break out of the loop */ + --p; + break; + case '&': + strnacat(&genbuf, s + pmatch[0].rm_so, pmatch[0].rm_eo - pmatch[0].rm_so); + break; + case '\\': + if (isdigit(*++p)) { /* backreference */ + /* only need to check here if using lastre, otherwise we checked when building */ + if (!c->u.s.re && (size_t)(*p - '0') > re->re_nsub) + leprintf("back reference number greater than number of groups"); + rm = &pmatch[*p - '0']; + strnacat(&genbuf, s + rm->rm_so, rm->rm_eo - rm->rm_so); + } else { /* character after backslash taken literally (well one byte, but it works) */ + strnacat(&genbuf, p, 1); + } + break; + } + } + } else { + /* not replacing, copy over everything up to and including the match */ + strnacat(&genbuf, s, pmatch[0].rm_eo); + } + + if (!pmatch[0].rm_eo) { /* empty match, advance one rune and add it to output */ + end = s + strlen(s); + rlen = charntorune(&r, s, end - s); + + if (!rlen) { /* ran out of bytes, copy short sequence */ + stracat(&genbuf, s); + s = end; + } else { /* copy whether or not it's a good rune */ + strnacat(&genbuf, s, rlen); + s += rlen; + } + } + last_empty = !pmatch[0].rm_eo; + s += pmatch[0].rm_eo; + } + free(pmatch); + + if (!(matches && matches >= c->u.s.occurrence)) /* no replacement */ + return; + + gflags.s = 1; + + stracat(&genbuf, s); + + tmp = patt; + patt = genbuf; + genbuf = tmp; + + if (c->u.s.p) + check_puts(patt.str, stdout); + if (c->u.s.file) + check_puts(patt.str, c->u.s.file); +} + +static void +cmd_t(Cmd *c) +{ + if (!in_range(c) || !gflags.s) + return; + + /* if we jump backwards update to end, otherwise update to destination */ + update_ranges(c + 1, c->u.jump > c ? c->u.jump : prog + pcap); + pc = c->u.jump; + gflags.s = 0; +} + +static void +cmd_w(Cmd *c) +{ + if (in_range(c)) + check_puts(patt.str, c->u.file); +} + +static void +cmd_x(Cmd *c) +{ + String tmp; + + if (!in_range(c)) + return; + + tmp = patt; + patt = hold; + hold = tmp; +} + +static void +cmd_y(Cmd *c) +{ + String tmp; + Rune r, *rp; + size_t n, rlen; + char *s, *end, buf[UTFmax]; + + if (!in_range(c)) + return; + + *genbuf.str = '\0'; + for (s = patt.str, end = s + strlen(s); *s; s += rlen) { + if (!(rlen = charntorune(&r, s, end - s))) { /* ran out of chars, copy rest */ + stracat(&genbuf, s); + break; + } else if (r == Runeerror) { /* bad UTF-8 sequence, copy bytes */ + strnacat(&genbuf, s, rlen); + } else { + for (rp = c->u.y.set1; *rp; rp++) + if (*rp == r) + break; + if (*rp) { /* found r in set1, replace with Rune from set2 */ + n = runetochar(buf, c->u.y.set2 + (rp - c->u.y.set1)); + strnacat(&genbuf, buf, n); + } else { + strnacat(&genbuf, s, rlen); + } + } + } + tmp = patt; + patt = genbuf; + genbuf = tmp; +} + +static void +cmd_colon(Cmd *c) +{ +} + +static void +cmd_equal(Cmd *c) +{ + if (in_range(c)) + printf("%zu\n", lineno); +} + +static void +cmd_lbrace(Cmd *c) +{ + Cmd *jump; + + if (in_range(c)) + return; + + /* update ranges on all commands we skip */ + jump = prog + c->u.offset; + update_ranges(c + 1, jump); + pc = jump; +} + +static void +cmd_rbrace(Cmd *c) +{ +} + +/* not actually a sed function, but acts like one, put in last spot of script */ +static void +cmd_last(Cmd *c) +{ + if (!gflags.n) + check_puts(patt.str, stdout); + do_writes(); + new_next(); +} + +/* + * Actions + */ + +/* read new line, continue current cycle */ +static void +new_line(void) +{ + while (read_line(file, &patt) == EOF) { + if (next_file()) { + gflags.halt = 1; + return; + } + } + gflags.s = 0; + lineno++; +} + +/* append new line, continue current cycle + * FIXME: used for N, POSIX specifies do not print pattern space when out of + * input, but GNU does so busybox does as well. Currently we don't. + * Should we? + */ +static void +app_line(void) +{ + while (read_line(file, &genbuf) == EOF) { + if (next_file()) { + gflags.halt = 1; + return; + } + } + + stracat(&patt, "\n"); + stracat(&patt, genbuf.str); + gflags.s = 0; + lineno++; +} + +/* read new line, start new cycle */ +static void +new_next(void) +{ + *patt.str = '\0'; + update_ranges(pc + 1, prog + pcap); + new_line(); + pc = prog - 1; +} + +/* keep old pattern space, start new cycle */ +static void +old_next(void) +{ + update_ranges(pc + 1, prog + pcap); + pc = prog - 1; +} + +int +main(int argc, char *argv[]) +{ + char *arg; + int ret = 0, script = 0; + + ARGBEGIN { + case 'n': + gflags.n = 1; + break; + case 'r': + case 'E': + gflags.E = 1; + break; + case 'e': + arg = EARGF(usage()); + compile(arg, 0); + script = 1; + break; + case 'f': + arg = EARGF(usage()); + compile(arg, 1); + script = 1; + break; + default : usage(); + } ARGEND + + /* no script to run */ + if (!script && !argc) + usage(); + + /* no script yet, next argument is script */ + if (!script) + compile(*argv++, 0); + + /* shrink/grow memory to fit and add our last instruction */ + resize((void **)&prog, &pcap, sizeof(*prog), pc - prog + 1, NULL); + pc = prog + pcap - 1; + pc->fninfo = &(Fninfo){ cmd_last, NULL, NULL, 0 }; + + files = argv; + run(); + + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/seq.1 b/source/sbase/seq.1 new file mode 100644 index 00000000..f8f7ee96 --- /dev/null +++ b/source/sbase/seq.1 @@ -0,0 +1,39 @@ +.Dd 2015-10-08 +.Dt SEQ 1 +.Os sbase +.Sh NAME +.Nm seq +.Nd print a sequence of numbers +.Sh SYNOPSIS +.Nm +.Op Fl w +.Op Fl f Ar fmt +.Op Fl s Ar sep +.Op Ar startnum Op Ar step +.Ar endnum +.Sh DESCRIPTION +.Nm +writes a sequence of numbers from +.Ar startnum +(default: 1) to +.Ar endnum +in +.Ar step +intervals (default: 1) +to stdout. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl f Ar fmt +Use +.Ar fmt +as the output line format according to +.Xr printf 3 . +.It Fl s Ar sep +Print +.Ar sep +between output lines. The default is "\en". +.It Fl w +Print out lines in equal width. +.El +.Sh SEE ALSO +.Xr printf 3 diff --git a/source/sbase/seq.c b/source/sbase/seq.c new file mode 100644 index 00000000..70763d1e --- /dev/null +++ b/source/sbase/seq.c @@ -0,0 +1,147 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "util.h" + +static int +digitsleft(const char *d) +{ + int shift; + char *exp; + + if (*d == '+') + d++; + exp = strpbrk(d, "eE"); + shift = exp ? estrtonum(exp + 1, INT_MIN, INT_MAX) : 0; + + return MAX(0, strspn(d, "-0123456789") + shift); +} + +static int +digitsright(const char *d) +{ + int shift, after; + char *exp; + + exp = strpbrk(d, "eE"); + shift = exp ? estrtonum(&exp[1], INT_MIN, INT_MAX) : 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; + } +} + +static void +usage(void) +{ + eprintf("usage: %s [-f fmt] [-s sep] [-w] " + "[startnum [step]] endnum\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + double start, step, end, out, dir; + int wflag = 0, left, right; + char *tmp, ftmp[BUFSIZ], *fmt = ftmp; + const char *starts = "1", *steps = "1", *ends = "1", *sep = "\n"; + + 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); + } + putchar('\n'); + + return fshut(stdout, ""); +} diff --git a/source/sbase/setsid.1 b/source/sbase/setsid.1 new file mode 100644 index 00000000..d43bcfc9 --- /dev/null +++ b/source/sbase/setsid.1 @@ -0,0 +1,17 @@ +.Dd 2015-10-08 +.Dt SETSID 1 +.Os sbase +.Sh NAME +.Nm setsid +.Nd run a command in a new session +.Sh SYNOPSIS +.Nm +.Ar cmd +.Op Ar arg ... +.Sh DESCRIPTION +.Nm +runs +.Ar cmd +in a new session. +.Sh SEE ALSO +.Xr setsid 2 diff --git a/source/sbase/setsid.c b/source/sbase/setsid.c new file mode 100644 index 00000000..4c885a4c --- /dev/null +++ b/source/sbase/setsid.c @@ -0,0 +1,40 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s cmd [arg ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int savederrno; + + argv0 = argv[0], argc--, argv++; + + if (!argc) + 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]); + + _exit(126 + (savederrno == ENOENT)); +} diff --git a/source/sbase/sha1.h b/source/sbase/sha1.h new file mode 100644 index 00000000..86317770 --- /dev/null +++ b/source/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/source/sbase/sha1sum.1 b/source/sbase/sha1sum.1 new file mode 100644 index 00000000..c459918f --- /dev/null +++ b/source/sbase/sha1sum.1 @@ -0,0 +1,32 @@ +.Dd 2015-10-08 +.Dt SHA1SUM 1 +.Os sbase +.Sh NAME +.Nm sha1sum +.Nd compute or check SHA-1 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes SHA-1 (160-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of SHA-1 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/source/sbase/sha1sum.c b/source/sbase/sha1sum.c new file mode 100644 index 00000000..4f3ae77c --- /dev/null +++ b/source/sbase/sha1sum.c @@ -0,0 +1,41 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#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[]) +{ + int ret = 0, (*cryptfunc)(int, char **, struct crypt_ops *, uint8_t *, size_t) = cryptmain; + uint8_t md[SHA1_DIGEST_LENGTH]; + + ARGBEGIN { + case 'c': + cryptfunc = cryptcheck; + break; + default: + usage(); + } ARGEND + + ret |= cryptfunc(argc, argv, &sha1_ops, md, sizeof(md)); + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/sha224.h b/source/sbase/sha224.h new file mode 100644 index 00000000..d7f40532 --- /dev/null +++ b/source/sbase/sha224.h @@ -0,0 +1,16 @@ +/* public domain sha224 implementation based on fips180-3 */ + +#include "sha256.h" + +#define sha224 sha256 /*struct*/ + +enum { SHA224_DIGEST_LENGTH = 28 }; + +/* reset state */ +void sha224_init(void *ctx); +/* process message */ +#define sha224_update sha256_update +/* 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 sha224_sum(void *ctx, uint8_t md[SHA224_DIGEST_LENGTH]); diff --git a/source/sbase/sha224sum.1 b/source/sbase/sha224sum.1 new file mode 100644 index 00000000..ff7ec974 --- /dev/null +++ b/source/sbase/sha224sum.1 @@ -0,0 +1,32 @@ +.Dd 2016-02-24 +.Dt SHA224SUM 1 +.Os sbase +.Sh NAME +.Nm sha224sum +.Nd compute or check SHA-224 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes SHA-224 (224-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of SHA-224 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/source/sbase/sha224sum.c b/source/sbase/sha224sum.c new file mode 100644 index 00000000..5c4a6cba --- /dev/null +++ b/source/sbase/sha224sum.c @@ -0,0 +1,41 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "crypt.h" +#include "sha224.h" +#include "util.h" + +static struct sha224 s; +struct crypt_ops sha224_ops = { + sha224_init, + sha224_update, + sha224_sum, + &s, +}; + +static void +usage(void) +{ + eprintf("usage: %s [-c] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int ret = 0, (*cryptfunc)(int, char **, struct crypt_ops *, uint8_t *, size_t) = cryptmain; + uint8_t md[SHA224_DIGEST_LENGTH]; + + ARGBEGIN { + case 'c': + cryptfunc = cryptcheck; + break; + default: + usage(); + } ARGEND + + ret |= cryptfunc(argc, argv, &sha224_ops, md, sizeof(md)); + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/sha256.h b/source/sbase/sha256.h new file mode 100644 index 00000000..5968b8e1 --- /dev/null +++ b/source/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/source/sbase/sha256sum.1 b/source/sbase/sha256sum.1 new file mode 100644 index 00000000..8a93a026 --- /dev/null +++ b/source/sbase/sha256sum.1 @@ -0,0 +1,32 @@ +.Dd 2015-10-08 +.Dt SHA256SUM 1 +.Os sbase +.Sh NAME +.Nm sha256sum +.Nd compute or check SHA-256 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes SHA-256 (256-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of SHA-256 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/source/sbase/sha256sum.c b/source/sbase/sha256sum.c new file mode 100644 index 00000000..d863539e --- /dev/null +++ b/source/sbase/sha256sum.c @@ -0,0 +1,41 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#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[]) +{ + int ret = 0, (*cryptfunc)(int, char **, struct crypt_ops *, uint8_t *, size_t) = cryptmain; + uint8_t md[SHA256_DIGEST_LENGTH]; + + ARGBEGIN { + case 'c': + cryptfunc = cryptcheck; + break; + default: + usage(); + } ARGEND + + ret |= cryptfunc(argc, argv, &sha256_ops, md, sizeof(md)); + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/sha384.h b/source/sbase/sha384.h new file mode 100644 index 00000000..2ab9bc49 --- /dev/null +++ b/source/sbase/sha384.h @@ -0,0 +1,16 @@ +/* public domain sha512 implementation based on fips180-3 */ + +#include "sha512.h" + +#define sha384 sha512 /*struct*/ + +enum { SHA384_DIGEST_LENGTH = 48 }; + +/* reset state */ +void sha384_init(void *ctx); +/* process message */ +#define sha384_update sha512_update +/* 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 sha384_sum(void *ctx, uint8_t md[SHA384_DIGEST_LENGTH]); diff --git a/source/sbase/sha384sum.1 b/source/sbase/sha384sum.1 new file mode 100644 index 00000000..c0aa5b69 --- /dev/null +++ b/source/sbase/sha384sum.1 @@ -0,0 +1,32 @@ +.Dd 2016-02-24 +.Dt SHA384SUM 1 +.Os sbase +.Sh NAME +.Nm sha384sum +.Nd compute or check SHA-384 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes SHA-384 (384-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of SHA-384 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/source/sbase/sha384sum.c b/source/sbase/sha384sum.c new file mode 100644 index 00000000..f975b619 --- /dev/null +++ b/source/sbase/sha384sum.c @@ -0,0 +1,41 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "crypt.h" +#include "sha384.h" +#include "util.h" + +static struct sha384 s; +struct crypt_ops sha384_ops = { + sha384_init, + sha384_update, + sha384_sum, + &s, +}; + +static void +usage(void) +{ + eprintf("usage: %s [-c] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int ret = 0, (*cryptfunc)(int, char **, struct crypt_ops *, uint8_t *, size_t) = cryptmain; + uint8_t md[SHA384_DIGEST_LENGTH]; + + ARGBEGIN { + case 'c': + cryptfunc = cryptcheck; + break; + default: + usage(); + } ARGEND + + ret |= cryptfunc(argc, argv, &sha384_ops, md, sizeof(md)); + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/sha512-224.h b/source/sbase/sha512-224.h new file mode 100644 index 00000000..8364fc5f --- /dev/null +++ b/source/sbase/sha512-224.h @@ -0,0 +1,16 @@ +/* public domain sha512/224 implementation based on fips180-3 */ + +#include "sha512.h" + +#define sha512_224 sha512 /*struct*/ + +enum { SHA512_224_DIGEST_LENGTH = 28 }; + +/* reset state */ +void sha512_224_init(void *ctx); +/* process message */ +#define sha512_224_update sha512_update +/* 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_224_sum(void *ctx, uint8_t md[SHA512_224_DIGEST_LENGTH]); diff --git a/source/sbase/sha512-224sum.1 b/source/sbase/sha512-224sum.1 new file mode 100644 index 00000000..50925829 --- /dev/null +++ b/source/sbase/sha512-224sum.1 @@ -0,0 +1,32 @@ +.Dd 2016-02-24 +.Dt SHA512-224SUM 1 +.Os sbase +.Sh NAME +.Nm sha512-224sum +.Nd compute or check SHA-512/224 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes SHA-512/224 (224-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of SHA-512/224 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/source/sbase/sha512-224sum.c b/source/sbase/sha512-224sum.c new file mode 100644 index 00000000..6e4a9d60 --- /dev/null +++ b/source/sbase/sha512-224sum.c @@ -0,0 +1,41 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "crypt.h" +#include "sha512-224.h" +#include "util.h" + +static struct sha512_224 s; +struct crypt_ops sha512_224_ops = { + sha512_224_init, + sha512_224_update, + sha512_224_sum, + &s, +}; + +static void +usage(void) +{ + eprintf("usage: %s [-c] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int ret = 0, (*cryptfunc)(int, char **, struct crypt_ops *, uint8_t *, size_t) = cryptmain; + uint8_t md[SHA512_224_DIGEST_LENGTH]; + + ARGBEGIN { + case 'c': + cryptfunc = cryptcheck; + break; + default: + usage(); + } ARGEND + + ret |= cryptfunc(argc, argv, &sha512_224_ops, md, sizeof(md)); + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/sha512-256.h b/source/sbase/sha512-256.h new file mode 100644 index 00000000..eb0b731d --- /dev/null +++ b/source/sbase/sha512-256.h @@ -0,0 +1,16 @@ +/* public domain sha512/256 implementation based on fips180-3 */ + +#include "sha512.h" + +#define sha512_256 sha512 /*struct*/ + +enum { SHA512_256_DIGEST_LENGTH = 32 }; + +/* reset state */ +void sha512_256_init(void *ctx); +/* process message */ +#define sha512_256_update sha512_update +/* 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_256_sum(void *ctx, uint8_t md[SHA512_256_DIGEST_LENGTH]); diff --git a/source/sbase/sha512-256sum.1 b/source/sbase/sha512-256sum.1 new file mode 100644 index 00000000..db0406d3 --- /dev/null +++ b/source/sbase/sha512-256sum.1 @@ -0,0 +1,32 @@ +.Dd 2016-02-24 +.Dt SHA512-256SUM 1 +.Os sbase +.Sh NAME +.Nm sha512-256sum +.Nd compute or check SHA-512/256 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes SHA-512/256 (256-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of SHA-512/256 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/source/sbase/sha512-256sum.c b/source/sbase/sha512-256sum.c new file mode 100644 index 00000000..c2d582e3 --- /dev/null +++ b/source/sbase/sha512-256sum.c @@ -0,0 +1,41 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "crypt.h" +#include "sha512-256.h" +#include "util.h" + +static struct sha512_256 s; +struct crypt_ops sha512_256_ops = { + sha512_256_init, + sha512_256_update, + sha512_256_sum, + &s, +}; + +static void +usage(void) +{ + eprintf("usage: %s [-c] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int ret = 0, (*cryptfunc)(int, char **, struct crypt_ops *, uint8_t *, size_t) = cryptmain; + uint8_t md[SHA512_256_DIGEST_LENGTH]; + + ARGBEGIN { + case 'c': + cryptfunc = cryptcheck; + break; + default: + usage(); + } ARGEND + + ret |= cryptfunc(argc, argv, &sha512_256_ops, md, sizeof(md)); + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/sha512.h b/source/sbase/sha512.h new file mode 100644 index 00000000..c761712a --- /dev/null +++ b/source/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/source/sbase/sha512sum.1 b/source/sbase/sha512sum.1 new file mode 100644 index 00000000..724e99db --- /dev/null +++ b/source/sbase/sha512sum.1 @@ -0,0 +1,32 @@ +.Dd 2015-10-08 +.Dt SHA512SUM 1 +.Os sbase +.Sh NAME +.Nm sha512sum +.Nd compute or check SHA-512 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes SHA-512 (512-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of SHA-512 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/source/sbase/sha512sum.c b/source/sbase/sha512sum.c new file mode 100644 index 00000000..65a0c2c6 --- /dev/null +++ b/source/sbase/sha512sum.c @@ -0,0 +1,41 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#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[]) +{ + int ret = 0, (*cryptfunc)(int, char **, struct crypt_ops *, uint8_t *, size_t) = cryptmain; + uint8_t md[SHA512_DIGEST_LENGTH]; + + ARGBEGIN { + case 'c': + cryptfunc = cryptcheck; + break; + default: + usage(); + } ARGEND + + ret |= cryptfunc(argc, argv, &sha512_ops, md, sizeof(md)); + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/sleep.1 b/source/sbase/sleep.1 new file mode 100644 index 00000000..2b78bfbb --- /dev/null +++ b/source/sbase/sleep.1 @@ -0,0 +1,22 @@ +.Dd 2015-10-08 +.Dt SLEEP 1 +.Os sbase +.Sh NAME +.Nm sleep +.Nd wait for a number of seconds +.Sh SYNOPSIS +.Nm +.Ar num +.Sh DESCRIPTION +.Nm +waits for +.Ar num +seconds to elapse. +.Sh SEE ALSO +.Xr sleep 3 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/sleep.c b/source/sbase/sleep.c new file mode 100644 index 00000000..b1028ed2 --- /dev/null +++ b/source/sbase/sleep.c @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s num\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + unsigned seconds; + + argv0 = argv[0], argc--, argv++; + + if (argc != 1) + usage(); + + seconds = estrtonum(argv[0], 0, UINT_MAX); + while ((seconds = sleep(seconds)) > 0) + ; + + return 0; +} diff --git a/source/sbase/sort.1 b/source/sbase/sort.1 new file mode 100644 index 00000000..7bd6d621 --- /dev/null +++ b/source/sbase/sort.1 @@ -0,0 +1,100 @@ +.Dd 2016-02-17 +.Dt SORT 1 +.Os sbase +.Sh NAME +.Nm sort +.Nd sort lines +.Sh SYNOPSIS +.Nm +.Op Fl Cbcdfimnru +.Op Fl o Ar outfile +.Op Fl t Ar delim +.Op Fl k Ar key ... +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes the sorted concatenation of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl C +Check that the concatenation of the given +.Ar 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. +.It Fl b +Skip leading whitespace of columns when sorting. +.It Fl c +The same as +.Fl C +except that when disorder is detected, a message is written to stderr +indicating the location of the disorder. +.It Fl d +Skip non-whitespace and non-alphanumeric characters. +.It Fl f +Ignore letter case when sorting. +.It FL i +Skip non-printable characters. +.It Fl k Ar key +Specify a key definition of the form +.Sm off +.Sy S +.No [. +.Sy s +.No ][ +.Sy f +.No ][, +.Sy E +.No [. +.Sy e +.No ][ +.Sy f +.No ]] +.Sm on +where +.Sy S , s , E +and +.Sy 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, +.Sy s +refers to the first character of the specified starting column, +.Sy E +refers to the last column of every line, and +.Sy e +refers to the last character of the ending column. +.Sy f +can be used to specify options +.Sy ( n , b ) +that only apply to this key definition. +.Sy b +is special in that it only applies to the column that it was specified after. +.It Fl m +Assume sorted input, merge only. +.It Fl n +Perform a numeric sort. +.It Fl o Ar outfile +Write output to +.Ar outfile +rather than stdout. +.It Fl r +Reverses the sort. +.It Fl t Ar delim +Set +.Ar delim +as the field delimiter. +.It Fl u +Print equal lines only once. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/sort.c b/source/sbase/sort.c new file mode 100644 index 00000000..90ee9110 --- /dev/null +++ b/source/sbase/sort.c @@ -0,0 +1,434 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "queue.h" +#include "text.h" +#include "utf.h" +#include "util.h" + +struct keydef { + int start_column; + int end_column; + int start_char; + int end_char; + int flags; + TAILQ_ENTRY(keydef) entry; +}; + +enum { + MOD_N = 1 << 0, + MOD_STARTB = 1 << 1, + MOD_ENDB = 1 << 2, + MOD_R = 1 << 3, + MOD_D = 1 << 4, + MOD_F = 1 << 5, + MOD_I = 1 << 6, +}; + +static TAILQ_HEAD(kdhead, keydef) kdhead = TAILQ_HEAD_INITIALIZER(kdhead); + +static int Cflag = 0, cflag = 0, uflag = 0; +static char *fieldsep = NULL; +static size_t fieldseplen = 0; +static struct line col1, col2; + +static void +skipblank(struct line *a) +{ + while (a->len && (*(a->data) == ' ' || *(a->data) == '\t')) { + a->data++; + a->len--; + } +} + +static void +skipnonblank(struct line *a) +{ + while (a->len && (*(a->data) != '\n' && *(a->data) != ' ' && + *(a->data) != '\t')) { + a->data++; + a->len--; + } +} + +static void +skipcolumn(struct line *a, int skip_to_next_col) +{ + char *s; + + if (fieldsep) { + if ((s = memmem(a->data, a->len, fieldsep, fieldseplen))) { + if (skip_to_next_col) { + s += fieldseplen; + a->data = s; + a->len = a->len - (s - a->data); + } + } else { + a->data += a->len - 1; + a->len = 1; + } + } else { + skipblank(a); + skipnonblank(a); + } +} + +static size_t +columns(struct line *line, const struct keydef *kd, struct line *col) +{ + Rune r; + struct line start, end; + size_t len, utflen, rlen; + int i; + + start.data = line->data; + start.len = line->len; + for (i = 1; i < kd->start_column; i++) + skipcolumn(&start, 1); + if (kd->flags & MOD_STARTB) + skipblank(&start); + for (utflen = 0; start.len > 1 && utflen < kd->start_char - 1;) { + rlen = chartorune(&r, start.data); + start.data += rlen; + start.len -= rlen; + utflen++; + } + + end.data = line->data; + end.len = line->len; + if (kd->end_column) { + for (i = 1; i < kd->end_column; i++) + skipcolumn(&end, 1); + if (kd->flags & MOD_ENDB) + skipblank(&end); + if (kd->end_char) { + for (utflen = 0; end.len > 1 && utflen < kd->end_char;) { + rlen = chartorune(&r, end.data); + end.data += rlen; + end.len -= rlen; + utflen++; + } + } else { + skipcolumn(&end, 0); + } + } else { + end.data += end.len - 1; + end.len = 1; + } + len = MAX(0, end.data - start.data); + if (!(col->data) || col->len < len) + col->data = erealloc(col->data, len + 1); + memcpy(col->data, start.data, len); + col->data[len] = '\0'; + if (col->len < len) + col->len = len; + + return len; +} + +static int +skipmodcmp(struct line *a, struct line *b, int flags) +{ + Rune r1, r2; + size_t offa = 0, offb = 0; + + do { + offa += chartorune(&r1, a->data + offa); + offb += chartorune(&r2, b->data + offb); + + if (flags & MOD_D && flags & MOD_I) { + while (offa < a->len && ((!isblankrune(r1) && + !isalnumrune(r1)) || (!isprintrune(r1)))) + offa += chartorune(&r1, a->data + offa); + while (offb < b->len && ((!isblankrune(r2) && + !isalnumrune(r2)) || (!isprintrune(r2)))) + offb += chartorune(&r2, b->data + offb); + } + else if (flags & MOD_D) { + while (offa < a->len && !isblankrune(r1) && + !isalnumrune(r1)) + offa += chartorune(&r1, a->data + offa); + while (offb < b->len && !isblankrune(r2) && + !isalnumrune(r2)) + offb += chartorune(&r2, b->data + offb); + } + else if (flags & MOD_I) { + while (offa < a->len && !isprintrune(r1)) + offa += chartorune(&r1, a->data + offa); + while (offb < b->len && !isprintrune(r2)) + offb += chartorune(&r2, b->data + offb); + } + if (flags & MOD_F) { + r1 = toupperrune(r1); + r2 = toupperrune(r2); + } + } while (r1 && r1 == r2); + + return r1 - r2; +} + +static int +slinecmp(struct line *a, struct line *b) +{ + int res = 0; + long double x, y; + struct keydef *kd; + + TAILQ_FOREACH(kd, &kdhead, entry) { + columns(a, kd, &col1); + columns(b, kd, &col2); + + /* if -u is given, don't use default key definition + * unless it is the only one */ + if (uflag && kd == TAILQ_LAST(&kdhead, kdhead) && + TAILQ_LAST(&kdhead, kdhead) != TAILQ_FIRST(&kdhead)) { + res = 0; + } else if (kd->flags & MOD_N) { + x = strtold(col1.data, NULL); + y = strtold(col2.data, NULL); + res = (x < y) ? -1 : (x > y); + } else if (kd->flags & (MOD_D | MOD_F | MOD_I)) { + res = skipmodcmp(&col1, &col2, kd->flags); + } else { + res = linecmp(&col1, &col2); + } + + if (kd->flags & MOD_R) + res = -res; + if (res) + break; + } + + return res; +} + +static int +check(FILE *fp, const char *fname) +{ + static struct line prev, cur, tmp; + static size_t prevsize, cursize, tmpsize; + ssize_t len; + + if (!prev.data) { + if ((len = getline(&prev.data, &prevsize, fp)) < 0) + eprintf("getline:"); + prev.len = len; + } + while ((len = getline(&cur.data, &cursize, fp)) > 0) { + cur.len = len; + if (uflag > slinecmp(&cur, &prev)) { + if (!Cflag) { + weprintf("disorder %s: ", fname); + fwrite(cur.data, 1, cur.len, stderr); + } + return 1; + } + tmp = cur; + tmpsize = cursize; + cur = prev; + cursize = prevsize; + prev = tmp; + prevsize = tmpsize; + } + + return 0; +} + +static int +parse_flags(char **s, int *flags, int bflag) +{ + while (isalpha((int)**s)) { + switch (*((*s)++)) { + case 'b': + *flags |= bflag; + break; + case 'd': + *flags |= MOD_D; + break; + case 'f': + *flags |= MOD_F; + break; + case 'i': + *flags |= MOD_I; + break; + case 'n': + *flags |= MOD_N; + break; + case 'r': + *flags |= MOD_R; + break; + default: + return -1; + } + } + + return 0; +} + +static void +addkeydef(char *kdstr, int flags) +{ + struct keydef *kd; + + kd = enmalloc(2, sizeof(*kd)); + + /* parse key definition kdstr with format + * start_column[.start_char][flags][,end_column[.end_char][flags]] + */ + kd->start_column = 1; + kd->start_char = 1; + kd->end_column = 0; /* 0 means end of line */ + kd->end_char = 0; /* 0 means end of column */ + kd->flags = flags; + + if ((kd->start_column = strtol(kdstr, &kdstr, 10)) < 1) + enprintf(2, "invalid start column in key definition\n"); + + if (*kdstr == '.') { + if ((kd->start_char = strtol(kdstr + 1, &kdstr, 10)) < 1) + enprintf(2, "invalid start character in key " + "definition\n"); + } + if (parse_flags(&kdstr, &kd->flags, MOD_STARTB) < 0) + enprintf(2, "invalid start flags in key definition\n"); + + if (*kdstr == ',') { + if ((kd->end_column = strtol(kdstr + 1, &kdstr, 10)) < 0) + enprintf(2, "invalid end column in key definition\n"); + if (*kdstr == '.') { + if ((kd->end_char = strtol(kdstr + 1, &kdstr, 10)) < 0) + enprintf(2, "invalid end character in key " + "definition\n"); + } + if (parse_flags(&kdstr, &kd->flags, MOD_ENDB) < 0) + enprintf(2, "invalid end flags in key definition\n"); + } + + if (*kdstr != '\0') + enprintf(2, "invalid key definition\n"); + + TAILQ_INSERT_TAIL(&kdhead, kd, entry); +} + +static void +usage(void) +{ + enprintf(2, "usage: %s [-Cbcdfimnru] [-o outfile] [-t delim] " + "[-k def]... [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp, *ofp = stdout; + struct linebuf linebuf = EMPTY_LINEBUF; + size_t i; + int global_flags = 0, ret = 0; + char *outfile = NULL; + + ARGBEGIN { + case 'C': + Cflag = 1; + break; + case 'b': + global_flags |= MOD_STARTB | MOD_ENDB; + break; + case 'c': + cflag = 1; + break; + case 'd': + global_flags |= MOD_D; + break; + case 'f': + global_flags |= MOD_F; + break; + case 'i': + global_flags |= MOD_I; + break; + case 'k': + addkeydef(EARGF(usage()), global_flags); + break; + case 'm': + /* more or less for free, but for performance-reasons, + * we should keep this flag in mind and maybe some later + * day implement it properly so we don't run out of memory + * while merging large sorted files. + */ + break; + case 'n': + global_flags |= MOD_N; + break; + case 'o': + outfile = EARGF(usage()); + break; + case 'r': + global_flags |= MOD_R; + break; + case 't': + fieldsep = EARGF(usage()); + if (!*fieldsep) + eprintf("empty delimiter\n"); + fieldseplen = unescape(fieldsep); + break; + case 'u': + uflag = 1; + break; + default: + usage(); + } ARGEND + + /* -b shall only apply to custom key definitions */ + if (TAILQ_EMPTY(&kdhead) && global_flags) + addkeydef("1", global_flags & ~(MOD_STARTB | MOD_ENDB)); + addkeydef("1", global_flags & MOD_R); + + if (!argc) { + if (Cflag || cflag) { + if (check(stdin, "") && !ret) + ret = 1; + } else { + getlines(stdin, &linebuf); + } + } else for (; *argv; argc--, argv++) { + if (!strcmp(*argv, "-")) { + *argv = ""; + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + enprintf(2, "fopen %s:", *argv); + continue; + } + if (Cflag || cflag) { + if (check(fp, *argv) && !ret) + ret = 1; + } else { + getlines(fp, &linebuf); + } + if (fp != stdin && fshut(fp, *argv)) + ret = 2; + } + + if (!Cflag && !cflag) { + if (outfile && !(ofp = fopen(outfile, "w"))) + eprintf("fopen %s:", outfile); + + qsort(linebuf.lines, linebuf.nlines, sizeof(*linebuf.lines), + (int (*)(const void *, const void *))slinecmp); + + for (i = 0; i < linebuf.nlines; i++) { + if (!uflag || i == 0 || + slinecmp(&linebuf.lines[i], &linebuf.lines[i - 1])) { + fwrite(linebuf.lines[i].data, 1, + linebuf.lines[i].len, ofp); + } + } + } + + if (fshut(stdin, "") | fshut(stdout, "") | + fshut(stderr, "")) + ret = 2; + + return ret; +} diff --git a/source/sbase/split.1 b/source/sbase/split.1 new file mode 100644 index 00000000..3cd434f8 --- /dev/null +++ b/source/sbase/split.1 @@ -0,0 +1,50 @@ +.Dd 2015-10-08 +.Dt SPLIT 1 +.Os sbase +.Sh NAME +.Nm split +.Nd split up a file +.Sh SYNOPSIS +.Nm +.Op Fl a Ar num +.Op Fl b Ar num[k|m|g] | Fl l Ar num +.Op Fl d +.Op Ar file Op Ar prefix +.Sh DESCRIPTION +.Nm +splits +.Ar file +into files with 1000 lines each, named with +.Ar prefix +"x" followed by 2-digit alphabetical count suffixes. +If +.Nm +runs out of suffixes, it stops after the last valid filename. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl a Ar num +Set suffix length to +.Ar num +characters. +The default is 2. +.It Fl b Ar num[k|m|g] | Fl l Ar num +Start a new file every +.Ar num +bytes | lines. +The units k, m, and g are case insensitive and powers of 2, not 10. +The default is 1000 lines. +.It Fl d +Use decimal rather than alphabetical suffixes. +.El +.Sh SEE ALSO +.Xr cat 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The +.Op Fl d +flag and g unit are an extension to that specification. diff --git a/source/sbase/split.c b/source/sbase/split.c new file mode 100644 index 00000000..7033a284 --- /dev/null +++ b/source/sbase/split.c @@ -0,0 +1,111 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "util.h" + +static int base = 26, start = 'a'; + +static int +itostr(char *str, int x, int n) +{ + str[n] = '\0'; + while (n-- > 0) { + str[n] = start + (x % base); + x /= base; + } + + return x ? -1 : 0; +} + +static FILE * +nextfile(FILE *f, char *buf, int plen, int slen) +{ + static int filecount = 0; + + if (f) + fshut(f, ""); + if (itostr(buf + plen, filecount++, slen) < 0) + return NULL; + + if (!(f = fopen(buf, "w"))) + eprintf("'%s':", buf); + + return f; +} + +static void +usage(void) +{ + eprintf("usage: %s [-a num] [-b num[k|m|g] | -l num] [-d] " + "[file [prefix]]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *in = stdin, *out = NULL; + off_t size = 1000, n; + int ret = 0, ch, plen, slen = 2, always = 0; + char name[NAME_MAX + 1], *prefix = "x", *file = NULL; + + ARGBEGIN { + case 'a': + slen = estrtonum(EARGF(usage()), 0, INT_MAX); + break; + case 'b': + always = 1; + if ((size = parseoffset(EARGF(usage()))) < 0) + return 1; + if (!size) + eprintf("size needs to be positive\n"); + break; + case 'd': + base = 10; + start = '0'; + break; + case 'l': + always = 0; + size = estrtonum(EARGF(usage()), 1, MIN(LLONG_MAX, SSIZE_MAX)); + 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); + estrlcpy(name, prefix, sizeof(name)); + + if (file && strcmp(file, "-")) { + if (!(in = fopen(file, "r"))) + eprintf("fopen %s:", file); + } + + n = 0; + while ((ch = getc(in)) != EOF) { + if (!out || n >= size) { + if (!(out = nextfile(out, name, plen, slen))) + eprintf("fopen: %s:", name); + n = 0; + } + n += (always || ch == '\n'); + putc(ch, out); + } + + ret |= (in != stdin) && fshut(in, ""); + ret |= out && (out != stdout) && fshut(out, ""); + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/sponge.1 b/source/sbase/sponge.1 new file mode 100644 index 00000000..52495677 --- /dev/null +++ b/source/sbase/sponge.1 @@ -0,0 +1,19 @@ +.Dd 2015-10-08 +.Dt SPONGE 1 +.Os sbase +.Sh NAME +.Nm sponge +.Nd soak up standard input and write to a file +.Sh SYNOPSIS +.Nm +.Ar file +.Sh DESCRIPTION +.Nm +reads stdin completely, then writes the saved contents to +.Ar file . +This makes it possible to easily create pipes which read from and write to +the same file. +.Pp +If +.Ar file +is a symbolic link, it writes to its destination instead. diff --git a/source/sbase/sponge.c b/source/sbase/sponge.c new file mode 100644 index 00000000..baeac7fe --- /dev/null +++ b/source/sbase/sponge.c @@ -0,0 +1,36 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "text.h" +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s file\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp, *tmpfp; + int ret = 0; + + argv0 = argv[0], argc--, argv++; + + if (argc != 1) + usage(); + + if (!(tmpfp = tmpfile())) + eprintf("tmpfile:"); + concat(stdin, "", tmpfp, ""); + rewind(tmpfp); + + if (!(fp = fopen(argv[0], "w"))) + eprintf("fopen %s:", argv[0]); + concat(tmpfp, "", fp, argv[0]); + + ret |= fshut(fp, argv[0]) | fshut(tmpfp, ""); + + return ret; +} diff --git a/source/sbase/strings.1 b/source/sbase/strings.1 new file mode 100644 index 00000000..21ad833a --- /dev/null +++ b/source/sbase/strings.1 @@ -0,0 +1,52 @@ +.Dd 2015-10-08 +.Dt STRINGS 1 +.Os sbase +.Sh NAME +.Nm strings +.Nd print strings of printable characters in files +.Sh SYNOPSIS +.Nm +.Op Fl a +.Op Fl n Ar num +.Op Fl t Ar format +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes sequences of at least 4 printable characters in each +.Ar file +to stdout. +If no +.Ar file +is given, +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl a +Scan each +.Ar file +entirely. This is the default. +.It Fl n Ar num +Print sequences of at least +.Ar num +characters. The default is 4. +.It Fl t Ar format +Prepend each string with its byte offset, with +.Ar format +being one of +.Sy d , o , x +for decimal, octal or hexadecimal numbers. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The +.Op Fl t +output format has been changed from "%F %s" to "%8lF: %s", with +.Sy F +being one of +.Sy d , o , x . diff --git a/source/sbase/strings.c b/source/sbase/strings.c new file mode 100644 index 00000000..76b3316d --- /dev/null +++ b/source/sbase/strings.c @@ -0,0 +1,100 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "utf.h" +#include "util.h" + +static char *format = ""; + +static void +strings(FILE *fp, const char *fname, size_t len) +{ + Rune r, *rbuf; + size_t i, bread; + off_t off; + + rbuf = ereallocarray(NULL, len, sizeof(*rbuf)); + + for (off = 0, i = 0; (bread = efgetrune(&r, fp, fname)); ) { + off += bread; + if (r == Runeerror) + continue; + if (!isprintrune(r)) { + if (i > len) + putchar('\n'); + i = 0; + continue; + } + if (i < len) { + rbuf[i++] = r; + continue; + } else if (i > len) { + efputrune(&r, stdout, ""); + continue; + } + printf(format, (long)off - i); + for (i = 0; i < len; i++) + efputrune(rbuf + i, stdout, ""); + efputrune(&r, stdout, ""); + i++; + } + free(rbuf); +} + +static void +usage(void) +{ + eprintf("usage: %s [-a] [-n num] [-t format] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + size_t len = 4; + int ret = 0; + char f; + + ARGBEGIN { + case 'a': + break; + case 'n': + len = estrtonum(EARGF(usage()), 1, LLONG_MAX); + break; + case 't': + format = estrdup("%8l#: "); + f = *EARGF(usage()); + if (f == 'd' || f == 'o' || f == 'x') + format[3] = f; + else + usage(); + break; + default: + usage(); + } ARGEND + + if (!argc) { + strings(stdin, "", len); + } else { + for (; *argv; argc--, argv++) { + if (!strcmp(*argv, "-")) { + *argv = ""; + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + ret = 1; + continue; + } + strings(fp, *argv, len); + if (fp != stdin && fshut(fp, *argv)) + ret = 1; + } + } + + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/sync.1 b/source/sbase/sync.1 new file mode 100644 index 00000000..d24bb5ce --- /dev/null +++ b/source/sbase/sync.1 @@ -0,0 +1,17 @@ +.Dd 2015-10-08 +.Dt SYNC 1 +.Os sbase +.Sh NAME +.Nm sync +.Nd flush disk cache +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +.Nm +invokes +.Xr sync 2 +to flush all unwritten changes to disk. This is +usually done before shutting down, rebooting or halting. +.Sh SEE ALSO +.Xr fsync 2 , +.Xr sync 2 diff --git a/source/sbase/sync.c b/source/sbase/sync.c new file mode 100644 index 00000000..15e53f46 --- /dev/null +++ b/source/sbase/sync.c @@ -0,0 +1,22 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + argv0 = argv[0], argc--, argv++; + + if (argc) + usage(); + sync(); + + return 0; +} diff --git a/source/sbase/sysconf_l.h b/source/sbase/sysconf_l.h new file mode 100644 index 00000000..5bceb337 --- /dev/null +++ b/source/sbase/sysconf_l.h @@ -0,0 +1,354 @@ +#ifdef _SC_AIO_LISTIO_MAX + {"AIO_LISTIO_MAX", _SC_AIO_LISTIO_MAX}, +#endif +#ifdef _SC_AIO_MAX + {"AIO_MAX", _SC_AIO_MAX}, +#endif +#ifdef _SC_AIO_PRIO_DELTA_MAX + {"AIO_PRIO_DELTA_MAX", _SC_AIO_PRIO_DELTA_MAX}, +#endif +#ifdef _SC_ARG_MAX + {"ARG_MAX", _SC_ARG_MAX}, +#endif +#ifdef _SC_ATEXIT_MAX + {"ATEXIT_MAX", _SC_ATEXIT_MAX}, +#endif +#ifdef _SC_BC_BASE_MAX + {"BC_BASE_MAX", _SC_BC_BASE_MAX}, +#endif +#ifdef _SC_BC_DIM_MAX + {"BC_DIM_MAX", _SC_BC_DIM_MAX}, +#endif +#ifdef _SC_BC_SCALE_MAX + {"BC_SCALE_MAX", _SC_BC_SCALE_MAX}, +#endif +#ifdef _SC_BC_STRING_MAX + {"BC_STRING_MAX", _SC_BC_STRING_MAX}, +#endif +#ifdef _SC_CHILD_MAX + {"CHILD_MAX", _SC_CHILD_MAX}, +#endif +#ifdef _SC_COLL_WEIGHTS_MAX + {"COLL_WEIGHTS_MAX", _SC_COLL_WEIGHTS_MAX}, +#endif +#ifdef _SC_DELAYTIMER_MAX + {"DELAYTIMER_MAX", _SC_DELAYTIMER_MAX}, +#endif +#ifdef _SC_EXPR_NEST_MAX + {"EXPR_NEST_MAX", _SC_EXPR_NEST_MAX}, +#endif +#ifdef _SC_HOST_NAME_MAX + {"HOST_NAME_MAX", _SC_HOST_NAME_MAX}, +#endif +#ifdef _SC_IOV_MAX + {"IOV_MAX", _SC_IOV_MAX}, +#endif +#ifdef _SC_LINE_MAX + {"LINE_MAX", _SC_LINE_MAX}, +#endif +#ifdef _SC_LOGIN_NAME_MAX + {"LOGIN_NAME_MAX", _SC_LOGIN_NAME_MAX}, +#endif +#ifdef _SC_NGROUPS_MAX + {"NGROUPS_MAX", _SC_NGROUPS_MAX}, +#endif +#ifdef _SC_MQ_OPEN_MAX + {"MQ_OPEN_MAX", _SC_MQ_OPEN_MAX}, +#endif +#ifdef _SC_MQ_PRIO_MAX + {"MQ_PRIO_MAX", _SC_MQ_PRIO_MAX}, +#endif +#ifdef _SC_OPEN_MAX + {"OPEN_MAX", _SC_OPEN_MAX}, +#endif +#ifdef _SC_ADVISORY_INFO + {"_POSIX_ADVISORY_INFO", _SC_ADVISORY_INFO}, +#endif +#ifdef _SC_BARRIERS + {"_POSIX_BARRIERS", _SC_BARRIERS}, +#endif +#ifdef _SC_ASYNCHRONOUS_IO + {"_POSIX_ASYNCHRONOUS_IO", _SC_ASYNCHRONOUS_IO}, +#endif +#ifdef _SC_CLOCK_SELECTION + {"_POSIX_CLOCK_SELECTION", _SC_CLOCK_SELECTION}, +#endif +#ifdef _SC_CPUTIME + {"_POSIX_CPUTIME", _SC_CPUTIME}, +#endif +#ifdef _SC_FSYNC + {"_POSIX_FSYNC", _SC_FSYNC}, +#endif +#ifdef _SC_IPV6 + {"_POSIX_IPV6", _SC_IPV6}, +#endif +#ifdef _SC_JOB_CONTROL + {"_POSIX_JOB_CONTROL", _SC_JOB_CONTROL}, +#endif +#ifdef _SC_MAPPED_FILES + {"_POSIX_MAPPED_FILES", _SC_MAPPED_FILES}, +#endif +#ifdef _SC_MEMLOCK + {"_POSIX_MEMLOCK", _SC_MEMLOCK}, +#endif +#ifdef _SC_MEMLOCK_RANGE + {"_POSIX_MEMLOCK_RANGE", _SC_MEMLOCK_RANGE}, +#endif +#ifdef _SC_MEMORY_PROTECTION + {"_POSIX_MEMORY_PROTECTION", _SC_MEMORY_PROTECTION}, +#endif +#ifdef _SC_MESSAGE_PASSING + {"_POSIX_MESSAGE_PASSING", _SC_MESSAGE_PASSING}, +#endif +#ifdef _SC_MONOTONIC_CLOCK + {"_POSIX_MONOTONIC_CLOCK", _SC_MONOTONIC_CLOCK}, +#endif +#ifdef _SC_PRIORITIZED_IO + {"_POSIX_PRIORITIZED_IO", _SC_PRIORITIZED_IO}, +#endif +#ifdef _SC_PRIORITY_SCHEDULING + {"_POSIX_PRIORITY_SCHEDULING", _SC_PRIORITY_SCHEDULING}, +#endif +#ifdef _SC_RAW_SOCKETS + {"_POSIX_RAW_SOCKETS", _SC_RAW_SOCKETS}, +#endif +#ifdef _SC_READER_WRITER_LOCKS + {"_POSIX_READER_WRITER_LOCKS", _SC_READER_WRITER_LOCKS}, +#endif +#ifdef _SC_REALTIME_SIGNALS + {"_POSIX_REALTIME_SIGNALS", _SC_REALTIME_SIGNALS}, +#endif +#ifdef _SC_REGEXP + {"_POSIX_REGEXP", _SC_REGEXP}, +#endif +#ifdef _SC_SAVED_IDS + {"_POSIX_SAVED_IDS", _SC_SAVED_IDS}, +#endif +#ifdef _SC_SEMAPHORES + {"_POSIX_SEMAPHORES", _SC_SEMAPHORES}, +#endif +#ifdef _SC_SHARED_MEMORY_OBJECTS + {"_POSIX_SHARED_MEMORY_OBJECTS", _SC_SHARED_MEMORY_OBJECTS}, +#endif +#ifdef _SC_SHELL + {"_POSIX_SHELL", _SC_SHELL}, +#endif +#ifdef _SC_SPAWN + {"_POSIX_SPAWN", _SC_SPAWN}, +#endif +#ifdef _SC_SPIN_LOCKS + {"_POSIX_SPIN_LOCKS", _SC_SPIN_LOCKS}, +#endif +#ifdef _SC_SPORADIC_SERVER + {"_POSIX_SPORADIC_SERVER", _SC_SPORADIC_SERVER}, +#endif +#ifdef _SC_SS_REPL_MAX + {"_POSIX_SS_REPL_MAX", _SC_SS_REPL_MAX}, +#endif +#ifdef _SC_SYNCHRONIZED_IO + {"_POSIX_SYNCHRONIZED_IO", _SC_SYNCHRONIZED_IO}, +#endif +#ifdef _SC_THREAD_ATTR_STACKADDR + {"_POSIX_THREAD_ATTR_STACKADDR", _SC_THREAD_ATTR_STACKADDR}, +#endif +#ifdef _SC_THREAD_ATTR_STACKSIZE + {"_POSIX_THREAD_ATTR_STACKSIZE", _SC_THREAD_ATTR_STACKSIZE}, +#endif +#ifdef _SC_THREAD_CPUTIME + {"_POSIX_THREAD_CPUTIME", _SC_THREAD_CPUTIME}, +#endif +#ifdef _SC_THREAD_PRIO_INHERIT + {"_POSIX_THREAD_PRIO_INHERIT", _SC_THREAD_PRIO_INHERIT}, +#endif +#ifdef _SC_THREAD_PRIO_PROTECT + {"_POSIX_THREAD_PRIO_PROTECT", _SC_THREAD_PRIO_PROTECT}, +#endif +#ifdef _SC_THREAD_PRIORITY_SCHEDULING + {"_POSIX_THREAD_PRIORITY_SCHEDULING", _SC_THREAD_PRIORITY_SCHEDULING}, +#endif +#ifdef _SC_THREAD_PROCESS_SHARED + {"_POSIX_THREAD_PROCESS_SHARED", _SC_THREAD_PROCESS_SHARED}, +#endif +#ifdef _SC_THREAD_ROBUST_PRIO_INHERIT + {"_POSIX_THREAD_ROBUST_PRIO_INHERIT", _SC_THREAD_ROBUST_PRIO_INHERIT}, +#endif +#ifdef _SC_THREAD_ROBUST_PRIO_PROTECT + {"_POSIX_THREAD_ROBUST_PRIO_PROTECT", _SC_THREAD_ROBUST_PRIO_PROTECT}, +#endif +#ifdef _SC_THREAD_SAFE_FUNCTIONS + {"_POSIX_THREAD_SAFE_FUNCTIONS", _SC_THREAD_SAFE_FUNCTIONS}, +#endif +#ifdef _SC_THREAD_SPORADIC_SERVER + {"_POSIX_THREAD_SPORADIC_SERVER", _SC_THREAD_SPORADIC_SERVER}, +#endif +#ifdef _SC_THREADS + {"_POSIX_THREADS", _SC_THREADS}, +#endif +#ifdef _SC_TIMEOUTS + {"_POSIX_TIMEOUTS", _SC_TIMEOUTS}, +#endif +#ifdef _SC_TIMERS + {"_POSIX_TIMERS", _SC_TIMERS}, +#endif +#ifdef _SC_TRACE + {"_POSIX_TRACE", _SC_TRACE}, +#endif +#ifdef _SC_TRACE_EVENT_FILTER + {"_POSIX_TRACE_EVENT_FILTER", _SC_TRACE_EVENT_FILTER}, +#endif +#ifdef _SC_TRACE_EVENT_NAME_MAX + {"_POSIX_TRACE_EVENT_NAME_MAX", _SC_TRACE_EVENT_NAME_MAX}, +#endif +#ifdef _SC_TRACE_INHERIT + {"_POSIX_TRACE_INHERIT", _SC_TRACE_INHERIT}, +#endif +#ifdef _SC_TRACE_LOG + {"_POSIX_TRACE_LOG", _SC_TRACE_LOG}, +#endif +#ifdef _SC_TRACE_NAME_MAX + {"_POSIX_TRACE_NAME_MAX", _SC_TRACE_NAME_MAX}, +#endif +#ifdef _SC_TRACE_SYS_MAX + {"_POSIX_TRACE_SYS_MAX", _SC_TRACE_SYS_MAX}, +#endif +#ifdef _SC_TRACE_USER_EVENT_MAX + {"_POSIX_TRACE_USER_EVENT_MAX", _SC_TRACE_USER_EVENT_MAX}, +#endif +#ifdef _SC_TYPED_MEMORY_OBJECTS + {"_POSIX_TYPED_MEMORY_OBJECTS", _SC_TYPED_MEMORY_OBJECTS}, +#endif +#ifdef _SC_VERSION + {"_POSIX_VERSION", _SC_VERSION}, +#endif +#ifdef _SC_V7_ILP32_OFF32 + {"_POSIX_V7_ILP32_OFF32", _SC_V7_ILP32_OFF32}, +#endif +#ifdef _SC_V7_ILP32_OFFBIG + {"_POSIX_V7_ILP32_OFFBIG", _SC_V7_ILP32_OFFBIG}, +#endif +#ifdef _SC_V7_LP64_OFF64 + {"_POSIX_V7_LP64_OFF64", _SC_V7_LP64_OFF64}, +#endif +#ifdef _SC_V7_LPBIG_OFFBIG + {"_POSIX_V7_LPBIG_OFFBIG", _SC_V7_LPBIG_OFFBIG}, +#endif +#ifdef _SC_2_C_BIND + {"_POSIX2_C_BIND", _SC_2_C_BIND}, +#endif +#ifdef _SC_2_C_DEV + {"_POSIX2_C_DEV", _SC_2_C_DEV}, +#endif +#ifdef _SC_2_CHAR_TERM + {"_POSIX2_CHAR_TERM", _SC_2_CHAR_TERM}, +#endif +#ifdef _SC_2_FORT_DEV + {"_POSIX2_FORT_DEV", _SC_2_FORT_DEV}, +#endif +#ifdef _SC_2_FORT_RUN + {"_POSIX2_FORT_RUN", _SC_2_FORT_RUN}, +#endif +#ifdef _SC_2_LOCALEDEF + {"_POSIX2_LOCALEDEF", _SC_2_LOCALEDEF}, +#endif +#ifdef _SC_2_PBS + {"_POSIX2_PBS", _SC_2_PBS}, +#endif +#ifdef _SC_2_PBS_ACCOUNTING + {"_POSIX2_PBS_ACCOUNTING", _SC_2_PBS_ACCOUNTING}, +#endif +#ifdef _SC_2_PBS_CHECKPOINT + {"_POSIX2_PBS_CHECKPOINT", _SC_2_PBS_CHECKPOINT}, +#endif +#ifdef _SC_2_PBS_LOCATE + {"_POSIX2_PBS_LOCATE", _SC_2_PBS_LOCATE}, +#endif +#ifdef _SC_2_PBS_MESSAGE + {"_POSIX2_PBS_MESSAGE", _SC_2_PBS_MESSAGE}, +#endif +#ifdef _SC_2_PBS_TRACK + {"_POSIX2_PBS_TRACK", _SC_2_PBS_TRACK}, +#endif +#ifdef _SC_2_SW_DEV + {"_POSIX2_SW_DEV", _SC_2_SW_DEV}, +#endif +#ifdef _SC_2_UPE + {"_POSIX2_UPE", _SC_2_UPE}, +#endif +#ifdef _SC_2_VERSION + {"_POSIX2_VERSION", _SC_2_VERSION}, +#endif +#ifdef _SC_PAGE_SIZE + {"PAGE_SIZE", _SC_PAGE_SIZE}, +#endif +#ifdef _SC_PAGESIZE + {"PAGESIZE", _SC_PAGESIZE}, +#endif +#ifdef _SC_THREAD_DESTRUCTOR_ITERATIONS + {"PTHREAD_DESTRUCTOR_ITERATIONS", _SC_THREAD_DESTRUCTOR_ITERATIONS}, +#endif +#ifdef _SC_THREAD_KEYS_MAX + {"PTHREAD_KEYS_MAX", _SC_THREAD_KEYS_MAX}, +#endif +#ifdef _SC_THREAD_STACK_MIN + {"PTHREAD_STACK_MIN", _SC_THREAD_STACK_MIN}, +#endif +#ifdef _SC_THREAD_THREADS_MAX + {"PTHREAD_THREADS_MAX", _SC_THREAD_THREADS_MAX}, +#endif +#ifdef _SC_RE_DUP_MAX + {"RE_DUP_MAX", _SC_RE_DUP_MAX}, +#endif +#ifdef _SC_RTSIG_MAX + {"RTSIG_MAX", _SC_RTSIG_MAX}, +#endif +#ifdef _SC_SEM_NSEMS_MAX + {"SEM_NSEMS_MAX", _SC_SEM_NSEMS_MAX}, +#endif +#ifdef _SC_SEM_VALUE_MAX + {"SEM_VALUE_MAX", _SC_SEM_VALUE_MAX}, +#endif +#ifdef _SC_SIGQUEUE_MAX + {"SIGQUEUE_MAX", _SC_SIGQUEUE_MAX}, +#endif +#ifdef _SC_STREAM_MAX + {"STREAM_MAX", _SC_STREAM_MAX}, +#endif +#ifdef _SC_SYMLOOP_MAX + {"SYMLOOP_MAX", _SC_SYMLOOP_MAX}, +#endif +#ifdef _SC_TIMER_MAX + {"TIMER_MAX", _SC_TIMER_MAX}, +#endif +#ifdef _SC_TTY_NAME_MAX + {"TTY_NAME_MAX", _SC_TTY_NAME_MAX}, +#endif +#ifdef _SC_TZNAME_MAX + {"TZNAME_MAX", _SC_TZNAME_MAX}, +#endif +#ifdef _SC_XOPEN_CRYPT + {"_XOPEN_CRYPT", _SC_XOPEN_CRYPT}, +#endif +#ifdef _SC_XOPEN_ENH_I18N + {"_XOPEN_ENH_I18N", _SC_XOPEN_ENH_I18N}, +#endif +#ifdef _SC_XOPEN_REALTIME + {"_XOPEN_REALTIME", _SC_XOPEN_REALTIME}, +#endif +#ifdef _SC_XOPEN_REALTIME_THREADS + {"_XOPEN_REALTIME_THREADS", _SC_XOPEN_REALTIME_THREADS}, +#endif +#ifdef _SC_XOPEN_SHM + {"_XOPEN_SHM", _SC_XOPEN_SHM}, +#endif +#ifdef _SC_XOPEN_STREAMS + {"_XOPEN_STREAMS", _SC_XOPEN_STREAMS}, +#endif +#ifdef _SC_XOPEN_UNIX + {"_XOPEN_UNIX", _SC_XOPEN_UNIX}, +#endif +#ifdef _SC_XOPEN_UUCP + {"_XOPEN_UUCP", _SC_XOPEN_UUCP}, +#endif +#ifdef _SC_XOPEN_VERSION + {"_XOPEN_VERSION", _SC_XOPEN_VERSION}, +#endif diff --git a/source/sbase/tail.1 b/source/sbase/tail.1 new file mode 100644 index 00000000..433404d3 --- /dev/null +++ b/source/sbase/tail.1 @@ -0,0 +1,53 @@ +.Dd 2015-10-08 +.Dt TAIL 1 +.Os sbase +.Sh NAME +.Nm tail +.Nd display final lines of files +.Sh SYNOPSIS +.Nm +.Op Fl f +.Op Fl c Ar num | Fl n Ar num | Fl Ns Ar num +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes the last 10 lines of each +.Ar file +to stdout. If no +.Ar file +is given, +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c Ar num | Fl n Ar num | Fl Ns Ar num +Display final +.Ar num +characters | lines | +lines. If +.Ar num +begins with '+' +it is an offset from the beginning of each +.Ar file . +If +.Ar num +begins with '-' it is as if no sign was given. The default is 10 lines. +.It Fl f +If one +.Ar file +is specified, append lines to output as +.Ar file +grows. +.El +.Sh SEE ALSO +.Xr head 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The +.Op Fl Ns Ar num +syntax is an extension to that specification. diff --git a/source/sbase/tail.c b/source/sbase/tail.c new file mode 100644 index 00000000..711707f3 --- /dev/null +++ b/source/sbase/tail.c @@ -0,0 +1,169 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include +#include +#include + +#include "text.h" +#include "utf.h" +#include "util.h" + +static char mode = 'n'; + +static void +dropinit(FILE *fp, const char *str, size_t n) +{ + Rune r; + char *buf = NULL; + size_t size = 0, i = 1; + ssize_t len; + + if (mode == 'n') { + while (i < n && (len = getline(&buf, &size, fp)) > 0) + if (len > 0 && buf[len - 1] == '\n') + i++; + } else { + while (i < n && efgetrune(&r, fp, str)) + i++; + } + free(buf); + concat(fp, str, stdout, ""); +} + +static void +taketail(FILE *fp, const char *str, size_t n) +{ + Rune *r = NULL; + struct line *ring = NULL; + size_t i, j, *size = NULL; + ssize_t len; + int seenln = 0; + + if (!n) + return; + + if (mode == 'n') { + ring = ecalloc(n, sizeof(*ring)); + size = ecalloc(n, sizeof(*size)); + + for (i = j = 0; (len = getline(&ring[i].data, + &size[i], fp)) > 0; seenln = 1) { + ring[i].len = len; + i = j = (i + 1) % n; + } + } else { + r = ecalloc(n, sizeof(*r)); + + for (i = j = 0; efgetrune(&r[i], fp, str); ) + i = j = (i + 1) % n; + } + if (ferror(fp)) + eprintf("%s: read error:", str); + + do { + if (seenln && ring && ring[j].data) { + fwrite(ring[j].data, 1, ring[j].len, stdout); + free(ring[j].data); + } else if (r) { + efputrune(&r[j], stdout, ""); + } + } while ((j = (j + 1) % n) != i); + + free(ring); + free(size); + free(r); +} + +static void +usage(void) +{ + eprintf("usage: %s [-f] [-c num | -n num | -num] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct stat st1, st2; + FILE *fp; + size_t tmpsize, n = 10; + int fflag = 0, ret = 0, newline = 0, many = 0; + char *numstr, *tmp; + void (*tail)(FILE *, const char *, size_t) = taketail; + + ARGBEGIN { + case 'f': + fflag = 1; + break; + case 'c': + case 'n': + mode = ARGC(); + numstr = EARGF(usage()); + n = MIN(llabs(estrtonum(numstr, LLONG_MIN + 1, + MIN(LLONG_MAX, SIZE_MAX))), SIZE_MAX); + if (strchr(numstr, '+')) + tail = dropinit; + break; + ARGNUM: + n = ARGNUMF(); + break; + default: + usage(); + } ARGEND + + if (!argc) + tail(stdin, "", n); + else { + if ((many = argc > 1) && fflag) + usage(); + for (newline = 0; *argv; argc--, argv++) { + if (!strcmp(*argv, "-")) { + *argv = ""; + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + ret = 1; + continue; + } + if (many) + printf("%s==> %s <==\n", newline ? "\n" : "", *argv); + if (stat(*argv, &st1) < 0) + eprintf("stat %s:", *argv); + if (!(S_ISFIFO(st1.st_mode) || S_ISREG(st1.st_mode))) + fflag = 0; + newline = 1; + tail(fp, *argv, n); + + if (!fflag) { + if (fp != stdin && fshut(fp, *argv)) + ret = 1; + continue; + } + for (tmp = NULL, tmpsize = 0;;) { + while (getline(&tmp, &tmpsize, fp) > 0) { + fputs(tmp, stdout); + fflush(stdout); + } + if (ferror(fp)) + eprintf("readline %s:", *argv); + clearerr(fp); + /* ignore error in case file was removed, we continue + * tracking the existing open file descriptor */ + if (!stat(*argv, &st2)) { + if (st2.st_size < st1.st_size) { + fprintf(stderr, "%s: file truncated\n", *argv); + rewind(fp); + } + st1 = st2; + } + sleep(1); + } + } + } + + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/tar.1 b/source/sbase/tar.1 new file mode 100644 index 00000000..7cc34b41 --- /dev/null +++ b/source/sbase/tar.1 @@ -0,0 +1,69 @@ +.Dd 2015-10-08 +.Dt TAR 1 +.Os sbase +.Sh NAME +.Nm tar +.Nd create, list or extract a tape archive +.Sh SYNOPSIS +.Nm +.Op Fl C Ar dir +.Op Fl J | Fl Z | Fl a | Fl j | Fl z +.Fl x Op Fl m | Fl t +.Op Fl f Ar file +.Op Ar file ... +.Nm +.Op Fl C Ar dir +.Op Fl J | Fl Z | Fl a | Fl j | Fl z +.Op Fl h +.Fl c Ar path ... +.Op Fl f Ar file +.Sh DESCRIPTION +.Nm +is the standard file archiver. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c Ar path ... +Create archive from +.Ar path . +.It Fl C Ar dir +Change directory to +.Ar dir +before beginning. +.It Fl f Ar file +Set +.Ar file +as input | output archive instead of stdin | stdout. If '-', +stdin | stdout is used. +.It Fl m +Do not preserve modification time. +.It Fl t +List all files in the archive. +.It Fl x +Extract archive. +.It Fl h +Always dereference symbolic links while recursively traversing directories. +.It Fl J | Fl Z | Fl a | Fl j | Fl z +Use xz | compress | lzma | bzip2 | gzip compression or decompression. These +utilities must be installed separately. +Using these flags is discouraged in favour of the flexibility +and clarity of pipes: +.Bd -literal -offset indent +$ bzip2 -cd archive.tar.bz2 | tar -x +$ gzip -cd archive.tar.gz | tar -x +.Ed +.Bd -literal -offset indent +$ tar -c file ... | bzip2 > archive.tar.bz2 +$ tar -c file ... | gzip2 > archive.tar.gz +.Ed +.El +.Sh SEE ALSO +.Xr ar 1 , +.Xr bzip2 1 , +.Xr gzip 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the UStar (Uniform Standard Tape ARchive) +format defined in the +.St -p1003.1-88 +specification. diff --git a/source/sbase/tar.c b/source/sbase/tar.c new file mode 100644 index 00000000..71719b01 --- /dev/null +++ b/source/sbase/tar.c @@ -0,0 +1,593 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fs.h" +#include "util.h" + +#define BLKSIZ 512 + +enum Type { + REG = '0', + AREG = '\0', + HARDLINK = '1', + SYMLINK = '2', + CHARDEV = '3', + BLOCKDEV = '4', + DIRECTORY = '5', + FIFO = '6', + RESERVED = '7' +}; + +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 linkname[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char major[8]; + char minor[8]; + char prefix[155]; +}; + +static struct dirtime { + char *name; + time_t mtime; +} *dirtimes; + +static size_t dirtimeslen; + +static int tarfd; +static ino_t tarinode; +static dev_t tardev; + +static int mflag, vflag; +static int filtermode; +static const char *filtertool; + +static const char *filtertools[] = { + ['J'] = "xz", + ['Z'] = "compress", + ['a'] = "lzma", + ['j'] = "bzip2", + ['z'] = "gzip", +}; + +static void +pushdirtime(char *name, time_t mtime) +{ + dirtimes = reallocarray(dirtimes, dirtimeslen + 1, sizeof(*dirtimes)); + dirtimes[dirtimeslen].name = strdup(name); + dirtimes[dirtimeslen].mtime = mtime; + dirtimeslen++; +} + +static struct dirtime * +popdirtime(void) +{ + if (dirtimeslen) { + dirtimeslen--; + return &dirtimes[dirtimeslen]; + } + return NULL; +} + +static int +comp(int fd, const char *tool, const char *flags) +{ + int fds[2]; + + if (pipe(fds) < 0) + eprintf("pipe:"); + + switch (fork()) { + case -1: + eprintf("fork:"); + case 0: + dup2(fd, 1); + dup2(fds[0], 0); + close(fds[0]); + close(fds[1]); + + execlp(tool, tool, flags, NULL); + weprintf("execlp %s:", tool); + _exit(1); + } + close(fds[0]); + return fds[1]; +} + +static int +decomp(int fd, const char *tool, const char *flags) +{ + int fds[2]; + + if (pipe(fds) < 0) + eprintf("pipe:"); + + switch (fork()) { + case -1: + eprintf("fork:"); + case 0: + dup2(fd, 0); + dup2(fds[1], 1); + close(fds[0]); + close(fds[1]); + + execlp(tool, tool, flags, NULL); + weprintf("execlp %s:", tool); + _exit(1); + } + close(fds[1]); + return fds[0]; +} + +static ssize_t +eread(int fd, void *buf, size_t n) +{ + ssize_t r; + +again: + r = read(fd, buf, n); + if (r < 0) { + if (errno == EINTR) + goto again; + eprintf("read:"); + } + return r; +} + +static ssize_t +ewrite(int fd, const void *buf, size_t n) +{ + ssize_t r; + + if ((r = write(fd, buf, n)) != n) + eprintf("write:"); + return r; +} + +static void +putoctal(char *dst, unsigned num, int size) +{ + if (snprintf(dst, size, "%.*o", size - 1, num) >= size) + eprintf("snprintf: input number too large\n"); +} + +static int +archive(const char *path) +{ + char b[BLKSIZ]; + struct group *gr; + struct header *h; + struct passwd *pw; + struct stat st; + size_t chksum, i; + ssize_t l, r; + int fd = -1; + + if (lstat(path, &st) < 0) { + weprintf("lstat %s:", path); + return 0; + } else if (st.st_ino == tarinode && st.st_dev == tardev) { + weprintf("ignoring %s\n", path); + return 0; + } + + pw = getpwuid(st.st_uid); + gr = getgrgid(st.st_gid); + + h = (struct header *)b; + memset(b, 0, sizeof(b)); + estrlcpy(h->name, path, sizeof(h->name)); + 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)); + estrlcpy(h->uname, pw ? pw->pw_name : "", sizeof(h->uname)); + estrlcpy(h->gname, gr ? gr->gr_name : "", sizeof(h->gname)); + + if (S_ISREG(st.st_mode)) { + h->type = REG; + putoctal(h->size, (unsigned)st.st_size, sizeof(h->size)); + fd = open(path, O_RDONLY); + if (fd < 0) + eprintf("open %s:", path); + } else if (S_ISDIR(st.st_mode)) { + h->type = DIRECTORY; + } else if (S_ISLNK(st.st_mode)) { + h->type = SYMLINK; + if ((r = readlink(path, h->linkname, sizeof(h->linkname) - 1)) < 0) + eprintf("readlink %s:", path); + h->linkname[r] = '\0'; + } else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) { + h->type = S_ISCHR(st.st_mode) ? CHARDEV : BLOCKDEV; + putoctal(h->major, (unsigned)major(st.st_dev), sizeof(h->major)); + putoctal(h->minor, (unsigned)minor(st.st_dev), sizeof(h->minor)); + } else if (S_ISFIFO(st.st_mode)) { + h->type = FIFO; + } + + memset(h->chksum, ' ', sizeof(h->chksum)); + for (i = 0, chksum = 0; i < sizeof(*h); i++) + chksum += (unsigned char)b[i]; + putoctal(h->chksum, chksum, sizeof(h->chksum)); + ewrite(tarfd, b, BLKSIZ); + + if (fd != -1) { + while ((l = eread(fd, b, BLKSIZ)) > 0) { + if (l < BLKSIZ) + memset(b + l, 0, BLKSIZ - l); + ewrite(tarfd, b, BLKSIZ); + } + close(fd); + } + + return 0; +} + +static int +unarchive(char *fname, ssize_t l, char b[BLKSIZ]) +{ + char lname[101], *tmp, *p; + long mode, major, minor, type, mtime, uid, gid; + struct header *h = (struct header *)b; + int fd = -1; + struct timespec times[2]; + + if (!mflag && ((mtime = strtol(h->mtime, &p, 8)) < 0 || *p != '\0')) + eprintf("strtol %s: invalid number\n", h->mtime); + if (remove(fname) < 0 && errno != ENOENT) + weprintf("remove %s:", fname); + + tmp = estrdup(fname); + mkdirp(dirname(tmp)); + free(tmp); + + switch (h->type) { + case REG: + case AREG: + case RESERVED: + if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0') + eprintf("strtol %s: invalid number\n", h->mode); + fd = open(fname, O_WRONLY | O_TRUNC | O_CREAT, 0600); + if (fd < 0) + eprintf("open %s:", fname); + break; + case HARDLINK: + case SYMLINK: + snprintf(lname, sizeof(lname), "%.*s", (int)sizeof(h->linkname), + h->linkname); + if (((h->type == HARDLINK) ? link : symlink)(lname, fname) < 0) + eprintf("%s %s -> %s:", + (h->type == HARDLINK) ? "link" : "symlink", + fname, lname); + break; + case DIRECTORY: + if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0') + eprintf("strtol %s: invalid number\n", h->mode); + if (mkdir(fname, (mode_t)mode) < 0 && errno != EEXIST) + eprintf("mkdir %s:", fname); + pushdirtime(fname, mtime); + break; + case CHARDEV: + case BLOCKDEV: + if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0') + eprintf("strtol %s: invalid number\n", h->mode); + if ((major = strtol(h->major, &p, 8)) < 0 || *p != '\0') + eprintf("strtol %s: invalid number\n", h->major); + if ((minor = strtol(h->minor, &p, 8)) < 0 || *p != '\0') + eprintf("strtol %s: invalid number\n", h->minor); + type = (h->type == CHARDEV) ? S_IFCHR : S_IFBLK; + if (mknod(fname, type | mode, makedev(major, minor)) < 0) + eprintf("mknod %s:", fname); + break; + case FIFO: + if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0') + eprintf("strtol %s: invalid number\n", h->mode); + if (mknod(fname, S_IFIFO | mode, 0) < 0) + eprintf("mknod %s:", fname); + break; + default: + eprintf("unsupported tar-filetype %c\n", h->type); + } + + if ((uid = strtol(h->uid, &p, 8)) < 0 || *p != '\0') + eprintf("strtol %s: invalid number\n", h->uid); + if ((gid = strtol(h->gid, &p, 8)) < 0 || *p != '\0') + eprintf("strtol %s: invalid number\n", h->gid); + + if (fd != -1) { + for (; l > 0; l -= BLKSIZ) + if (eread(tarfd, b, BLKSIZ) > 0) + ewrite(fd, b, MIN(l, BLKSIZ)); + close(fd); + } + + if (h->type == HARDLINK) + return 0; + + times[0].tv_sec = times[1].tv_sec = mtime; + times[0].tv_nsec = times[1].tv_nsec = 0; + if (!mflag && utimensat(AT_FDCWD, fname, times, AT_SYMLINK_NOFOLLOW) < 0) + weprintf("utimensat %s:\n", fname); + if (h->type == SYMLINK) { + if (!getuid() && lchown(fname, uid, gid)) + weprintf("lchown %s:\n", fname); + } else { + if (!getuid() && chown(fname, uid, gid)) + weprintf("chown %s:\n", fname); + if (chmod(fname, mode) < 0) + eprintf("fchmod %s:\n", fname); + } + + return 0; +} + +static void +skipblk(ssize_t l) +{ + char b[BLKSIZ]; + + for (; l > 0; l -= BLKSIZ) + if (!eread(tarfd, b, BLKSIZ)) + break; +} + +static int +print(char *fname, ssize_t l, char b[BLKSIZ]) +{ + puts(fname); + skipblk(l); + return 0; +} + +static void +c(const char *path, struct stat *st, void *data, struct recursor *r) +{ + archive(path); + if (vflag) + puts(path); + + if (st && S_ISDIR(st->st_mode)) + recurse(path, NULL, r); +} + +static void +sanitize(struct header *h) +{ + size_t i, j; + struct { + char *f; + size_t l; + } fields[] = { + { h->mode, sizeof(h->mode) }, + { h->uid, sizeof(h->uid) }, + { h->gid, sizeof(h->gid) }, + { h->size, sizeof(h->size) }, + { h->mtime, sizeof(h->mtime) }, + { h->chksum, sizeof(h->chksum) }, + { h->major, sizeof(h->major) }, + { h->minor, sizeof(h->minor) } + }; + + /* Numeric fields can be terminated with spaces instead of + * NULs as per the ustar specification. Patch all of them to + * use NULs so we can perform string operations on them. */ + for (i = 0; i < LEN(fields); i++) + for (j = 0; j < fields[i].l; j++) + if (fields[i].f[j] == ' ') + fields[i].f[j] = '\0'; +} + +static void +chktar(struct header *h) +{ + char tmp[8], *err; + char *p = (char *)h; + long s1, s2, i; + + if (h->prefix[0] == '\0' && h->name[0] == '\0') + goto bad; + if (h->magic[0] && strncmp("ustar", h->magic, 5)) + goto bad; + memcpy(tmp, h->chksum, sizeof(tmp)); + for (i = 0; i < sizeof(tmp); i++) + if (tmp[i] == ' ') + tmp[i] = '\0'; + s1 = strtol(tmp, &err, 8); + if (s1 < 0 || *err != '\0') + goto bad; + memset(h->chksum, ' ', sizeof(h->chksum)); + for (i = 0, s2 = 0; i < sizeof(*h); i++) + s2 += (unsigned char)p[i]; + if (s1 != s2) + goto bad; + memcpy(h->chksum, tmp, sizeof(h->chksum)); + return; +bad: + eprintf("malformed tar archive\n"); +} + +static void +xt(int argc, char *argv[], int mode) +{ + char b[BLKSIZ], fname[256 + 1], *p; + struct timespec times[2]; + struct header *h = (struct header *)b; + struct dirtime *dirtime; + long size; + int i, n; + int (*fn)(char *, ssize_t, char[BLKSIZ]) = (mode == 'x') ? unarchive : print; + + while (eread(tarfd, b, BLKSIZ) > 0 && h->name[0]) { + chktar(h); + sanitize(h), n = 0; + + /* small dance around non-null terminated fields */ + if (h->prefix[0]) + n = snprintf(fname, sizeof(fname), "%.*s/", + (int)sizeof(h->prefix), h->prefix); + snprintf(fname + n, sizeof(fname) - n, "%.*s", + (int)sizeof(h->name), h->name); + + if ((size = strtol(h->size, &p, 8)) < 0 || *p != '\0') + eprintf("strtol %s: invalid number\n", h->size); + + if (argc) { + /* only extract the given files */ + for (i = 0; i < argc; i++) + if (!strcmp(argv[i], fname)) + break; + if (i == argc) { + skipblk(size); + continue; + } + } + + /* ignore global pax header craziness */ + if (h->type == 'g' || h->type == 'x') { + skipblk(size); + continue; + } + + fn(fname, size, b); + if (vflag && mode != 't') + puts(fname); + } + + if (mode == 'x' && !mflag) { + while ((dirtime = popdirtime())) { + times[0].tv_sec = times[1].tv_sec = dirtime->mtime; + times[0].tv_nsec = times[1].tv_nsec = 0; + if (utimensat(AT_FDCWD, dirtime->name, times, 0) < 0) + eprintf("utimensat %s:", fname); + free(dirtime->name); + } + free(dirtimes); + dirtimes = NULL; + } +} + +static void +usage(void) +{ + eprintf("usage: %s [-C dir] [-J | -Z | -a | -j | -z] -x [-m | -t] " + "[-f file] [file ...]\n" + " %s [-C dir] [-J | -Z | -a | -j | -z] [-h] -c path ... " + "[-f file]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + struct recursor r = { .fn = c, .hist = NULL, .depth = 0, .maxdepth = 0, + .follow = 'P', .flags = DIRFIRST }; + struct stat st; + char *file = NULL, *dir = ".", mode = '\0'; + int fd; + + ARGBEGIN { + case 'x': + case 'c': + case 't': + mode = ARGC(); + break; + case 'C': + dir = EARGF(usage()); + break; + case 'f': + file = EARGF(usage()); + break; + case 'm': + mflag = 1; + break; + case 'J': + case 'Z': + case 'a': + case 'j': + case 'z': + filtermode = ARGC(); + filtertool = filtertools[filtermode]; + break; + case 'h': + r.follow = 'L'; + break; + case 'v': + vflag = 1; + break; + default: + usage(); + } ARGEND + + if (!mode) + usage(); + if (mode == 'c') + if (!argc) + usage(); + + switch (mode) { + case 'c': + tarfd = 1; + if (file && *file != '-') { + tarfd = open(file, O_WRONLY | O_TRUNC | O_CREAT, 0644); + if (tarfd < 0) + eprintf("open %s:", file); + if (lstat(file, &st) < 0) + eprintf("lstat %s:", file); + tarinode = st.st_ino; + tardev = st.st_dev; + } + + if (filtertool) + tarfd = comp(tarfd, filtertool, "-cf"); + + if (chdir(dir) < 0) + eprintf("chdir %s:", dir); + for (; *argv; argc--, argv++) + recurse(*argv, NULL, &r); + break; + case 't': + case 'x': + tarfd = 0; + if (file && *file != '-') { + tarfd = open(file, O_RDONLY); + if (tarfd < 0) + eprintf("open %s:", file); + } + + if (filtertool) { + fd = tarfd; + tarfd = decomp(tarfd, filtertool, "-cd"); + close(fd); + } + + if (chdir(dir) < 0) + eprintf("chdir %s:", dir); + xt(argc, argv, mode); + break; + } + + return recurse_status; +} diff --git a/source/sbase/tee.1 b/source/sbase/tee.1 new file mode 100644 index 00000000..6fb4e5a4 --- /dev/null +++ b/source/sbase/tee.1 @@ -0,0 +1,30 @@ +.Dd 2015-10-08 +.Dt TEE 1 +.Os sbase +.Sh NAME +.Nm tee +.Nd multiply stdin +.Sh SYNOPSIS +.Nm +.Op Fl ai +.Op Ar file ... +.Sh DESCRIPTION +.Nm +reads from stdin and writes to stdout and each +.Ar file . +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl a +Append to each +.Ar file +instead of overwriting it. +.It Fl i +Ignore SIGINT, see +.Xr signal 7 . +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/tee.c b/source/sbase/tee.c new file mode 100644 index 00000000..399824cb --- /dev/null +++ b/source/sbase/tee.c @@ -0,0 +1,53 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-ai] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE **fps = NULL; + size_t i, n, nfps; + int ret = 0, aflag = 0, iflag = 0; + char buf[BUFSIZ]; + + ARGBEGIN { + case 'a': + aflag = 1; + break; + case 'i': + iflag = 1; + break; + default: + usage(); + } ARGEND + + if (iflag && signal(SIGINT, SIG_IGN) == SIG_ERR) + eprintf("signal:"); + nfps = argc + 1; + fps = ecalloc(nfps, sizeof(*fps)); + + for (i = 0; i < argc; i++) + if (!(fps[i] = fopen(argv[i], aflag ? "a" : "w"))) + eprintf("fopen %s:", argv[i]); + fps[i] = stdout; + + while ((n = fread(buf, 1, sizeof(buf), stdin))) { + for (i = 0; i < nfps; i++) { + if (fwrite(buf, 1, n, fps[i]) == n) + continue; + eprintf("fwrite %s:", (i != argc) ? argv[i] : ""); + } + } + + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/test.1 b/source/sbase/test.1 new file mode 100644 index 00000000..539016ad --- /dev/null +++ b/source/sbase/test.1 @@ -0,0 +1,124 @@ +.Dd 2015-10-08 +.Dt TEST 1 +.Os sbase +.Sh NAME +.Nm test +.Nd evaluate expression +.Sh SYNOPSIS +.Nm +.Ar expression +.Sh DESCRIPTION +.Nm +returns the status of the +.Ar expression . +.Sh OPTIONS +.Bl -tag -width Ds +.It ! Ar expression +invert +.Ar expression . +.It ( Fl e | Fl s ) Ar file +.Ar file +exists and has (any size +.Op Fl e +| non-zero size +.Op Fl s ) . +.It ( Fl f | Fl d | Fl p | Fl hL | Fl S | Fl b | Fl c ) Ar file +.Ar file +exists and is a +(regular file +.Op Fl f +| directory +.Op Fl d +| named pipe +.Op Fl p +| symbolic link +.Op Fl h | Fl L +| socket +.Op Fl S +| block special +.Op Fl b +| character special +.Op Fl c ) . +.It ( Fl k | Fl g | Fl u | Fl r | Fl w | Fl x ) Ar file +.Ar file +exists and has +.Xr ( sticky 1 +.Op Fl k +| +.Xr setgid 2 +.Op Fl g +| +.Xr setuid 4 +.Op Fl u +| +.Xr read 4 +.Op Fl r +| +.Xr write 2 +.Op Fl w +| +.Xr execute 1 +.Op Fl x ) +permissions. +.It Fl t Ar fd +.Ar fd +as a file descriptor is associated with a terminal. +.It Ar string +True if +.Ar string +is not the null string. +.It ( Fl z | Fl n ) Ar string +True if +.Ar string +has (zero +.Op Fl z +| non-zero +.Op Fl n ) +length. +.It Ar s1 Sy ( = | != ) Ar s2 +True if strings +.Ar s1 +and +.Ar s2 +are +(identical +.Oo Sy = Oc +| different +.Oo Sy != Oc ) . +.It Ar n1 ( Fl eq | Fl ne | Fl gt | Fl ge | Fl le | Fl lt ) Ar n2 +True if integers +.Ar n1 +and +.Ar n2 +are (= +.Op Fl eq +| != +.Op Fl ne +| > +.Op Fl gt +| >= +.Op Fl ge +| <= +.Op Fl le +| < +.Op Fl lt ) . +.El +.Sh EXIT STATUS +.Bl -tag -width Ds +.It 0 +.Ar expression +is true. +.It 1 +.Ar expression +is false. +.It > 1 +An error occurred. +.El +.Sh SEE ALSO +.Xr expr 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/test.c b/source/sbase/test.c new file mode 100644 index 00000000..3fe12c36 --- /dev/null +++ b/source/sbase/test.c @@ -0,0 +1,243 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include + +#include "util.h" + +static int +intcmp(char *a, char *b) +{ + char *s; + int asign = *a == '-' ? -1 : 1; + int bsign = *b == '-' ? -1 : 1; + + if (*a == '-' || *a == '+') a += 1; + if (*b == '-' || *b == '+') b += 1; + + if (!*a || !*b) + goto noint; + for (s = a; *s; s++) + if (!isdigit(*s)) + goto noint; + for (s = b; *s; s++) + if (!isdigit(*s)) + goto noint; + + while (*a == '0') a++; + while (*b == '0') b++; + asign *= !!*a; + bsign *= !!*b; + + if (asign != bsign) + return asign < bsign ? -1 : 1; + else if (strlen(a) != strlen(b)) + return asign * (strlen(a) < strlen(b) ? -1 : 1); + else + return asign * strcmp(a, b); + +noint: + enprintf(2, "expected integer operands\n"); + + return 0; /* not reached */ +} + +static int +mtimecmp(struct stat *buf1, struct stat *buf2) +{ + if (buf1->st_mtime < buf2->st_mtime) return -1; + if (buf1->st_mtime > buf2->st_mtime) return +1; +#ifdef st_mtime + if (buf1->st_mtim.tv_nsec < buf2->st_mtim.tv_nsec) return -1; + if (buf1->st_mtim.tv_nsec > buf2->st_mtim.tv_nsec) return +1; +#endif + return 0; +} + +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_k(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISVTX & 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 *s; } +static int unary_z(char *s) { return !*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 = enstrtonum(2, s, 0, INT_MAX); return isatty(fd); } + +static int binary_se(char *s1, char *s2) { return !strcmp(s1, s2); } +static int binary_sn(char *s1, char *s2) { return strcmp(s1, s2); } + +static int binary_eq(char *s1, char *s2) { return intcmp(s1, s2) == 0; } +static int binary_ne(char *s1, char *s2) { return intcmp(s1, s2) != 0; } +static int binary_gt(char *s1, char *s2) { return intcmp(s1, s2) > 0; } +static int binary_ge(char *s1, char *s2) { return intcmp(s1, s2) >= 0; } +static int binary_lt(char *s1, char *s2) { return intcmp(s1, s2) < 0; } +static int binary_le(char *s1, char *s2) { return intcmp(s1, s2) <= 0; } + +static int +binary_ef(char *s1, char *s2) +{ + struct stat buf1, buf2; + if (stat(s1, &buf1) || stat(s2, &buf2)) return 0; + return buf1.st_dev == buf2.st_dev && buf1.st_ino == buf2.st_ino; +} + +static int +binary_ot(char *s1, char *s2) +{ + struct stat buf1, buf2; + if (stat(s1, &buf1) || stat(s2, &buf2)) return 0; + return mtimecmp(&buf1, &buf2) < 0; +} + +static int +binary_nt(char *s1, char *s2) +{ + struct stat buf1, buf2; + if (stat(s1, &buf1) || stat(s2, &buf2)) return 0; + return mtimecmp(&buf1, &buf2) > 0; +} + +struct test { + char *name; + int (*func)(); +}; + +static struct test unary[] = { + { "-b", unary_b }, + { "-c", unary_c }, + { "-d", unary_d }, + { "-e", unary_e }, + { "-f", unary_f }, + { "-g", unary_g }, + { "-h", unary_h }, + { "-k", unary_k }, + { "-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 struct test binary[] = { + { "=" , binary_se }, + { "!=" , binary_sn }, + { "-eq", binary_eq }, + { "-ne", binary_ne }, + { "-gt", binary_gt }, + { "-ge", binary_ge }, + { "-lt", binary_lt }, + { "-le", binary_le }, + { "-ef", binary_ef }, + { "-ot", binary_ot }, + { "-nt", binary_nt }, + + { NULL, NULL }, +}; + +static struct test * +find_test(struct test *tests, char *name) +{ + struct test *t; + + for (t = tests; t->name; t++) + if (!strcmp(t->name, name)) + return t; + + return NULL; +} + +static int +noarg(char *argv[]) +{ + return 0; +} + +static int +onearg(char *argv[]) +{ + return unary_n(argv[0]); +} + +static int +twoarg(char *argv[]) +{ + struct test *t; + + if (!strcmp(argv[0], "!")) + return !onearg(argv + 1); + + if ((t = find_test(unary, *argv))) + return t->func(argv[1]); + + enprintf(2, "bad unary test %s\n", argv[0]); + + return 0; /* not reached */ +} + +static int +threearg(char *argv[]) +{ + struct test *t = find_test(binary, argv[1]); + + if (t) + return t->func(argv[0], argv[2]); + + if (!strcmp(argv[0], "!")) + return !twoarg(argv + 1); + + enprintf(2, "bad binary test %s\n", argv[1]); + + return 0; /* not reached */ +} + +static int +fourarg(char *argv[]) +{ + if (!strcmp(argv[0], "!")) + return !threearg(argv + 1); + + enprintf(2, "too many arguments\n"); + + return 0; /* not reached */ +} + +int +main(int argc, char *argv[]) +{ + int (*narg[])(char *[]) = { noarg, onearg, twoarg, threearg, fourarg }; + size_t len; + + argv0 = argv[0], argc--, argv++; + + len = strlen(argv0); + if (len && argv0[--len] == '[' && (!len || argv0[--len] == '/') && strcmp(argv[--argc], "]")) + enprintf(2, "no matching ]\n"); + + if (argc > 4) + enprintf(2, "too many arguments\n"); + + return !narg[argc](argv); +} diff --git a/source/sbase/text.h b/source/sbase/text.h new file mode 100644 index 00000000..bceda525 --- /dev/null +++ b/source/sbase/text.h @@ -0,0 +1,17 @@ +/* See LICENSE file for copyright and license details. */ + +struct line { + char *data; + size_t len; +}; + +struct linebuf { + struct line *lines; + size_t nlines; + size_t capacity; +}; +#define EMPTY_LINEBUF {NULL, 0, 0,} +void getlines(FILE *, struct linebuf *); + +void concat(FILE *, const char *, FILE *, const char *); +int linecmp(struct line *, struct line *); diff --git a/source/sbase/tftp.1 b/source/sbase/tftp.1 new file mode 100644 index 00000000..424519e3 --- /dev/null +++ b/source/sbase/tftp.1 @@ -0,0 +1,29 @@ +.Dd 2015-10-08 +.Dt TFTP 1 +.Os sbase +.Sh NAME +.Nm tftp +.Nd trivial file transfer protocol client +.Sh SYNOPSIS +.Nm +.Fl h Ar host +.Op Fl p Ar port +.Op Fl x | c +.Ar file +.Sh DESCRIPTION +.Nm +is a client that implements the trivial file transfer protocol over +either IPv4 or IPv6 as specified in RFC 1350. It can be used to transfer +files to and from remote machines. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl h Ar host +Set the remote hostname. +.It Fl p Ar port +Set the remote port. It defaults to port 69. +.It Fl x +Extract a file from the server. This is the default +if no flags are specified. Output goes to stdout. +.It Fl c +Create a file on the server. Input comes from stdin. +.El diff --git a/source/sbase/tftp.c b/source/sbase/tftp.c new file mode 100644 index 00000000..0a099ff2 --- /dev/null +++ b/source/sbase/tftp.c @@ -0,0 +1,309 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "util.h" + +#define BLKSIZE 512 +#define HDRSIZE 4 +#define PKTSIZE (BLKSIZE + HDRSIZE) + +#define TIMEOUT_SEC 5 +/* transfer will time out after NRETRIES * TIMEOUT_SEC */ +#define NRETRIES 5 + +#define RRQ 1 +#define WWQ 2 +#define DATA 3 +#define ACK 4 +#define ERR 5 + +static char *errtext[] = { + "Undefined", + "File not found", + "Access violation", + "Disk full or allocation exceeded", + "Illegal TFTP operation", + "Unknown transfer ID", + "File already exists", + "No such user" +}; + +static struct sockaddr_storage to; +static socklen_t tolen; +static int timeout; +static int state; +static int s; + +static int +packreq(unsigned char *buf, int op, char *path, char *mode) +{ + unsigned char *p = buf; + + *p++ = op >> 8; + *p++ = op & 0xff; + if (strlen(path) + 1 > 256) + eprintf("filename too long\n"); + memcpy(p, path, strlen(path) + 1); + p += strlen(path) + 1; + memcpy(p, mode, strlen(mode) + 1); + p += strlen(mode) + 1; + return p - buf; +} + +static int +packack(unsigned char *buf, int blkno) +{ + buf[0] = ACK >> 8; + buf[1] = ACK & 0xff; + buf[2] = blkno >> 8; + buf[3] = blkno & 0xff; + return 4; +} + +static int +packdata(unsigned char *buf, int blkno) +{ + buf[0] = DATA >> 8; + buf[1] = DATA & 0xff; + buf[2] = blkno >> 8; + buf[3] = blkno & 0xff; + return 4; +} + +static int +unpackop(unsigned char *buf) +{ + return (buf[0] << 8) | (buf[1] & 0xff); +} + +static int +unpackblkno(unsigned char *buf) +{ + return (buf[2] << 8) | (buf[3] & 0xff); +} + +static int +unpackerrc(unsigned char *buf) +{ + int errc; + + errc = (buf[2] << 8) | (buf[3] & 0xff); + if (errc < 0 || errc >= LEN(errtext)) + eprintf("bad error code: %d\n", errc); + return errc; +} + +static int +writepkt(unsigned char *buf, int len) +{ + int n; + + n = sendto(s, buf, len, 0, (struct sockaddr *)&to, + tolen); + if (n < 0) + if (errno != EINTR) + eprintf("sendto:"); + return n; +} + +static int +readpkt(unsigned char *buf, int len) +{ + int n; + + n = recvfrom(s, buf, len, 0, (struct sockaddr *)&to, + &tolen); + if (n < 0) { + if (errno != EINTR && errno != EWOULDBLOCK) + eprintf("recvfrom:"); + timeout++; + if (timeout == NRETRIES) + eprintf("transfer timed out\n"); + } else { + timeout = 0; + } + return n; +} + +static void +getfile(char *file) +{ + unsigned char buf[PKTSIZE]; + int n, op, blkno, nextblkno = 1, done = 0; + + state = RRQ; + for (;;) { + switch (state) { + case RRQ: + n = packreq(buf, RRQ, file, "octet"); + writepkt(buf, n); + n = readpkt(buf, sizeof(buf)); + if (n > 0) { + op = unpackop(buf); + if (op != DATA && op != ERR) + eprintf("bad opcode: %d\n", op); + state = op; + } + break; + case DATA: + n -= HDRSIZE; + if (n < 0) + eprintf("truncated packet\n"); + blkno = unpackblkno(buf); + if (blkno == nextblkno) { + nextblkno++; + write(1, &buf[HDRSIZE], n); + } + if (n < BLKSIZE) + done = 1; + state = ACK; + break; + case ACK: + n = packack(buf, blkno); + writepkt(buf, n); + if (done) + return; + n = readpkt(buf, sizeof(buf)); + if (n > 0) { + op = unpackop(buf); + if (op != DATA && op != ERR) + eprintf("bad opcode: %d\n", op); + state = op; + } + break; + case ERR: + eprintf("error: %s\n", errtext[unpackerrc(buf)]); + } + } +} + +static void +putfile(char *file) +{ + unsigned char inbuf[PKTSIZE], outbuf[PKTSIZE]; + int inb, outb, op, blkno, nextblkno = 0, done = 0; + + state = WWQ; + for (;;) { + switch (state) { + case WWQ: + outb = packreq(outbuf, WWQ, file, "octet"); + writepkt(outbuf, outb); + inb = readpkt(inbuf, sizeof(inbuf)); + if (inb > 0) { + op = unpackop(inbuf); + if (op != ACK && op != ERR) + eprintf("bad opcode: %d\n", op); + state = op; + } + break; + case DATA: + if (blkno == nextblkno) { + nextblkno++; + packdata(outbuf, nextblkno); + outb = read(0, &outbuf[HDRSIZE], BLKSIZE); + if (outb < BLKSIZE) + done = 1; + } + writepkt(outbuf, outb + HDRSIZE); + inb = readpkt(inbuf, sizeof(inbuf)); + if (inb > 0) { + op = unpackop(inbuf); + if (op != ACK && op != ERR) + eprintf("bad opcode: %d\n", op); + state = op; + } + break; + case ACK: + if (inb < HDRSIZE) + eprintf("truncated packet\n"); + blkno = unpackblkno(inbuf); + if (blkno == nextblkno) + if (done) + return; + state = DATA; + break; + case ERR: + eprintf("error: %s\n", errtext[unpackerrc(inbuf)]); + } + } +} + +static void +usage(void) +{ + eprintf("usage: %s -h host [-p port] [-x | -c] file\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct addrinfo hints, *res, *r; + struct timeval tv; + char *host = NULL, *port = "tftp"; + void (*fn)(char *) = getfile; + int ret; + + ARGBEGIN { + case 'h': + host = EARGF(usage()); + break; + case 'p': + port = EARGF(usage()); + break; + case 'x': + fn = getfile; + break; + case 'c': + fn = putfile; + break; + default: + usage(); + } ARGEND + + if (!host || !argc) + usage(); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + ret = getaddrinfo(host, port, &hints, &res); + if (ret) + eprintf("getaddrinfo: %s\n", gai_strerror(ret)); + + for (r = res; r; r = r->ai_next) { + if (r->ai_family != AF_INET && + r->ai_family != AF_INET6) + continue; + s = socket(r->ai_family, r->ai_socktype, + r->ai_protocol); + if (s < 0) + continue; + break; + } + if (!r) + eprintf("cannot create socket\n"); + memcpy(&to, r->ai_addr, r->ai_addrlen); + tolen = r->ai_addrlen; + freeaddrinfo(res); + + tv.tv_sec = TIMEOUT_SEC; + tv.tv_usec = 0; + if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) + eprintf("setsockopt:"); + + fn(argv[0]); + return 0; +} diff --git a/source/sbase/time.1 b/source/sbase/time.1 new file mode 100644 index 00000000..d5327ff1 --- /dev/null +++ b/source/sbase/time.1 @@ -0,0 +1,49 @@ +.Dd 2015-10-08 +.Dt TIME 1 +.Os sbase +.Sh NAME +.Nm time +.Nd time a command +.Sh SYNOPSIS +.Nm +.Op Fl p +.Ar cmd +.Op Ar arg ... +.Sh DESCRIPTION +.Nm +executes +.Ar cmd +and writes timing statistics to stderr after it finishes. +The statistics include the elapsed real time +between invocation and termination and the user +and system CPU time (see +.Xr times 2 ) . +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl p +Use the format "real %f\enuser %f\ensys %f\en" for printing. +This is the default. +.El +.Sh EXIT STATUS +.Bl -tag -width Ds +.It 0 +.Ar cmd +executed successfully. +.It 1 +Internal error. +.It 126 +.Ar cmd +was found but could not be executed. +.It 127 +.Ar cmd +could not be found. +.El +.Sh SEE ALSO +.Xr times 2 , +.Xr waitpid 2 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/time.c b/source/sbase/time.c new file mode 100644 index 00000000..60a8c8df --- /dev/null +++ b/source/sbase/time.c @@ -0,0 +1,73 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-p] cmd [arg ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + pid_t pid; + struct tms tms; /* user and sys times */ + clock_t r0, r1; /* real time */ + long ticks; /* per second */ + int status, savederrno, ret = 0; + + ARGBEGIN { + case 'p': + break; + default: + usage(); + } ARGEND + + if (!argc) + usage(); + + if ((ticks = sysconf(_SC_CLK_TCK)) <= 0) + eprintf("sysconf _SC_CLK_TCK:"); + + if ((r0 = times(&tms)) == (clock_t)-1) + eprintf("times:"); + + switch ((pid = fork())) { + case -1: + eprintf("fork:"); + case 0: + execvp(argv[0], argv); + savederrno = errno; + weprintf("execvp %s:", argv[0]); + _exit(126 + (savederrno == ENOENT)); + default: + break; + } + waitpid(pid, &status, 0); + + if ((r1 = times(&tms)) == (clock_t)-1) + eprintf("times:"); + + if (WIFSIGNALED(status)) { + fprintf(stderr, "Command terminated by signal %d\n", + WTERMSIG(status)); + ret = 128 + WTERMSIG(status); + } + + fprintf(stderr, "real %f\nuser %f\nsys %f\n", + (r1 - r0) / (double)ticks, + tms.tms_cutime / (double)ticks, + tms.tms_cstime / (double)ticks); + + if (WIFEXITED(status)) + ret = WEXITSTATUS(status); + + return ret; +} diff --git a/source/sbase/touch.1 b/source/sbase/touch.1 new file mode 100644 index 00000000..38c99e2a --- /dev/null +++ b/source/sbase/touch.1 @@ -0,0 +1,65 @@ +.Dd 2015-10-08 +.Dt TOUCH 1 +.Os sbase +.Sh NAME +.Nm touch +.Nd set file timestamps +.Sh SYNOPSIS +.Nm +.Op Fl acm +.Op Fl d Ar time | Fl r Ar ref_file | Fl T Ar time | Fl t Ar time +.Ar file ... +.Sh DESCRIPTION +.Nm +sets the access and modification time of each +.Ar file +to the current time of day. If +.Ar file +doesn't exist, it is created with default permissions. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl a | Fl m +Set the access | modification time of +.Ar file. +.It Fl c +Don't create +.Ar file +if it doesn't exist, not affecting exit status. +.It Fl d Ar time +Set the +.Ar time +of the format YYYY-MM-DDThh:mm:SS[Z] used for +.Op Fl am . +.It Fl r Ar ref_file +Set the +.Ar time +used for +.Op Fl am +to the modification time of +.Ar ref_file . +.It Fl T Ar time +Set the +.Ar time +used for +.Op Fl am +given as the number of seconds since the +Unix epoch 1970-01-01T00:00:00Z. +.It Fl t Ar time +Set the +.Ar time +of the format [[CC]YY]MMDDhhmm[.SS] used for +.Op Fl am . +.El +.Sh SEE ALSO +.Xr date 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification except from fractional seconds with +.Op Fl d . +.Pp +The +.Op Fl T +flag is an extension to that specification. diff --git a/source/sbase/touch.c b/source/sbase/touch.c new file mode 100644 index 00000000..b957fa54 --- /dev/null +++ b/source/sbase/touch.c @@ -0,0 +1,164 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +static int aflag; +static int cflag; +static int mflag; +static struct timespec times[2]; + +static void +touch(const char *file) +{ + int fd; + struct stat st; + int r; + + if ((r = stat(file, &st)) < 0) { + if (errno != ENOENT) + eprintf("stat %s:", file); + if (cflag) + return; + } else if (!r) { + if (!aflag) + times[0] = st.st_atim; + if (!mflag) + times[1] = st.st_mtim; + if (utimensat(AT_FDCWD, file, times, 0) < 0) + eprintf("utimensat %s:", file); + return; + } + + if ((fd = open(file, O_CREAT | O_EXCL, 0644)) < 0) + eprintf("open %s:", file); + close(fd); + + touch(file); +} + +static time_t +parsetime(char *str, time_t current) +{ + struct tm *cur, t; + int zulu = 0; + char *format; + size_t len = strlen(str); + + cur = localtime(¤t); + t.tm_isdst = -1; + + switch (len) { + /* -t flag argument */ + case 8: + t.tm_sec = 0; + t.tm_year = cur->tm_year; + format = "%m%d%H%M"; + break; + case 10: + t.tm_sec = 0; + format = "%y%m%d%H%M"; + break; + case 11: + t.tm_year = cur->tm_year; + format = "%m%d%H%M.%S"; + break; + case 12: + t.tm_sec = 0; + format = "%Y%m%d%H%M"; + break; + case 13: + format = "%y%m%d%H%M.%S"; + break; + case 15: + format = "%Y%m%d%H%M.%S"; + break; + /* -d flag argument */ + case 19: + format = "%Y-%m-%dT%H:%M:%S"; + break; + case 20: + /* only Zulu-timezone supported */ + if (str[19] != 'Z') + eprintf("Invalid time zone\n"); + str[19] = 0; + zulu = 1; + format = "%Y-%m-%dT%H:%M:%S"; + break; + default: + eprintf("Invalid date format length\n", str); + } + + if (!strptime(str, format, &t)) + weprintf("strptime %s: Invalid date format\n", str); + if (zulu) { + t.tm_hour += t.tm_gmtoff / 60; + t.tm_gmtoff = 0; + t.tm_zone = "Z"; + } + + return mktime(&t); +} + +static void +usage(void) +{ + eprintf("usage: %s [-acm] [-d time | -r ref_file | -t time | -T time] " + "file ...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct stat st; + char *ref = NULL; + clock_gettime(CLOCK_REALTIME, ×[0]); + + ARGBEGIN { + case 'a': + aflag = 1; + break; + case 'c': + cflag = 1; + break; + case 'd': + case 't': + times[0].tv_sec = parsetime(EARGF(usage()), times[0].tv_sec); + break; + case 'm': + mflag = 1; + break; + case 'r': + ref = EARGF(usage()); + if (stat(ref, &st) < 0) + eprintf("stat '%s':", ref); + times[0] = st.st_atim; + times[1] = st.st_mtim; + break; + case 'T': + times[0].tv_sec = estrtonum(EARGF(usage()), 0, LLONG_MAX); + break; + default: + usage(); + } ARGEND + + if (!argc) + usage(); + if (!aflag && !mflag) + aflag = mflag = 1; + if (!ref) + times[1] = times[0]; + + for (; *argv; argc--, argv++) + touch(*argv); + + return 0; +} diff --git a/source/sbase/tr.1 b/source/sbase/tr.1 new file mode 100644 index 00000000..a9b25c6e --- /dev/null +++ b/source/sbase/tr.1 @@ -0,0 +1,82 @@ +.Dd 2015-10-08 +.Dt TR 1 +.Os sbase +.Sh NAME +.Nm tr +.Nd translate characters +.Sh SYNOPSIS +.Nm +.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 +.Fl d +is set. +.El +.Sh SET +.Bl -tag -width Ds +.It Literal Sy c +.It Escape sequence Sy \ec +\e\e, \e', \e", \ea, \eb, \ee, \ef, \en, \er, \et, \ev, \exH[H], \eO[OO] +.It Range Sy c-d +.It Repeat Sy [c*n] +Only in +.Ar set2 . +If n = 0 or left out, set n to length of +.Ar set1 . +.It Character class Sy [:class:] +See +.Xr wctype 3 . +.It Equivalence class Sy [=c=] +Resolve to +.Sy c . +.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 awk 1 , +.Xr sed 1 , +.Xr utf8 7 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification except from equivalence classes. diff --git a/source/sbase/tr.c b/source/sbase/tr.c new file mode 100644 index 00000000..361ac409 --- /dev/null +++ b/source/sbase/tr.c @@ -0,0 +1,277 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#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)(Rune); +} classes[] = { + { "alnum", isalnumrune }, + { "alpha", isalpharune }, + { "blank", isblankrune }, + { "cntrl", iscntrlrune }, + { "digit", isdigitrune }, + { "graph", isgraphrune }, + { "lower", islowerrune }, + { "print", isprintrune }, + { "punct", ispunctrune }, + { "space", isspacerune }, + { "upper", isupperrune }, + { "xdigit", isxdigitrune }, +}; + +static struct range *set1 = NULL; +static size_t set1ranges = 0; +static int (*set1check)(Rune) = NULL; +static struct range *set2 = NULL; +static size_t set2ranges = 0; +static int (*set2check)(Rune) = 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 +makeset(char *str, struct range **set, int (**check)(Rune)) +{ + Rune *rstr; + size_t len, i, j, m, n; + size_t q, setranges = 0; + int factor, base; + + /* rstr defines at most len ranges */ + unescape(str); + rstr = ereallocarray(NULL, utflen(str) + 1, sizeof(*rstr)); + len = utftorunestr(str, rstr); + *set = ereallocarray(NULL, 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] == '=') { + if (j - i != 4) + goto literal; + (*set)[setranges].start = rstr[i + 2]; + (*set)[setranges].end = rstr[i + 2]; + (*set)[setranges].quant = 1; + setranges++; + 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; + int ret = 0; + + ARGBEGIN { + case 'c': + case 'C': + cflag = 1; + break; + case 'd': + dflag = 1; + break; + case 's': + sflag = 1; + break; + default: + usage(); + } ARGEND + + if (!argc || 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 (argc == 2 && !set2check != !set1check) + eprintf("can't mix classes with non-classes.\n"); + if (set2check && set2check != islowerrune && set2check != isupperrune) + eprintf("set2 can only be the 'lower' or 'upper' class.\n"); + if (set2check && cflag && !dflag) + eprintf("set2 can't be imaged to from a complement.\n"); +read: + if (!efgetrune(&r, stdin, "")) { + ret |= fshut(stdin, "") | fshut(stdout, ""); + return ret; + } + off1 = off2 = 0; + for (i = 0; i < set1ranges; i++) { + if (set1[i].start <= r && r <= set1[i].end) { + if (dflag) { + if (!cflag || (sflag && r == lastrune)) + goto read; + else + goto write; + } + if (cflag) + 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; + + if (sflag && (r == lastrune)) + goto read; + goto write; + } + } + if (set1check && set1check(r)) { + if (dflag) { + if (!cflag || (sflag && r == lastrune)) + goto read; + else + goto write; + } + if (sflag) { + if (r == lastrune) + goto read; + else + goto write; + } + if (set1check == isupperrune && set2check == islowerrune) + r = tolowerrune(r); + else if (set1check == islowerrune && set2check == isupperrune) + r = toupperrune(r); + else if (set2ranges > 0) + r = cflag ? r : set2[set2ranges - 1].end; + else + eprintf("Misaligned character classes.\n"); + } else if (cflag && set2ranges > 0) { + r = set2[set2ranges - 1].end; + } + if (dflag && cflag) + goto read; + if (dflag && sflag && r == lastrune) + goto read; +write: + lastrune = r; + efputrune(&r, stdout, ""); + goto read; +} diff --git a/source/sbase/true.1 b/source/sbase/true.1 new file mode 100644 index 00000000..8f09c409 --- /dev/null +++ b/source/sbase/true.1 @@ -0,0 +1,17 @@ +.Dd 2015-10-08 +.Dt TRUE 1 +.Os sbase +.Sh NAME +.Nm true +.Nd return success +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +.Nm +returns a status code indicating success. +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/true.c b/source/sbase/true.c new file mode 100644 index 00000000..cb081ec0 --- /dev/null +++ b/source/sbase/true.c @@ -0,0 +1,6 @@ +/* See LICENSE file for copyright and license details. */ +int +main(void) +{ + return 0; +} diff --git a/source/sbase/tsort.1 b/source/sbase/tsort.1 new file mode 100644 index 00000000..2f8f0a1e --- /dev/null +++ b/source/sbase/tsort.1 @@ -0,0 +1,73 @@ +.Dd 2016-02-16 +.Dt TSORT 1 +.Os sbase +.Sh NAME +.Nm tsort +.Nd topological sort +.Sh SYNOPSIS +.Nm +.Op Ar file +.Sh DESCRIPTION +.Nm +topologically sorts a graph. The graph is read +either from +.Ar file +or from standard input. The result is not optimized +for any particular usage. Loops are detected and +reported to standard error, but does not stop the sort. +.Pp +The input is a list of edges (vertex pairs), where +the edge is directed from the first vertex to the +second vertex. +.Sh OPTIONS +None. +.Sh EXIT STATUS +.Bl -tag -width Ds +.It 0 +The graph as successfully sorted. +.It 1 +The graph as successfully sorted, but contained loops. +.It > 1 +An error occurred. +.El +.Sh EXAMPLES +.Bd -literal -offset left +The input + + a a + a b + a c + a c + a d + b c + c b + e f + +or equivalently + + a a a b a c a c a d + b c c b e f + +represents the graph + + ┌─┐ + ↓ │ + ┏━━━┓ + ┌──────┃ a ┃──────┐ + │ ┗━━━┛ │ + │ │ │ │ + ↓ ↓ ↓ ↓ + ┏━━━┓───→┏━━━┓ ┏━━━┓ + ┃ b ┃ ┃ c ┃ ┃ d ┃ + ┗━━━┛←───┗━━━┛ ┗━━━┛ + + ┏━━━┓ ┏━━━┓ + ┃ e ┃───→┃ f ┃ + ┗━━━┛ ┗━━━┛ +.Ed +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/tsort.c b/source/sbase/tsort.c new file mode 100644 index 00000000..d093df8a --- /dev/null +++ b/source/sbase/tsort.c @@ -0,0 +1,209 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "util.h" + +enum { WHITE = 0, GREY, BLACK }; + +struct vertex; + +struct edge { + struct vertex *to; + struct edge *next; +}; + +struct vertex { + char *name; + struct vertex *next; + struct edge edges; + size_t in_edges; + int colour; +}; + +static struct vertex graph; + +static void +find_vertex(const char *name, struct vertex **it, struct vertex **prev) +{ + for (*prev = &graph; (*it = (*prev)->next); *prev = *it) { + int cmp = strcmp(name, (*it)->name); + if (cmp > 0) + continue; + if (cmp < 0) + *it = 0; + return; + } +} + +static void +find_edge(struct vertex *from, const char *to, struct edge **it, struct edge **prev) +{ + for (*prev = &(from->edges); (*it = (*prev)->next); *prev = *it) { + int cmp = strcmp(to, (*it)->to->name); + if (cmp > 0) + continue; + if (cmp < 0) + *it = 0; + return; + } +} + +static struct vertex * +add_vertex(char *name) +{ + struct vertex *vertex; + struct vertex *prev; + + find_vertex(name, &vertex, &prev); + if (vertex) + return vertex; + + vertex = encalloc(2, 1, sizeof(*vertex)); + vertex->name = name; + vertex->next = prev->next; + prev->next = vertex; + + return vertex; +} + +static struct edge * +add_edge(struct vertex *from, struct vertex* to) +{ + struct edge *edge; + struct edge *prev; + + find_edge(from, to->name, &edge, &prev); + if (edge) + return edge; + + edge = encalloc(2, 1, sizeof(*edge)); + edge->to = to; + edge->next = prev->next; + prev->next = edge; + to->in_edges += 1; + + return edge; +} + +static void +load_graph(FILE *fp) +{ +#define SKIP(VAR, START, FUNC) for (VAR = START; FUNC(*VAR) && *VAR; VAR++) +#define TOKEN_END(P) do { if (*P) *P++ = 0; else P = 0; } while (0) + + char *line = 0; + size_t size = 0; + ssize_t len; + char *p; + char *name; + struct vertex *from = 0; + + while ((len = getline(&line, &size, fp)) != -1) { + if (line[len - 1] == '\n') + line[--len] = 0; + for (p = line; p;) { + SKIP(name, p, isspace); + if (!*name) + break; + SKIP(p, name, !isspace); + TOKEN_END(p); + if (!from) { + from = add_vertex(enstrdup(2, name)); + } else if (strcmp(from->name, name)) { + add_edge(from, add_vertex(enstrdup(2, name))); + from = 0; + } else { + from = 0; + } + } + } + + free(line); + + if (from) + enprintf(2, "odd number of tokens in input\n"); +} + +static int +sort_graph_visit(struct vertex *u) +{ + struct edge *e = &(u->edges); + struct vertex *v; + int r = 0; + u->colour = GREY; + printf("%s\n", u->name); + while ((e = e->next)) { + v = e->to; + if (v->colour == WHITE) { + v->in_edges -= 1; + if (v->in_edges == 0) + r |= sort_graph_visit(v); + } else if (v->colour == GREY) { + r = 1; + fprintf(stderr, "%s: loop detected between %s and %s\n", + argv0, u->name, v->name); + } + } + u->colour = BLACK; + return r; +} + +static int +sort_graph(void) +{ + struct vertex *u, *prev; + int r = 0; + size_t in_edges; + for (in_edges = 0; graph.next; in_edges++) { + for (prev = &graph; (u = prev->next); prev = u) { + if (u->colour != WHITE) + goto unlist; + if (u->in_edges > in_edges) + continue; + r |= sort_graph_visit(u); + unlist: + prev->next = u->next; + u = prev; + } + } + return r; +} + +static void +usage(void) +{ + enprintf(2, "usage: %s [file]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp = stdin; + const char *fn = ""; + int ret = 0; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if (argc > 1) + usage(); + if (argc && strcmp(*argv, "-")) + if (!(fp = fopen(fn = *argv, "r"))) + enprintf(2, "fopen %s:", *argv); + + memset(&graph, 0, sizeof(graph)); + load_graph(fp); + enfshut(2, fp, fn); + + ret = sort_graph(); + + if (fshut(stdout, "") | fshut(stderr, "")) + ret = 2; + + return ret; +} diff --git a/source/sbase/tty.1 b/source/sbase/tty.1 new file mode 100644 index 00000000..e63d28c7 --- /dev/null +++ b/source/sbase/tty.1 @@ -0,0 +1,28 @@ +.Dd 2015-10-08 +.Dt TTY 1 +.Os sbase +.Sh NAME +.Nm tty +.Nd print terminal name +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +.Nm +writes the name of the terminal open on stdin to stdout. +.Sh EXIT STATUS +.Bl -tag -width Ds +.It 0 +stdin is a terminal. +.It 1 +stdin is not a terminal. +.It > 1 +An error occurred. +.El +.Sh SEE ALSO +.Xr ttyname 3 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/tty.c b/source/sbase/tty.c new file mode 100644 index 00000000..5afec799 --- /dev/null +++ b/source/sbase/tty.c @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *tty; + + argv0 = argv[0], argc--, argv++; + + if (argc) + usage(); + + tty = ttyname(STDIN_FILENO); + puts(tty ? tty : "not a tty"); + + return fshut(stdout, "") || !tty; +} diff --git a/source/sbase/uname.1 b/source/sbase/uname.1 new file mode 100644 index 00000000..5a58ca20 --- /dev/null +++ b/source/sbase/uname.1 @@ -0,0 +1,38 @@ +.Dd 2015-10-08 +.Dt UNAME 1 +.Os sbase +.Sh NAME +.Nm uname +.Nd print system information +.Sh SYNOPSIS +.Nm +.Op Fl amnrsv +.Sh DESCRIPTION +.Nm +writes system information to stdout. If no flags are given, +.Nm +implies +.Fl s . +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl a +Print all the information below. +.It Fl m +Print the machine's architecture. +.It Fl n +Print the system's network node hostname. +.It Fl r +Print the operating system's release name. +.It Fl s +Print the name of the operating system. +.It Fl v +Print the operating system's version name. +.El +.Sh SEE ALSO +.Xr uname 2 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/uname.c b/source/sbase/uname.c new file mode 100644 index 00000000..dfc979e3 --- /dev/null +++ b/source/sbase/uname.c @@ -0,0 +1,59 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s [-amnrsv]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct utsname u; + int mflag = 0, nflag = 0, rflag = 0, sflag = 0, vflag = 0; + + 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(stdout, u.sysname); + if (nflag) + putword(stdout, u.nodename); + if (rflag) + putword(stdout, u.release); + if (vflag) + putword(stdout, u.version); + if (mflag) + putword(stdout, u.machine); + putchar('\n'); + + return fshut(stdout, ""); +} diff --git a/source/sbase/unexpand.1 b/source/sbase/unexpand.1 new file mode 100644 index 00000000..62e33d06 --- /dev/null +++ b/source/sbase/unexpand.1 @@ -0,0 +1,45 @@ +.Dd 2015-10-08 +.Dt UNEXPAND 1 +.Os sbase +.Sh NAME +.Nm unexpand +.Nd unexpand spaces to tabs +.Sh SYNOPSIS +.Nm +.Op Fl a +.Op Fl t Ar tablist +.Op Ar file ... +.Sh DESCRIPTION +.Nm +converts spaces to tabs in each +.Ar file +as specified in +.Ar tablist . +If no file is given, +.Nm +reads from stdin. +.Pp +Backspace characters are preserved and decrement the column count +for tab calculations. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl a +Convert spaces to tabs everywhere, not just at the start of lines. +.It Fl t Ar tablist +Specify tab size or tabstops. +.Ar tablist +is a list of one (in the former case) or multiple (in the latter case) +strictly positive integers separated by ' ' or ','. +.Pp +The default +.Ar tablist +is "8". +.El +.Sh SEE ALSO +.Xr expand 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/unexpand.c b/source/sbase/unexpand.c new file mode 100644 index 00000000..1818691e --- /dev/null +++ b/source/sbase/unexpand.c @@ -0,0 +1,174 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "utf.h" +#include "util.h" + +static int aflag = 0; +static size_t *tablist = NULL; +static size_t tablistlen = 8; + +static size_t +parselist(const char *s) +{ + size_t i; + char *p, *tmp; + + tmp = estrdup(s); + for (i = 0; (p = strsep(&tmp, " ,")); i++) { + if (*p == '\0') + eprintf("empty field in tablist\n"); + tablist = ereallocarray(tablist, i + 1, sizeof(*tablist)); + tablist[i] = estrtonum(p, 1, MIN(LLONG_MAX, SIZE_MAX)); + if (i > 0 && tablist[i - 1] >= tablist[i]) + eprintf("tablist must be ascending\n"); + } + tablist = ereallocarray(tablist, i + 1, sizeof(*tablist)); + + return i; +} + +static void +unexpandspan(size_t last, size_t col) +{ + size_t off, i, j; + Rune r; + + if (tablistlen == 1) { + i = 0; + off = last % tablist[i]; + + if ((col - last) + off >= tablist[i] && last < col) + last -= off; + + r = '\t'; + for (; last + tablist[i] <= col; last += tablist[i]) + efputrune(&r, stdout, ""); + r = ' '; + for (; last < col; last++) + efputrune(&r, stdout, ""); + } else { + for (i = 0; i < tablistlen; i++) + if (col < tablist[i]) + break; + for (j = 0; j < tablistlen; j++) + if (last < tablist[j]) + break; + r = '\t'; + for (; j < i; j++) { + efputrune(&r, stdout, ""); + last = tablist[j]; + } + r = ' '; + for (; last < col; last++) + efputrune(&r, stdout, ""); + } +} + +static void +unexpand(const char *file, FILE *fp) +{ + Rune r; + size_t last = 0, col = 0, i; + int bol = 1; + + while (efgetrune(&r, fp, file)) { + switch (r) { + case ' ': + if (!bol && !aflag) + last++; + col++; + break; + case '\t': + if (tablistlen == 1) { + if (!bol && !aflag) + last += tablist[0] - col % tablist[0]; + col += tablist[0] - col % tablist[0]; + } else { + for (i = 0; i < tablistlen; i++) + if (col < tablist[i]) + break; + if (!bol && !aflag) + last = tablist[i]; + col = tablist[i]; + } + break; + case '\b': + if (bol || aflag) + unexpandspan(last, col); + col -= (col > 0); + last = col; + bol = 0; + break; + case '\n': + if (bol || aflag) + unexpandspan(last, col); + last = col = 0; + bol = 1; + break; + default: + if (bol || aflag) + unexpandspan(last, col); + last = ++col; + bol = 0; + break; + } + if ((r != ' ' && r != '\t') || (!aflag && !bol)) + efputrune(&r, stdout, ""); + } + if (last < col && (bol || aflag)) + unexpandspan(last, col); +} + +static void +usage(void) +{ + eprintf("usage: %s [-a] [-t tablist] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + int ret = 0; + char *tl = "8"; + + ARGBEGIN { + case 't': + tl = EARGF(usage()); + if (!*tl) + eprintf("tablist cannot be empty\n"); + /* Fallthrough: -t implies -a */ + case 'a': + aflag = 1; + break; + default: + usage(); + } ARGEND + + tablistlen = parselist(tl); + + if (!argc) { + unexpand("", stdin); + } else { + for (; *argv; argc--, argv++) { + if (!strcmp(*argv, "-")) { + *argv = ""; + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + ret = 1; + continue; + } + unexpand(*argv, fp); + if (fp != stdin && fshut(fp, *argv)) + ret = 1; + } + } + + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/uniq.1 b/source/sbase/uniq.1 new file mode 100644 index 00000000..993fe442 --- /dev/null +++ b/source/sbase/uniq.1 @@ -0,0 +1,47 @@ +.Dd 2015-10-08 +.Dt UNIQ 1 +.Os sbase +.Sh NAME +.Nm uniq +.Nd report or filter out repeated lines in a file +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Fl d | u +.Op Fl f Ar num +.Op Fl s Ar num +.Op Ar input Op Ar output +.Sh DESCRIPTION +.Nm +reads the +.Ar input +file and writes one copy of a line from each group of consecutive +duplicate lines to the +.Ar output +file. If no +.Ar input +file is given +.Nm +reads from stdin. If no +.Ar output +file is given +.Nm +writes to stdout. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Prefix each line with the number of consecutive occurrences in +.Ar input . +.It Fl d | Fl u +Print duplicate | unique lines only. +.It Fl f Ar num | Fl s Ar num +Ignore the first +.Ar num +fields | characters in each input line when doing comparisons. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/uniq.c b/source/sbase/uniq.c new file mode 100644 index 00000000..f1ad6a7b --- /dev/null +++ b/source/sbase/uniq.c @@ -0,0 +1,144 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "text.h" +#include "util.h" + +static const char *countfmt = ""; +static int dflag = 0; +static int uflag = 0; +static int fskip = 0; +static int sskip = 0; + +static struct line prevl; +static ssize_t prevoff = -1; +static long prevlinecount = 0; + +static size_t +uniqskip(struct line *l) +{ + size_t i; + int f = fskip, s = sskip; + + for (i = 0; i < l->len && f; --f) { + while (isblank(l->data[i])) + i++; + while (i < l->len && !isblank(l->data[i])) + i++; + } + for (; s && i < l->len && l->data[i] != '\n'; --s, i++) + ; + + return i; +} + +static void +uniqline(FILE *ofp, struct line *l) +{ + size_t loff; + + if (l) { + loff = uniqskip(l); + + if (prevoff >= 0 && (l->len - loff) == (prevl.len - prevoff) && + !memcmp(l->data + loff, prevl.data + prevoff, l->len - loff)) { + ++prevlinecount; + return; + } + } + + if (prevoff >= 0) { + if ((prevlinecount == 1 && !dflag) || + (prevlinecount != 1 && !uflag)) { + if (*countfmt) + fprintf(ofp, countfmt, prevlinecount); + fwrite(prevl.data, 1, prevl.len, ofp); + } + prevoff = -1; + } + + if (l) { + if (!prevl.data || l->len >= prevl.len) { + prevl.data = erealloc(prevl.data, l->len); + } + prevl.len = l->len; + memcpy(prevl.data, l->data, prevl.len); + prevoff = loff; + } + prevlinecount = 1; +} + +static void +uniq(FILE *fp, FILE *ofp) +{ + static struct line line; + static size_t size; + ssize_t len; + + while ((len = getline(&line.data, &size, fp)) > 0) { + line.len = len; + uniqline(ofp, &line); + } +} + +static void +uniqfinish(FILE *ofp) +{ + uniqline(ofp, NULL); +} + +static void +usage(void) +{ + eprintf("usage: %s [-c] [-d | -u] [-f fields] [-s chars]" + " [input [output]]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp[2] = { stdin, stdout }; + int ret = 0, i; + char *fname[2] = { "", "" }; + + ARGBEGIN { + case 'c': + countfmt = "%7ld "; + break; + case 'd': + dflag = 1; + break; + case 'u': + uflag = 1; + break; + case 'f': + fskip = estrtonum(EARGF(usage()), 0, INT_MAX); + break; + case 's': + sskip = estrtonum(EARGF(usage()), 0, INT_MAX); + break; + default: + usage(); + } ARGEND + + if (argc > 2) + usage(); + + for (i = 0; i < argc; i++) { + if (strcmp(argv[i], "-")) { + fname[i] = argv[i]; + if (!(fp[i] = fopen(argv[i], (i == 0) ? "r" : "w"))) + eprintf("fopen %s:", argv[i]); + } + } + + uniq(fp[0], fp[1]); + uniqfinish(fp[1]); + + ret |= fshut(fp[0], fname[0]) | fshut(fp[1], fname[1]); + + return ret; +} diff --git a/source/sbase/unlink.1 b/source/sbase/unlink.1 new file mode 100644 index 00000000..a327b240 --- /dev/null +++ b/source/sbase/unlink.1 @@ -0,0 +1,23 @@ +.Dd 2015-10-08 +.Dt UNLINK 1 +.Os sbase +.Sh NAME +.Nm unlink +.Nd unlink file +.Sh SYNOPSIS +.Nm +.Ar file +.Sh DESCRIPTION +.Nm +calls +.Xr unlink 2 +on +.Ar file . +.Sh SEE ALSO +.Xr unlink 2 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/unlink.c b/source/sbase/unlink.c new file mode 100644 index 00000000..bdc6061e --- /dev/null +++ b/source/sbase/unlink.c @@ -0,0 +1,24 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s file\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + argv0 = argv[0], argc--, argv++; + + if (argc != 1) + usage(); + + if (unlink(argv[0]) < 0) + eprintf("unlink: '%s':", argv[0]); + + return 0; +} diff --git a/source/sbase/utf.h b/source/sbase/utf.h new file mode 100644 index 00000000..a74be94a --- /dev/null +++ b/source/sbase/utf.h @@ -0,0 +1,67 @@ +/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith + * + * 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 + +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 isalnumrune(Rune); +int isalpharune(Rune); +int isblankrune(Rune); +int iscntrlrune(Rune); +int isdigitrune(Rune); +int isgraphrune(Rune); +int islowerrune(Rune); +int isprintrune(Rune); +int ispunctrune(Rune); +int isspacerune(Rune); +int istitlerune(Rune); +int isupperrune(Rune); +int isxdigitrune(Rune); + +Rune tolowerrune(Rune); +Rune toupperrune(Rune); + +int utftorunestr(const char*, Rune *); + +int fgetrune(Rune *, FILE *); +int efgetrune(Rune *, FILE *, const char *); +int fputrune(const Rune *, FILE *); +int efputrune(const Rune *, FILE *, const char *); diff --git a/source/sbase/util.h b/source/sbase/util.h new file mode 100644 index 00000000..b5860dcb --- /dev/null +++ b/source/sbase/util.h @@ -0,0 +1,80 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include + +#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)) +#undef LIMIT +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) + +#define LEN(x) (sizeof (x) / sizeof *(x)) + +extern char *argv0; + +void *ecalloc(size_t, size_t); +void *emalloc(size_t); +void *erealloc(void *, size_t); +#undef reallocarray +void *reallocarray(void *, size_t, size_t); +void *ereallocarray(void *, size_t, size_t); +char *estrdup(const char *); +char *estrndup(const char *, size_t); +void *encalloc(int, size_t, size_t); +void *enmalloc(int, size_t); +void *enrealloc(int, void *, size_t); +char *enstrdup(int, const char *); +char *enstrndup(int, const char *, size_t); + +void enfshut(int, FILE *, const char *); +void efshut(FILE *, const char *); +int fshut(FILE *, const char *); + +void enprintf(int, const char *, ...); +void eprintf(const char *, ...); +void weprintf(const char *, ...); + +double estrtod(const char *); + +#undef strcasestr +char *strcasestr(const char *, const char *); + +#undef strlcat +size_t strlcat(char *, const char *, size_t); +size_t estrlcat(char *, const char *, size_t); +#undef strlcpy +size_t strlcpy(char *, const char *, size_t); +size_t estrlcpy(char *, const char *, size_t); + +#undef strsep +char *strsep(char **, const char *); + +/* 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 *, int)); +void fnck(const char *, const char *, int (*)(const char *, const char *, int), int); +mode_t getumask(void); +char *humansize(off_t); +mode_t parsemode(const char *, mode_t, mode_t); +off_t parseoffset(const char *); +void putword(FILE *, const char *); +#undef strtonum +long long strtonum(const char *, long long, long long, const char **); +long long enstrtonum(int, const char *, long long, long long); +long long estrtonum(const char *, long long, long long); +size_t unescape(char *); +int mkdirp(const char *); +#undef memmem +void *memmem(const void *, size_t, const void *, size_t); diff --git a/source/sbase/uudecode.1 b/source/sbase/uudecode.1 new file mode 100644 index 00000000..05b5deba --- /dev/null +++ b/source/sbase/uudecode.1 @@ -0,0 +1,49 @@ +.Dd 2015-10-08 +.Dt UUDECODE 1 +.Os sbase +.Sh NAME +.Nm uudecode +.Nd decode a uuencoded file +.Sh SYNOPSIS +.Nm +.Op Fl m +.Op Fl o Ar output +.Op Ar file +.Sh DESCRIPTION +.Nm +reads +.Ar file +and writes a decoded version to the file specified in the uuencoded header. +In case the file already exists, it is truncated. Otherwise a new file is +created. The permissions of the created/accessed file are changed to +reflect the mode in the header. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl m +Use Base64 for decoding. +.It Fl o Ar output +Write to +.Ar output +rather than the file specified in the header. +.El +.Sh IMPLEMENTATION NOTES +For safety uudecode operates on regular files and stdout only. +Trying to uudecode to a link, directory, or special file +yields an error. +.Sh SEE ALSO +.Xr uuencode 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. +.Pp +The +.Op Fl m +flag is an extension to that specification. diff --git a/source/sbase/uudecode.c b/source/sbase/uudecode.c new file mode 100644 index 00000000..1d0bf72a --- /dev/null +++ b/source/sbase/uudecode.c @@ -0,0 +1,282 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include +#include + +#include "util.h" + +static int mflag = 0; +static int oflag = 0; + +static FILE * +parsefile(const char *fname) +{ + struct stat st; + int ret; + + if (!strcmp(fname, "/dev/stdout") || !strcmp(fname, "-")) + 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, char **header, mode_t *mode, char **fname) +{ + static char bufs[PATH_MAX + 18]; /* 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 (!(q = strchr(p, ' '))) + eprintf("malformed mode string in header, expected ' '\n"); + *header = bufs; + *q++ = '\0'; + p = q; + /* now header should be null terminated, q points to mode */ + if (!(q = strchr(p, ' '))) + eprintf("malformed mode string in header, expected ' '\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; + else + eprintf("header string does not contain output file\n"); +} + +static const char b64dt[] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-2,-2,-2,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1, 0,-1,-1,-1, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, + 49,50,51,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +}; + +static void +uudecodeb64(FILE *fp, FILE *outfp) +{ + char bufb[60], *pb; + char out[45], *po; + size_t n; + int b = 0, e, t = -1, l = 1; + unsigned char b24[3] = {0, 0, 0}; + + while ((n = fread(bufb, 1, sizeof(bufb), fp))) { + for (pb = bufb, po = out; pb < bufb + n; pb++) { + if (*pb == '\n') { + l++; + continue; + } else if (*pb == '=') { + switch (b) { + case 0: + /* expected '=' remaining + * including footer */ + if (--t) { + fwrite(out, 1, + (po - out), + outfp); + return; + } + continue; + case 1: + eprintf("%d: unexpected \"=\"" + "appeared\n", l); + case 2: + *po++ = b24[0]; + b = 0; + t = 5; /* expect 5 '=' */ + continue; + case 3: + *po++ = b24[0]; + *po++ = b24[1]; + b = 0; + t = 6; /* expect 6 '=' */ + continue; + } + } else if ((e = b64dt[(int)*pb]) == -1) + eprintf("%d: invalid byte \"%c\"\n", l, *pb); + else if (e == -2) /* whitespace */ + continue; + else if (t > 0) /* state is parsing pad/footer */ + eprintf("%d: invalid byte \"%c\"" + " after padding\n", + l, *pb); + switch (b) { /* decode next base64 chr based on state */ + case 0: b24[0] |= e << 2; break; + case 1: b24[0] |= (e >> 4) & 0x3; + b24[1] |= (e & 0xf) << 4; break; + case 2: b24[1] |= (e >> 2) & 0xf; + b24[2] |= (e & 0x3) << 6; break; + case 3: b24[2] |= e; break; + } + if (++b == 4) { /* complete decoding an octet */ + *po++ = b24[0]; + *po++ = b24[1]; + *po++ = b24[2]; + b24[0] = b24[1] = b24[2] = 0; + b = 0; + } + } + fwrite(out, 1, (po - out), outfp); + } + eprintf("%d: invalid uudecode footer \"====\" not found\n", l); +} + +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]\n", (c), 1 + ' ', 077 + ' ' + 1) + + while ((len = getline(&bufb, &n, fp)) > 0) { + p = bufb; + /* trim newlines */ + if (!len || bufb[len - 1] != '\n') + eprintf("no newline found, aborting\n"); + bufb[len - 1] = '\0'; + + /* 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 */ + if ((len = getline(&bufb, &n, fp)) < 0) + eprintf("getline:"); + if (len < 3 || strncmp(bufb, "end", 3) || bufb[3] != '\n') + eprintf("invalid uudecode footer \"end\" not found\n"); + free(bufb); +} + +static void +usage(void) +{ + eprintf("usage: %s [-m] [-o output] [file]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp = NULL, *nfp = NULL; + mode_t mode = 0; + int ret = 0; + char *fname, *header, *ifname, *ofname = NULL; + void (*d) (FILE *, FILE *) = NULL; + + ARGBEGIN { + case 'm': + mflag = 1; /* accepted but unused (autodetect file type) */ + break; + case 'o': + oflag = 1; + ofname = EARGF(usage()); + break; + default: + usage(); + } ARGEND + + if (argc > 1) + usage(); + + if (!argc || !strcmp(argv[0], "-")) { + fp = stdin; + ifname = ""; + } else { + if (!(fp = fopen(argv[0], "r"))) + eprintf("fopen %s:", argv[0]); + ifname = argv[0]; + } + + parseheader(fp, ifname, &header, &mode, &fname); + + if (!strncmp(header, "begin", sizeof("begin"))) + d = uudecode; + else if (!strncmp(header, "begin-base64", sizeof("begin-base64"))) + d = uudecodeb64; + else + eprintf("unknown header %s:", header); + + if (oflag) + fname = ofname; + if (!(nfp = parsefile(fname))) + eprintf("fopen %s:", fname); + + d(fp, nfp); + + if (nfp != stdout && chmod(fname, mode) < 0) + eprintf("chmod %s:", fname); + + ret |= fshut(fp, (fp == stdin) ? "" : argv[0]); + ret |= fshut(nfp, (nfp == stdout) ? "" : fname); + + return ret; +} diff --git a/source/sbase/uuencode.1 b/source/sbase/uuencode.1 new file mode 100644 index 00000000..2b3729c0 --- /dev/null +++ b/source/sbase/uuencode.1 @@ -0,0 +1,38 @@ +.Dd 2015-10-08 +.Dt UUENCODE 1 +.Os sbase +.Sh NAME +.Nm uuencode +.Nd encode a binary file +.Sh SYNOPSIS +.Nm +.Op Fl m +.Op Ar file +.Ar name +.Sh DESCRIPTION +.Nm +reads +.Ar file +and writes an encoded version to stdout. +The encoding uses only printing ASCII characters and +includes the mode of the file and the operand +.Ar name +for use by uudecode. +If no +.Ar name +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl m +Use Base64 for encoding. +.El +.Sh SEE ALSO +.Xr uudecode 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/uuencode.c b/source/sbase/uuencode.c new file mode 100644 index 00000000..b5317205 --- /dev/null +++ b/source/sbase/uuencode.c @@ -0,0 +1,129 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include + +#include "util.h" + +static unsigned int +b64e(unsigned char *b) +{ + unsigned int o, p = b[2] | (b[1] << 8) | (b[0] << 16); + const char b64et[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + o = b64et[p & 0x3f]; p >>= 6; + o = (o << 8) | b64et[p & 0x3f]; p >>= 6; + o = (o << 8) | b64et[p & 0x3f]; p >>= 6; + o = (o << 8) | b64et[p & 0x3f]; + + return o; +} + +static void +uuencodeb64(FILE *fp, const char *name, const char *s) +{ + struct stat st; + ssize_t n, m = 0; + unsigned char buf[45], *pb; + unsigned int out[sizeof(buf)/3 + 1], *po; + + if (fstat(fileno(fp), &st) < 0) + eprintf("fstat %s:", s); + printf("begin-base64 %o %s\n", st.st_mode & 0777, name); + /* write line by line */ + while ((n = fread(buf, 1, sizeof(buf), fp))) { + /* clear old buffer if converting with non-multiple of 3 */ + if (n != sizeof(buf) && (m = n % 3) != 0) { + buf[n] = '\0'; /* m == 2 */ + if (m == 1) buf[n+1] = '\0'; /* m == 1 */ + } + for (pb = buf, po = out; pb < buf + n; pb += 3) + *po++ = b64e(pb); + if (m != 0) { + unsigned int mask = 0xffffffff, dest = 0x3d3d3d3d; + /* m==2 -> 0x00ffffff; m==1 -> 0x0000ffff */ + mask >>= ((3-m) << 3); + po[-1] = (po[-1] & mask) | (dest & ~mask); + } + *po++ = '\n'; + fwrite(out, 1, (po - out) * sizeof(unsigned int) - 3, stdout); + } + if (ferror(fp)) + eprintf("'%s' read error:", s); + puts("===="); +} + +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", '`'); +} + +static void +usage(void) +{ + eprintf("usage: %s [-m] [file] name\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp = NULL; + void (*uuencode_f)(FILE *, const char *, const char *) = uuencode; + int ret = 0; + + ARGBEGIN { + case 'm': + uuencode_f = uuencodeb64; + break; + default: + usage(); + } ARGEND + + if (!argc || argc > 2) + usage(); + + if (argc == 1 || !strcmp(argv[0], "-")) { + uuencode_f(stdin, argv[0], ""); + } else { + if (!(fp = fopen(argv[0], "r"))) + eprintf("fopen %s:", argv[0]); + uuencode_f(fp, argv[1], argv[0]); + } + + ret |= fp && fshut(fp, argv[0]); + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/wc.1 b/source/sbase/wc.1 new file mode 100644 index 00000000..0cf7708a --- /dev/null +++ b/source/sbase/wc.1 @@ -0,0 +1,32 @@ +.Dd 2015-10-08 +.Dt WC 1 +.Os sbase +.Sh NAME +.Nm wc +.Nd word count +.Sh SYNOPSIS +.Nm +.Op Fl c | Fl m +.Op Fl lw +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes the number of lines, words and bytes in each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c | Fl l | Fl m | Fl w +Print the number of bytes | lines | characters | words. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification. diff --git a/source/sbase/wc.c b/source/sbase/wc.c new file mode 100644 index 00000000..56f2fdac --- /dev/null +++ b/source/sbase/wc.c @@ -0,0 +1,117 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "utf.h" +#include "util.h" + +static int lflag = 0; +static int wflag = 0; +static char cmode = 0; +static size_t tc = 0, tl = 0, tw = 0; + +static void +output(const char *str, size_t nc, size_t nl, size_t nw) +{ + int noflags = !cmode && !lflag && !wflag; + int first = 1; + + if (lflag || noflags) { + if (!first) + putchar(' '); + printf("%*.1zu", first ? (first = 0) : 6, nl); + } + if (wflag || noflags) { + if (!first) + putchar(' '); + printf("%*.1zu", first ? (first = 0) : 6, nw); + } + if (cmode || noflags) { + if (!first) + putchar(' '); + printf("%*.1zu", first ? (first = 0) : 6, nc); + } + if (str) + printf(" %s", str); + putchar('\n'); +} + +static void +wc(FILE *fp, const char *str) +{ + int word = 0, rlen; + Rune c; + size_t nc = 0, nl = 0, nw = 0; + + while ((rlen = efgetrune(&c, fp, str))) { + nc += (cmode == 'c' || !cmode) ? rlen : (c != Runeerror); + if (c == '\n') + nl++; + if (!isspacerune(c)) + word = 1; + else if (word) { + word = 0; + nw++; + } + } + if (word) + nw++; + tc += nc; + tl += nl; + tw += nw; + output(str, nc, nl, nw); +} + +static void +usage(void) +{ + eprintf("usage: %s [-c | -m] [-lw] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + int many; + int ret = 0; + + 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) { + wc(stdin, NULL); + } else { + for (many = (argc > 1); *argv; argc--, argv++) { + if (!strcmp(*argv, "-")) { + *argv = ""; + fp = stdin; + } else if (!(fp = fopen(*argv, "r"))) { + weprintf("fopen %s:", *argv); + ret = 1; + continue; + } + wc(fp, *argv); + if (fp != stdin && fshut(fp, *argv)) + ret = 1; + } + if (many) + output("total", tc, tl, tw); + } + + ret |= fshut(stdin, "") | fshut(stdout, ""); + + return ret; +} diff --git a/source/sbase/which.1 b/source/sbase/which.1 new file mode 100644 index 00000000..ececa450 --- /dev/null +++ b/source/sbase/which.1 @@ -0,0 +1,44 @@ +.Dd 2015-10-08 +.Dt WHICH 1 +.Os sbase +.Sh NAME +.Nm which +.Nd locate programs in the path +.Sh SYNOPSIS +.Nm +.Op Fl a +.Ar name ... +.Sh DESCRIPTION +.Nm +looks for each +.Ar name +in the +.Ev PATH +directories, stopping at the first match and printing +the full path to stdout. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl a +Don't stop at the first match and search all +.Ev PATH +directories. +.El +.Sh EXIT STATUS +.Bl -tag -width Ds +.It 0 +Each +.Ar name +was found. +.It 1 +At least one +.Ar name +was not found. +.It 2 +No +.Ar name +was found. +.It 3 +An error occurred. +.El +.Sh SEE ALSO +.Xr environ 7 diff --git a/source/sbase/which.c b/source/sbase/which.c new file mode 100644 index 00000000..19757101 --- /dev/null +++ b/source/sbase/which.c @@ -0,0 +1,89 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "util.h" + +static int aflag; + +static int +which(const char *path, const char *name) +{ + char *ptr, *p; + size_t i, len; + struct stat st; + int dirfd, found = 0; + + ptr = p = enstrdup(3, path); + len = strlen(p); + for (i = 0; i < len + 1; i++) { + if (ptr[i] != ':' && ptr[i] != '\0') + continue; + ptr[i] = '\0'; + if ((dirfd = open(p, O_RDONLY, 0)) >= 0) { + if (!fstatat(dirfd, name, &st, 0) && + S_ISREG(st.st_mode) && + !faccessat(dirfd, name, X_OK, 0)) { + found = 1; + fputs(p, stdout); + if (i && ptr[i - 1] != '/') + fputc('/', stdout); + puts(name); + if (!aflag) { + close(dirfd); + break; + } + } + close(dirfd); + } + p = ptr + i + 1; + } + free(ptr); + + return found; +} + +static void +usage(void) +{ + eprintf("usage: %s [-a] name ...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + char *path; + int found = 0, foundall = 1; + + ARGBEGIN { + case 'a': + aflag = 1; + break; + default: + usage(); + } ARGEND + + if (!argc) + usage(); + + if (!(path = getenv("PATH"))) + enprintf(3, "$PATH is not set\n"); + + for (; *argv; argc--, argv++) { + if (which(path, *argv)) { + found = 1; + } else { + weprintf("%s: command not found.\n", *argv); + foundall = 0; + } + } + + return found ? foundall ? 0 : 1 : 2; +} diff --git a/source/sbase/whoami.1 b/source/sbase/whoami.1 new file mode 100644 index 00000000..535c927b --- /dev/null +++ b/source/sbase/whoami.1 @@ -0,0 +1,9 @@ +.Dd 2015-12-14 +.Dt WHOAMI 1 +.Os sbase +.Sh NAME +.Nm whoami +.Nd show effective uid +.Sh SYNOPSIS +.Nm +writes the name of the effective uid to stdout. diff --git a/source/sbase/whoami.c b/source/sbase/whoami.c new file mode 100644 index 00000000..11ee7e2b --- /dev/null +++ b/source/sbase/whoami.c @@ -0,0 +1,37 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "util.h" + +static void +usage(void) +{ + eprintf("usage: %s\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + uid_t uid; + struct passwd *pw; + + argv0 = argv[0], argc--, argv++; + + if (argc) + usage(); + + uid = geteuid(); + errno = 0; + if (!(pw = getpwuid(uid))) { + if (errno) + eprintf("getpwuid %d:", uid); + else + eprintf("getpwuid %d: no such user\n", uid); + } + puts(pw->pw_name); + + return fshut(stdout, ""); +} diff --git a/source/sbase/xargs.1 b/source/sbase/xargs.1 new file mode 100644 index 00000000..47cc1944 --- /dev/null +++ b/source/sbase/xargs.1 @@ -0,0 +1,96 @@ +.Dd 2015-10-08 +.Dt XARGS 1 +.Os sbase +.Sh NAME +.Nm xargs +.Nd construct argument lists and execute command +.Sh SYNOPSIS +.Nm +.Op Fl rtx +.Op Fl E Ar eofstr +.Op Fl n Ar num +.Op Fl s Ar num +.Op Ar cmd Op Ar arg ... +.Sh DESCRIPTION +.Nm +reads space, tab, newline and EOF delimited strings from stdin +and executes the specified +.Ar cmd +with the strings as +.Ar arguments . +.Pp +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. +.Pp +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 +.Bl -tag -width Ds +.It Fl n Ar num +Use at most +.Ar num +arguments per command line. +.It Fl 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. +.It Fl E Ar eofstr +Use +.Ar eofstr +as a logical EOF marker. +.It Fl s Ar num +Use at most +.Ar num +bytes per command line. +.It Fl t +Write the command line to stderr before executing it. +.It Fl x +Terminate if the command line exceeds the system limit or the number of bytes +given with the +.Op Fl s +flag. +.El +.Sh EXIT STATUS +.Nm +exits with one of the following values: +.Bl -tag -width Ds +.It 0 +All invocations of +.Ar cmd +returned a zero exit status. +.It 123 +One or more invocations of +.Ar cmd +returned a nonzero exit status. +.It 124 +.Ar cmd +exited with a 255 exit status. +.It 125 +.Ar cmd +was killed or stopped by a signal. +.It 126 +.Ar cmd +was found but could not be executed. +.It 127 +.Ar cmd +could not be found. +.It 1 +Some other error occurred. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2013 +specification except from the +.Op Fl p +flag. +.Pp +The +.Op Fl r +flag is an extension to that specification. diff --git a/source/sbase/xargs.c b/source/sbase/xargs.c new file mode 100644 index 00000000..0d5dd53a --- /dev/null +++ b/source/sbase/xargs.c @@ -0,0 +1,280 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +#define NARGS 10000 + +static int inputc(void); +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 size_t argbsz; +static size_t argbpos; +static size_t maxargs = 0; +static int nerrors = 0; +static int rflag = 0, nflag = 0, tflag = 0, xflag = 0; +static char *argb; +static char *cmd[NARGS]; +static char *eofstr; + +static int +inputc(void) +{ + int ch; + + ch = getc(stdin); + if (ch == EOF && ferror(stdin)) + eprintf("getc :"); + + return ch; +} + +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: + ungetc(ch, stdin); + 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'); + + return (eofstr && !strcmp(argb, eofstr)) ? NULL : 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) + nerrors++; + } + if (WIFSIGNALED(status)) + exit(125); +} + +static void +spawn(void) +{ + int savederrno; + int first = 1; + char **p; + + if (tflag) { + for (p = cmd; *p; p++) { + if (!first) + fputc(' ', stderr); + fputs(*p, stderr); + first = 0; + } + fputc('\n', stderr); + } + + switch (fork()) { + case -1: + eprintf("fork:"); + case 0: + execvp(*cmd, cmd); + savederrno = errno; + weprintf("execvp %s:", *cmd); + _exit(126 + (savederrno == ENOENT)); + } + waitchld(); +} + +static void +usage(void) +{ + eprintf("usage: %s [-rtx] [-E eofstr] [-n num] [-s num] " + "[cmd [arg ...]]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int ret = 0, leftover = 0, i; + size_t argsz, argmaxsz; + size_t arglen, a; + char *arg = ""; + + if ((argmaxsz = sysconf(_SC_ARG_MAX)) == (size_t)-1) + argmaxsz = _POSIX_ARG_MAX; + /* Leave some room for environment variables */ + argmaxsz -= 4096; + + ARGBEGIN { + case 'n': + nflag = 1; + maxargs = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX)); + break; + case 'r': + rflag = 1; + break; + case 's': + argmaxsz = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX)); + break; + case 't': + tflag = 1; + break; + case 'x': + xflag = 1; + break; + case 'E': + eofstr = EARGF(usage()); + break; + default: + usage(); + } ARGEND + + do { + argsz = 0; i = 0; a = 0; + if (argc) { + for (; i < argc; i++) { + cmd[i] = estrdup(argv[i]); + argsz += strlen(cmd[i]) + 1; + } + } else { + cmd[i] = estrdup("/bin/echo"); + argsz += strlen("/bin/echo") + 1; + i++; + } + while (leftover || (arg = poparg())) { + arglen = strlen(arg); + if (argsz + arglen >= argmaxsz || i >= NARGS - 1) { + if (arglen >= argmaxsz) { + weprintf("insufficient argument space\n"); + if (xflag) + exit(1); + } + leftover = 1; + break; + } + cmd[i] = estrdup(arg); + argsz += arglen + 1; + i++; + a++; + leftover = 0; + if (nflag && a >= maxargs) + break; + } + cmd[i] = NULL; + if (a >= maxargs && nflag) + spawn(); + else if (!a || (i == 1 && rflag)) + ; + else + spawn(); + for (; i >= 0; i--) + free(cmd[i]); + } while (arg); + + free(argb); + + if (nerrors || (fshut(stdin, "") | fshut(stdout, ""))) + ret = 123; + + return ret; +} diff --git a/source/sbase/xinstall.1 b/source/sbase/xinstall.1 new file mode 100644 index 00000000..1a727d3c --- /dev/null +++ b/source/sbase/xinstall.1 @@ -0,0 +1,89 @@ +.Dd 2016-02-12 +.Dt INSTALL 1 +.Os sbase +.Sh NAME +.Nm install +.Nd copy files and set attributes +.Sh SYNOPSIS +.Nm +.Op Fl g Ar group +.Op Fl o Ar owner +.Op Fl m Ar mode +.Po +.Fl d Ar dir ... +| +.Op Fl sD +.Po +.Fl t Ar dest +.Ar source ... +| +.Ar source ... +.Ar dest +.Pc +.Pc +.Sh DESCRIPTION +.Nm +copies +.Ar source +to +.Ar dest . +If more than one +.Ar source +is given +.Ar dest +is treated as a directory. Otherwise +.Ar dest +is treated as a filename. +.Nm +can also change the attributes of the copies. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl d +Create the directories +.Ar dir . +.It Fl D +Create missing parent directories to +.Ar dest . +If +.Ar dest +is to be treated as a directory, it is created too if missing. +.It Fl g Ar group +Change the installed files' group to +.Ar group . +This may be a group name or a group identifier. +.It Fl m Ar mode +Change the file modes. Both numerical and symbolic +values are supported. See +.Xr chmod 1 +for the syntex. +Default mode 0755. If a file has the mode 0644 and +is copied with +.It Fl o Ar owner +Change the installed files' owner to +.Ar owner . +This may be a user name or a user identifier. +.It Fl s +Remove unnecessary symbols using +.Xr strip 1 . +Failure to strip a file does not imply failure to install the file. +.It Fl t Ar dest +Copy files into the directory +.Ar dest . +.Nm install , +the copy's mode will be 0755 unless +.Fl m +is used to select another mode. When the symbolic +notation is used, the base mode is 0000. +.El +.Sh SEE ALSO +.Xr chmod 1 , +.Xr chown 1 , +.Xr cp 1 , +.Xr mkdir 1 , +.Xr strip 1 +.Sh STANDARDS +The +.Nm +utility is not standardized. This implementation is a subset +of the GNU implementation and a subset with extensions to +the FreeBSD implementation. diff --git a/source/sbase/xinstall.c b/source/sbase/xinstall.c new file mode 100644 index 00000000..f5f69566 --- /dev/null +++ b/source/sbase/xinstall.c @@ -0,0 +1,256 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "text.h" + +static int Dflag = 0; +static int sflag = 0; +static gid_t group; +static uid_t owner; +static mode_t mode = 0755; + +static void +make_dir(char *dir, int was_missing) +{ + if (!mkdir(dir, was_missing ? 0755 : mode)) { + if (!was_missing && (lchown(dir, owner, group) < 0)) + eprintf("lchmod %s:", dir); + } else if (errno != EEXIST) { + eprintf("mkdir %s:", dir); + } +} + +static void +make_dirs(char *dir, int was_missing) +{ + char *p; + for (p = strchr(dir + (dir[0] == '/'), '/'); p; p = strchr(p + 1, '/')) { + *p = '\0'; + make_dir(dir, was_missing); + *p = '/'; + } + make_dir(dir, was_missing); +} + +static void +strip(const char *filename) +{ + pid_t pid = fork(); + switch (pid) { + case -1: + eprintf("fork:"); + case 0: + execlp("strip", "strip", "--", filename, (char *)0); + eprintf("exec: strip:"); + default: + waitpid(pid, NULL, 0); + break; + } +} + +static int +install(const char *s1, const char *s2, int depth) +{ + DIR *dp; + FILE *f1, *f2; + struct dirent *d; + struct stat st; + ssize_t r; + char target[PATH_MAX], ns1[PATH_MAX], ns2[PATH_MAX]; + + if (stat(s1, &st) < 0) + eprintf("stat %s:", s1); + + if (S_ISLNK(st.st_mode)) { + if ((r = readlink(s1, target, sizeof(target) - 1)) >= 0) { + target[r] = '\0'; + if (unlink(s2) < 0 && errno != ENOENT) + eprintf("unlink %s:", s2); + else if (symlink(target, s2) < 0) + eprintf("symlink %s -> %s:", s2, target); + } + } else if (S_ISDIR(st.st_mode)) { + if (!(dp = opendir(s1))) + eprintf("opendir %s:", s1); + if (mkdir(s2, mode | 0111) < 0 && errno != EEXIST) + eprintf("mkdir %s:", s2); + + while ((d = readdir(dp))) { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + + estrlcpy(ns1, s1, sizeof(ns1)); + if (s1[strlen(s1) - 1] != '/') + estrlcat(ns1, "/", sizeof(ns1)); + estrlcat(ns1, d->d_name, sizeof(ns1)); + + estrlcpy(ns2, s2, sizeof(ns2)); + if (s2[strlen(s2) - 1] != '/') + estrlcat(ns2, "/", sizeof(ns2)); + estrlcat(ns2, d->d_name, sizeof(ns2)); + + fnck(ns1, ns2, install, depth + 1); + } + + closedir(dp); + } else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode) || + S_ISSOCK(st.st_mode) || S_ISFIFO(st.st_mode)) { + if (unlink(s2) < 0 && errno != ENOENT) + eprintf("unlink %s:", s2); + else if (mknod(s2, (st.st_mode & ~07777) | mode, st.st_rdev) < 0) + eprintf("mknod %s:", s2); + } else { + if (!(f1 = fopen(s1, "r"))) + eprintf("fopen %s:", s1); + if (!(f2 = fopen(s2, "w"))) { + if (unlink(s2) < 0 && errno != ENOENT) + eprintf("unlink %s:", s2); + else if (!(f2 = fopen(s2, "w"))) + eprintf("fopen %s:", s2); + } + concat(f1, s1, f2, s2); + + fchmod(fileno(f2), mode); + + if (fclose(f2) == EOF) + eprintf("fclose %s:", s2); + if (fclose(f1) == EOF) + eprintf("fclose %s:", s1); + + if (sflag) + strip(s2); + } + + if (lchown(s2, owner, group) < 0) + eprintf("lchown %s:", s2); + + return 0; +} + +static void +usage(void) +{ + eprintf("usage: %s [-g group] [-o owner] [-m mode] (-d dir ... | [-Ds] (-t dest source ... | source ... dest))\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int dflag = 0; + char *gflag = 0; + char *oflag = 0; + char *mflag = 0; + char *tflag = 0; + struct group *gr; + struct passwd *pw; + struct stat st; + char *p; + + ARGBEGIN { + case 'd': + dflag = 1; + break; + case 'D': + Dflag = 1; + break; + case 's': + sflag = 1; + break; + case 'g': + gflag = EARGF(usage()); + break; + case 'o': + oflag = EARGF(usage()); + break; + case 'm': + mflag = EARGF(usage()); + break; + case 't': + tflag = EARGF(usage()); + break; + default: + usage(); + } ARGEND + + if (argc < 1 + (!tflag & !dflag) || dflag & (Dflag | sflag | !!tflag)) + usage(); + + if (gflag) { + errno = 0; + gr = getgrnam(gflag); + if (gr) { + group = gr->gr_gid; + } else { + if (errno) + eprintf("getgrnam %s:", gflag); + group = estrtonum(gflag, 0, UINT_MAX); + } + } else { + group = getgid(); + } + + if (oflag) { + errno = 0; + pw = getpwnam(oflag); + if (pw) { + owner = pw->pw_uid; + } else { + if (errno) + eprintf("getpwnam %s:", oflag); + owner = estrtonum(oflag, 0, UINT_MAX); + } + } else { + owner = getuid(); + } + + if (mflag) { + mode = parsemode(mflag, mode, 0); + if (mode < 0) + return 1; + } + + if (tflag) { + memmove(argv - 1, argv, argc); + argv[argc++] = tflag; + } + if (tflag || argc > 2) { + if (stat(argv[argc - 1], &st) < 0) { + if ((errno == ENOENT) && Dflag) { + make_dirs(argv[argc - 1], 1); + } else { + eprintf("stat %s:", argv[argc - 1]); + } + } else if (!S_ISDIR(st.st_mode)) { + eprintf("%s: not a directory\n", argv[argc - 1]); + } + } + + if (dflag) { + for (; *argv; argc--, argv++) + make_dirs(*argv, 0); + } else { + if (stat(argv[argc - 1], &st) < 0) { + if (errno != ENOENT) + eprintf("stat %s:", argv[argc - 1]); + if (tflag || Dflag || argc > 2) { + if ((p = strrchr(argv[argc - 1], '/')) != NULL) { + *p = '\0'; + make_dirs(argv[argc - 1], 1); + *p = '/'; + } + } + } + enmasse(argc, argv, install); + } + + return 0; +} diff --git a/source/sbase/yes.1 b/source/sbase/yes.1 new file mode 100644 index 00000000..87e390af --- /dev/null +++ b/source/sbase/yes.1 @@ -0,0 +1,14 @@ +.Dd 2015-10-08 +.Dt YES 1 +.Os sbase +.Sh NAME +.Nm yes +.Nd output strings repeatedly +.Sh SYNOPSIS +.Nm +.Op Ar string ... +.Sh DESCRIPTION +.Nm +will repeatedly write 'y' or a line with each +.Ar string +to stdout. diff --git a/source/sbase/yes.c b/source/sbase/yes.c new file mode 100644 index 00000000..cbd81dc8 --- /dev/null +++ b/source/sbase/yes.c @@ -0,0 +1,19 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "util.h" + +int +main(int argc, char *argv[]) +{ + char **p; + + argv0 = argv[0], argc--, argv++; + + for (p = argv; ; p = (*p && *(p + 1)) ? p + 1 : argv) { + fputs(*p ? *p : "y", stdout); + putchar((!*p || !*(p + 1)) ? '\n' : ' '); + } + + return 1; /* not reached */ +} diff --git a/source/ubase/Rules.mk b/source/ubase/Rules.mk index fce53698..c14ef9bf 100644 --- a/source/ubase/Rules.mk +++ b/source/ubase/Rules.mk @@ -37,13 +37,11 @@ UBASE_BINS = \ ctrlaltdel \ dd \ df \ - dmesg \ eject \ fallocate \ free \ freeramdisk \ fsfreeze \ - getty \ halt \ hwclock \ id \ @@ -51,7 +49,6 @@ UBASE_BINS = \ killall5 \ last \ lastlog \ - login \ lsmod \ lsusb \ mesg \ -- 2.49.0