]> git.mdlowis.com Git - proto/albase.git/commitdiff
Added sbase rules and source code
authorMike Lowis <mike.lowis@gentex.com>
Tue, 3 May 2016 15:10:18 +0000 (11:10 -0400)
committerMike Lowis <mike.lowis@gentex.com>
Tue, 3 May 2016 15:10:18 +0000 (11:10 -0400)
274 files changed:
Makefile
source/sbase/LICENSE [new file with mode: 0644]
source/sbase/Makefile [new file with mode: 0644]
source/sbase/README [new file with mode: 0644]
source/sbase/Rules.mk [new file with mode: 0644]
source/sbase/TODO [new file with mode: 0644]
source/sbase/arg.h [new file with mode: 0644]
source/sbase/basename.1 [new file with mode: 0644]
source/sbase/basename.c [new file with mode: 0644]
source/sbase/cal.1 [new file with mode: 0644]
source/sbase/cal.c [new file with mode: 0644]
source/sbase/cat.1 [new file with mode: 0644]
source/sbase/cat.c [new file with mode: 0644]
source/sbase/chgrp.1 [new file with mode: 0644]
source/sbase/chgrp.c [new file with mode: 0644]
source/sbase/chmod.1 [new file with mode: 0644]
source/sbase/chmod.c [new file with mode: 0644]
source/sbase/chown.1 [new file with mode: 0644]
source/sbase/chown.c [new file with mode: 0644]
source/sbase/chroot.1 [new file with mode: 0644]
source/sbase/chroot.c [new file with mode: 0644]
source/sbase/cksum.1 [new file with mode: 0644]
source/sbase/cksum.c [new file with mode: 0644]
source/sbase/cmp.1 [new file with mode: 0644]
source/sbase/cmp.c [new file with mode: 0644]
source/sbase/cols.1 [new file with mode: 0644]
source/sbase/cols.c [new file with mode: 0644]
source/sbase/comm.1 [new file with mode: 0644]
source/sbase/comm.c [new file with mode: 0644]
source/sbase/compat.h [new file with mode: 0644]
source/sbase/config.mk [new file with mode: 0644]
source/sbase/confstr_l.h [new file with mode: 0644]
source/sbase/cp.1 [new file with mode: 0644]
source/sbase/cp.c [new file with mode: 0644]
source/sbase/cron.1 [new file with mode: 0644]
source/sbase/cron.c [new file with mode: 0644]
source/sbase/crypt.h [new file with mode: 0644]
source/sbase/cut.1 [new file with mode: 0644]
source/sbase/cut.c [new file with mode: 0644]
source/sbase/date.1 [new file with mode: 0644]
source/sbase/date.c [new file with mode: 0644]
source/sbase/dirname.1 [new file with mode: 0644]
source/sbase/dirname.c [new file with mode: 0644]
source/sbase/du.1 [new file with mode: 0644]
source/sbase/du.c [new file with mode: 0644]
source/sbase/echo.1 [new file with mode: 0644]
source/sbase/echo.c [new file with mode: 0644]
source/sbase/ed.1 [new file with mode: 0644]
source/sbase/ed.c [new file with mode: 0644]
source/sbase/env.1 [new file with mode: 0644]
source/sbase/env.c [new file with mode: 0644]
source/sbase/expand.1 [new file with mode: 0644]
source/sbase/expand.c [new file with mode: 0644]
source/sbase/expr.1 [new file with mode: 0644]
source/sbase/expr.c [new file with mode: 0644]
source/sbase/false.1 [new file with mode: 0644]
source/sbase/false.c [new file with mode: 0644]
source/sbase/find.1 [new file with mode: 0644]
source/sbase/find.c [new file with mode: 0644]
source/sbase/flock.1 [new file with mode: 0644]
source/sbase/flock.c [new file with mode: 0644]
source/sbase/fold.1 [new file with mode: 0644]
source/sbase/fold.c [new file with mode: 0644]
source/sbase/fs.h [new file with mode: 0644]
source/sbase/getconf.1 [new file with mode: 0644]
source/sbase/getconf.c [new file with mode: 0644]
source/sbase/getconf.sh [new file with mode: 0755]
source/sbase/grep.1 [new file with mode: 0644]
source/sbase/grep.c [new file with mode: 0644]
source/sbase/head.1 [new file with mode: 0644]
source/sbase/head.c [new file with mode: 0644]
source/sbase/hostname.1 [new file with mode: 0644]
source/sbase/hostname.c [new file with mode: 0644]
source/sbase/join.1 [new file with mode: 0644]
source/sbase/join.c [new file with mode: 0644]
source/sbase/kill.1 [new file with mode: 0644]
source/sbase/kill.c [new file with mode: 0644]
source/sbase/libutf/Makefile [new file with mode: 0644]
source/sbase/libutf/fgetrune.c [new file with mode: 0644]
source/sbase/libutf/fputrune.c [new file with mode: 0644]
source/sbase/libutf/isalnumrune.c [new file with mode: 0644]
source/sbase/libutf/isalpharune.c [new file with mode: 0644]
source/sbase/libutf/isblankrune.c [new file with mode: 0644]
source/sbase/libutf/iscntrlrune.c [new file with mode: 0644]
source/sbase/libutf/isdigitrune.c [new file with mode: 0644]
source/sbase/libutf/isgraphrune.c [new file with mode: 0644]
source/sbase/libutf/isprintrune.c [new file with mode: 0644]
source/sbase/libutf/ispunctrune.c [new file with mode: 0644]
source/sbase/libutf/isspacerune.c [new file with mode: 0644]
source/sbase/libutf/istitlerune.c [new file with mode: 0644]
source/sbase/libutf/isxdigitrune.c [new file with mode: 0644]
source/sbase/libutf/lowerrune.c [new file with mode: 0644]
source/sbase/libutf/mkrunetype.awk [new file with mode: 0644]
source/sbase/libutf/rune.c [new file with mode: 0644]
source/sbase/libutf/runetype.c [new file with mode: 0644]
source/sbase/libutf/runetype.h [new file with mode: 0644]
source/sbase/libutf/upperrune.c [new file with mode: 0644]
source/sbase/libutf/utf.c [new file with mode: 0644]
source/sbase/libutf/utftorunestr.c [new file with mode: 0644]
source/sbase/libutil/concat.c [new file with mode: 0644]
source/sbase/libutil/cp.c [new file with mode: 0644]
source/sbase/libutil/crypt.c [new file with mode: 0644]
source/sbase/libutil/ealloc.c [new file with mode: 0644]
source/sbase/libutil/enmasse.c [new file with mode: 0644]
source/sbase/libutil/eprintf.c [new file with mode: 0644]
source/sbase/libutil/eregcomp.c [new file with mode: 0644]
source/sbase/libutil/estrtod.c [new file with mode: 0644]
source/sbase/libutil/fnck.c [new file with mode: 0644]
source/sbase/libutil/fshut.c [new file with mode: 0644]
source/sbase/libutil/getlines.c [new file with mode: 0644]
source/sbase/libutil/human.c [new file with mode: 0644]
source/sbase/libutil/linecmp.c [new file with mode: 0644]
source/sbase/libutil/md5.c [new file with mode: 0644]
source/sbase/libutil/memmem.c [new file with mode: 0644]
source/sbase/libutil/mkdirp.c [new file with mode: 0644]
source/sbase/libutil/mode.c [new file with mode: 0644]
source/sbase/libutil/parseoffset.c [new file with mode: 0644]
source/sbase/libutil/putword.c [new file with mode: 0644]
source/sbase/libutil/reallocarray.c [new file with mode: 0644]
source/sbase/libutil/recurse.c [new file with mode: 0644]
source/sbase/libutil/rm.c [new file with mode: 0644]
source/sbase/libutil/sha1.c [new file with mode: 0644]
source/sbase/libutil/sha224.c [new file with mode: 0644]
source/sbase/libutil/sha256.c [new file with mode: 0644]
source/sbase/libutil/sha384.c [new file with mode: 0644]
source/sbase/libutil/sha512-224.c [new file with mode: 0644]
source/sbase/libutil/sha512-256.c [new file with mode: 0644]
source/sbase/libutil/sha512.c [new file with mode: 0644]
source/sbase/libutil/strcasestr.c [new file with mode: 0644]
source/sbase/libutil/strlcat.c [new file with mode: 0644]
source/sbase/libutil/strlcpy.c [new file with mode: 0644]
source/sbase/libutil/strsep.c [new file with mode: 0644]
source/sbase/libutil/strtonum.c [new file with mode: 0644]
source/sbase/libutil/unescape.c [new file with mode: 0644]
source/sbase/limits_l.h [new file with mode: 0644]
source/sbase/link.1 [new file with mode: 0644]
source/sbase/link.c [new file with mode: 0644]
source/sbase/ln.1 [new file with mode: 0644]
source/sbase/ln.c [new file with mode: 0644]
source/sbase/logger.1 [new file with mode: 0644]
source/sbase/logger.c [new file with mode: 0644]
source/sbase/logname.1 [new file with mode: 0644]
source/sbase/logname.c [new file with mode: 0644]
source/sbase/ls.1 [new file with mode: 0644]
source/sbase/ls.c [new file with mode: 0644]
source/sbase/md5.h [new file with mode: 0644]
source/sbase/md5sum.1 [new file with mode: 0644]
source/sbase/md5sum.c [new file with mode: 0644]
source/sbase/mkdir.1 [new file with mode: 0644]
source/sbase/mkdir.c [new file with mode: 0644]
source/sbase/mkfifo.1 [new file with mode: 0644]
source/sbase/mkfifo.c [new file with mode: 0644]
source/sbase/mktemp.1 [new file with mode: 0644]
source/sbase/mktemp.c [new file with mode: 0644]
source/sbase/mv.1 [new file with mode: 0644]
source/sbase/mv.c [new file with mode: 0644]
source/sbase/nice.1 [new file with mode: 0644]
source/sbase/nice.c [new file with mode: 0644]
source/sbase/nl.1 [new file with mode: 0644]
source/sbase/nl.c [new file with mode: 0644]
source/sbase/nohup.1 [new file with mode: 0644]
source/sbase/nohup.c [new file with mode: 0644]
source/sbase/od.1 [new file with mode: 0644]
source/sbase/od.c [new file with mode: 0644]
source/sbase/paste.1 [new file with mode: 0644]
source/sbase/paste.c [new file with mode: 0644]
source/sbase/pathchk.1 [new file with mode: 0644]
source/sbase/pathchk.c [new file with mode: 0644]
source/sbase/pathconf_l.h [new file with mode: 0644]
source/sbase/printenv.1 [new file with mode: 0644]
source/sbase/printenv.c [new file with mode: 0644]
source/sbase/printf.1 [new file with mode: 0644]
source/sbase/printf.c [new file with mode: 0644]
source/sbase/pwd.1 [new file with mode: 0644]
source/sbase/pwd.c [new file with mode: 0644]
source/sbase/queue.h [new file with mode: 0644]
source/sbase/readlink.1 [new file with mode: 0644]
source/sbase/readlink.c [new file with mode: 0644]
source/sbase/renice.1 [new file with mode: 0644]
source/sbase/renice.c [new file with mode: 0644]
source/sbase/rm.1 [new file with mode: 0644]
source/sbase/rm.c [new file with mode: 0644]
source/sbase/rmdir.1 [new file with mode: 0644]
source/sbase/rmdir.c [new file with mode: 0644]
source/sbase/sed.1 [new file with mode: 0644]
source/sbase/sed.c [new file with mode: 0644]
source/sbase/seq.1 [new file with mode: 0644]
source/sbase/seq.c [new file with mode: 0644]
source/sbase/setsid.1 [new file with mode: 0644]
source/sbase/setsid.c [new file with mode: 0644]
source/sbase/sha1.h [new file with mode: 0644]
source/sbase/sha1sum.1 [new file with mode: 0644]
source/sbase/sha1sum.c [new file with mode: 0644]
source/sbase/sha224.h [new file with mode: 0644]
source/sbase/sha224sum.1 [new file with mode: 0644]
source/sbase/sha224sum.c [new file with mode: 0644]
source/sbase/sha256.h [new file with mode: 0644]
source/sbase/sha256sum.1 [new file with mode: 0644]
source/sbase/sha256sum.c [new file with mode: 0644]
source/sbase/sha384.h [new file with mode: 0644]
source/sbase/sha384sum.1 [new file with mode: 0644]
source/sbase/sha384sum.c [new file with mode: 0644]
source/sbase/sha512-224.h [new file with mode: 0644]
source/sbase/sha512-224sum.1 [new file with mode: 0644]
source/sbase/sha512-224sum.c [new file with mode: 0644]
source/sbase/sha512-256.h [new file with mode: 0644]
source/sbase/sha512-256sum.1 [new file with mode: 0644]
source/sbase/sha512-256sum.c [new file with mode: 0644]
source/sbase/sha512.h [new file with mode: 0644]
source/sbase/sha512sum.1 [new file with mode: 0644]
source/sbase/sha512sum.c [new file with mode: 0644]
source/sbase/sleep.1 [new file with mode: 0644]
source/sbase/sleep.c [new file with mode: 0644]
source/sbase/sort.1 [new file with mode: 0644]
source/sbase/sort.c [new file with mode: 0644]
source/sbase/split.1 [new file with mode: 0644]
source/sbase/split.c [new file with mode: 0644]
source/sbase/sponge.1 [new file with mode: 0644]
source/sbase/sponge.c [new file with mode: 0644]
source/sbase/strings.1 [new file with mode: 0644]
source/sbase/strings.c [new file with mode: 0644]
source/sbase/sync.1 [new file with mode: 0644]
source/sbase/sync.c [new file with mode: 0644]
source/sbase/sysconf_l.h [new file with mode: 0644]
source/sbase/tail.1 [new file with mode: 0644]
source/sbase/tail.c [new file with mode: 0644]
source/sbase/tar.1 [new file with mode: 0644]
source/sbase/tar.c [new file with mode: 0644]
source/sbase/tee.1 [new file with mode: 0644]
source/sbase/tee.c [new file with mode: 0644]
source/sbase/test.1 [new file with mode: 0644]
source/sbase/test.c [new file with mode: 0644]
source/sbase/text.h [new file with mode: 0644]
source/sbase/tftp.1 [new file with mode: 0644]
source/sbase/tftp.c [new file with mode: 0644]
source/sbase/time.1 [new file with mode: 0644]
source/sbase/time.c [new file with mode: 0644]
source/sbase/touch.1 [new file with mode: 0644]
source/sbase/touch.c [new file with mode: 0644]
source/sbase/tr.1 [new file with mode: 0644]
source/sbase/tr.c [new file with mode: 0644]
source/sbase/true.1 [new file with mode: 0644]
source/sbase/true.c [new file with mode: 0644]
source/sbase/tsort.1 [new file with mode: 0644]
source/sbase/tsort.c [new file with mode: 0644]
source/sbase/tty.1 [new file with mode: 0644]
source/sbase/tty.c [new file with mode: 0644]
source/sbase/uname.1 [new file with mode: 0644]
source/sbase/uname.c [new file with mode: 0644]
source/sbase/unexpand.1 [new file with mode: 0644]
source/sbase/unexpand.c [new file with mode: 0644]
source/sbase/uniq.1 [new file with mode: 0644]
source/sbase/uniq.c [new file with mode: 0644]
source/sbase/unlink.1 [new file with mode: 0644]
source/sbase/unlink.c [new file with mode: 0644]
source/sbase/utf.h [new file with mode: 0644]
source/sbase/util.h [new file with mode: 0644]
source/sbase/uudecode.1 [new file with mode: 0644]
source/sbase/uudecode.c [new file with mode: 0644]
source/sbase/uuencode.1 [new file with mode: 0644]
source/sbase/uuencode.c [new file with mode: 0644]
source/sbase/wc.1 [new file with mode: 0644]
source/sbase/wc.c [new file with mode: 0644]
source/sbase/which.1 [new file with mode: 0644]
source/sbase/which.c [new file with mode: 0644]
source/sbase/whoami.1 [new file with mode: 0644]
source/sbase/whoami.c [new file with mode: 0644]
source/sbase/xargs.1 [new file with mode: 0644]
source/sbase/xargs.c [new file with mode: 0644]
source/sbase/xinstall.1 [new file with mode: 0644]
source/sbase/xinstall.c [new file with mode: 0644]
source/sbase/yes.1 [new file with mode: 0644]
source/sbase/yes.c [new file with mode: 0644]
source/ubase/Rules.mk

index 9c6904ce33ec42d7eb8f299477c36af9155d71e1..d0de9ea80f346282e659a8c344a77fbd32ddca6b 100644 (file)
--- 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 (file)
index 0000000..1ff215b
--- /dev/null
@@ -0,0 +1,63 @@
+MIT/X Consortium License
+
+© 2011 Connor Lane Smith <cls@lubutu.com>
+© 2011-2016 Dimitris Papastamos <sin@2f30.org>
+© 2014-2016 Laslo Hunhold <dev@frign.de>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+Authors/contributors include:
+
+© 2011 Kamil Cholewiński <harry666t@gmail.com>
+© 2011 Rob Pilling <robpilling@gmail.com>
+© 2011 Hiltjo Posthuma <hiltjo@codemadness.org>
+© 2011 pancake <pancake@youterm.com>
+© 2011 Random832 <random832@fastmail.us>
+© 2012 William Haddon <william@haddonthethird.net>
+© 2012 Kurt H. Maier <khm@intma.in>
+© 2012 Christoph Lohmann <20h@r-36.net>
+© 2012 David Galos <galosd83@students.rowan.edu>
+© 2012 Robert Ransom <rransom.8774@gmail.com>
+© 2013 Jakob Kramer <jakob.kramer@gmx.de>
+© 2013 Anselm R Garbe <anselm@garbe.us>
+© 2013 Truls Becken <truls.becken@gmail.com>
+© 2013 dsp <dsp@2f30.org>
+© 2013 Markus Teich <markus.teich@stusta.mhn.de>
+© 2013 Jesse Ogle <jesse.p.ogle@gmail.com>
+© 2013 Lorenzo Cogotti <miciamail@hotmail.it>
+© 2013 Federico G. Benavento <benavento@gmail.com>
+© 2013 Roberto E. Vargas Caballero <k0ga@shike2.com>
+© 2013 Christian Hesse <mail@eworm.de>
+© 2013 Markus Wichmann <nullplan@gmx.net>
+© 2014 Silvan Jegen <s.jegen@gmail.com>
+© 2014 Daniel Bainton <dpb@driftaway.org>
+© 2014 Tuukka Kataja <stuge@xor.fi>
+© 2014 Jeffrey Picard <jeff@jeffreypicard.com>
+© 2014 Evan Gates <evan.gates@gmail.com>
+© 2014 Michael Forney <mforney@mforney.org>
+© 2014 Ari Malinen <ari.malinen@gmail.com>
+© 2014 Brandon Mulcahy <brandon@jangler.info>
+© 2014 Adria Garriga <rhaps0dy@installgentoo.com>
+© 2014-2015 Greg Reagle <greg.reagle@umbc.edu>
+© 2015 Tai Chi Minh Ralph Eastwood <tcmreastwood@gmail.com>
+© 2015 Quentin Rameau <quinq@quinq.eu.org>
+© 2015 Dionysis Grigoropoulos <info@erethon.com>
+© 2015 Wolfgang Corcoran-Mathe <first.lord.of.teal@gmail.com>
+© 2016 Mattias Andrée <maandree@kth.se>
+© 2016 Eivind Uggedal <eivind@uggedal.com>
diff --git a/source/sbase/Makefile b/source/sbase/Makefile
new file mode 100644 (file)
index 0000000..6b2bfdf
--- /dev/null
@@ -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 <libgen.h>'                                                                                                     > build/$@.c
+       echo '#include <stdio.h>'                                                                                                     >> build/$@.c
+       echo '#include <stdlib.h>'                                                                                                    >> build/$@.c
+       echo '#include <string.h>'                                                                                                    >> build/$@.c
+       echo '#include "util.h"'                                                                                                      >> build/$@.c
+       for f in $(SRC); do echo "int $$(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 (file)
index 0000000..d60d8fc
--- /dev/null
@@ -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 (file)
index 0000000..60784f0
--- /dev/null
@@ -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 (file)
index 0000000..225034a
--- /dev/null
@@ -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 (file)
index 0000000..0b23c53
--- /dev/null
@@ -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 (file)
index 0000000..27ee983
--- /dev/null
@@ -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 (file)
index 0000000..4a6ac2a
--- /dev/null
@@ -0,0 +1,34 @@
+/* See LICENSE file for copyright and license details. */
+#include <libgen.h>
+#include <stdio.h>
+#include <string.h>
+
+#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, "<stdout>");
+}
diff --git a/source/sbase/cal.1 b/source/sbase/cal.1
new file mode 100644 (file)
index 0000000..38f862d
--- /dev/null
@@ -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 (file)
index 0000000..b403d00
--- /dev/null
@@ -0,0 +1,223 @@
+/* See LICENSE file for copyright and license details. */
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "util.h"
+
+enum { JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };
+enum caltype { JULIAN, GREGORIAN };
+enum { TRANS_YEAR = 1752, TRANS_MONTH = SEP, TRANS_DAY = 2 };
+
+static 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, "<stdout>");
+}
diff --git a/source/sbase/cat.1 b/source/sbase/cat.1
new file mode 100644 (file)
index 0000000..9b67e8f
--- /dev/null
@@ -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 (file)
index 0000000..e3741aa
--- /dev/null
@@ -0,0 +1,61 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#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, "<stdin>", stdout, "<stdout>");
+       } else {
+               for (; *argv; argc--, argv++) {
+                       if (!strcmp(*argv, "-")) {
+                               *argv = "<stdin>";
+                               fp = stdin;
+                       } else if (!(fp = fopen(*argv, "r"))) {
+                               weprintf("fopen %s:", *argv);
+                               ret = 1;
+                               continue;
+                       }
+                       cat(fp, *argv, stdout, "<stdout>");
+                       if (fp != stdin && fshut(fp, *argv))
+                               ret = 1;
+               }
+       }
+
+       ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/chgrp.1 b/source/sbase/chgrp.1
new file mode 100644 (file)
index 0000000..5efa2e7
--- /dev/null
@@ -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 (file)
index 0000000..299238b
--- /dev/null
@@ -0,0 +1,82 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <grp.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..4805ce0
--- /dev/null
@@ -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 (file)
index 0000000..a0d96f1
--- /dev/null
@@ -0,0 +1,82 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#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 (file)
index 0000000..d2bebca
--- /dev/null
@@ -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 (file)
index 0000000..2009507
--- /dev/null
@@ -0,0 +1,110 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <grp.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..de8761f
--- /dev/null
@@ -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 (file)
index 0000000..0b797cc
--- /dev/null
@@ -0,0 +1,46 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+       eprintf("usage: %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 (file)
index 0000000..5f99414
--- /dev/null
@@ -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 (file)
index 0000000..570ca81
--- /dev/null
@@ -0,0 +1,120 @@
+/* See LICENSE file for copyright and license details. */
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#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 : "<stdin>");
+               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 = "<stdin>";
+                               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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/cmp.1 b/source/sbase/cmp.1
new file mode 100644 (file)
index 0000000..f613f26
--- /dev/null
@@ -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 (file)
index 0000000..ea8902f
--- /dev/null
@@ -0,0 +1,82 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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] = "<stdin>";
+                       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, "<stdout>"))
+               ret = 2;
+
+       return ret;
+}
diff --git a/source/sbase/cols.1 b/source/sbase/cols.1
new file mode 100644 (file)
index 0000000..f362865
--- /dev/null
@@ -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 (file)
index 0000000..428cd79
--- /dev/null
@@ -0,0 +1,98 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/ioctl.h>
+
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 = "<stdin>";
+                               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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/comm.1 b/source/sbase/comm.1
new file mode 100644 (file)
index 0000000..3bb84a1
--- /dev/null
@@ -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 (file)
index 0000000..fbd50d9
--- /dev/null
@@ -0,0 +1,97 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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] = "<stdin>";
+                       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, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/compat.h b/source/sbase/compat.h
new file mode 100644 (file)
index 0000000..e2154a6
--- /dev/null
@@ -0,0 +1,6 @@
+/* See LICENSE file for copyright and license details. */
+#include <limits.h>
+
+#ifndef HOST_NAME_MAX
+#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
+#endif
diff --git a/source/sbase/config.mk b/source/sbase/config.mk
new file mode 100644 (file)
index 0000000..9fb18da
--- /dev/null
@@ -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 (file)
index 0000000..ccca383
--- /dev/null
@@ -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 (file)
index 0000000..54126e2
--- /dev/null
@@ -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 (file)
index 0000000..d87e77e
--- /dev/null
@@ -0,0 +1,57 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#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, "<stdout>") || cp_status;
+}
diff --git a/source/sbase/cron.1 b/source/sbase/cron.1
new file mode 100644 (file)
index 0000000..4553b46
--- /dev/null
@@ -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 (file)
index 0000000..77304cc
--- /dev/null
@@ -0,0 +1,566 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..e0cc08d
--- /dev/null
@@ -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 (file)
index 0000000..50825b7
--- /dev/null
@@ -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 (file)
index 0000000..a50bdcb
--- /dev/null
@@ -0,0 +1,215 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <stdout>:");
+                       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, "<stdin>");
+       else {
+               for (; *argv; argc--, argv++) {
+                       if (!strcmp(*argv, "-")) {
+                               *argv = "<stdin>";
+                               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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/date.1 b/source/sbase/date.1
new file mode 100644 (file)
index 0000000..29081a5
--- /dev/null
@@ -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 (file)
index 0000000..1671e1f
--- /dev/null
@@ -0,0 +1,49 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#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, "<stdout>");
+}
diff --git a/source/sbase/dirname.1 b/source/sbase/dirname.1
new file mode 100644 (file)
index 0000000..7904d3b
--- /dev/null
@@ -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 (file)
index 0000000..4bef9a4
--- /dev/null
@@ -0,0 +1,24 @@
+/* See LICENSE file for copyright and license details. */
+#include <libgen.h>
+#include <stdio.h>
+
+#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, "<stdout>");
+}
diff --git a/source/sbase/du.1 b/source/sbase/du.1
new file mode 100644 (file)
index 0000000..ec3d9b3
--- /dev/null
@@ -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 (file)
index 0000000..3dc3545
--- /dev/null
@@ -0,0 +1,115 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#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, "<stdout>") || recurse_status;
+}
diff --git a/source/sbase/echo.1 b/source/sbase/echo.1
new file mode 100644 (file)
index 0000000..2d7a28a
--- /dev/null
@@ -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 (file)
index 0000000..23bc55f
--- /dev/null
@@ -0,0 +1,24 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <string.h>
+#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, "<stdout>");
+}
diff --git a/source/sbase/ed.1 b/source/sbase/ed.1
new file mode 100644 (file)
index 0000000..93e3012
--- /dev/null
@@ -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 (file)
index 0000000..ce19cf7
--- /dev/null
@@ -0,0 +1,1424 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <regex.h>
+#include <unistd.h>
+
+#include <ctype.h>
+#include <limits.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 (file)
index 0000000..57fa518
--- /dev/null
@@ -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 (file)
index 0000000..5d7e8a5
--- /dev/null
@@ -0,0 +1,49 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+extern char **environ;
+
+static void
+usage(void)
+{
+       eprintf("usage: %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, "<stdout>");
+}
diff --git a/source/sbase/expand.1 b/source/sbase/expand.1
new file mode 100644 (file)
index 0000000..6dbf421
--- /dev/null
@@ -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 (file)
index 0000000..f534134
--- /dev/null
@@ -0,0 +1,131 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, "<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>", stdin);
+       } else {
+               for (; *argv; argc--, argv++) {
+                       if (!strcmp(*argv, "-")) {
+                               *argv = "<stdin>";
+                               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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/expr.1 b/source/sbase/expr.1
new file mode 100644 (file)
index 0000000..104b694
--- /dev/null
@@ -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 (file)
index 0000000..4570b33
--- /dev/null
@@ -0,0 +1,268 @@
+/* See LICENSE file for copyright and license details. */
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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("><!", s[0]) && s[1] == '=' && !s[2]) {
+               /* two-char operand */
+               type = (s[0] == '>') ? 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, "<stdout>"))
+               ret = 3;
+
+       return ret;
+}
diff --git a/source/sbase/false.1 b/source/sbase/false.1
new file mode 100644 (file)
index 0000000..12cd80b
--- /dev/null
@@ -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 (file)
index 0000000..fce3fd9
--- /dev/null
@@ -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 (file)
index 0000000..8c033b2
--- /dev/null
@@ -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 (file)
index 0000000..ad0c731
--- /dev/null
@@ -0,0 +1,1044 @@
+/* See LICENSE file for copyright and license details. */
+#include <dirent.h>
+#include <fnmatch.h>
+#include <grp.h>
+#include <libgen.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return gflags.ret;
+}
diff --git a/source/sbase/flock.1 b/source/sbase/flock.1
new file mode 100644 (file)
index 0000000..84a0098
--- /dev/null
@@ -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 (file)
index 0000000..fc2b6ed
--- /dev/null
@@ -0,0 +1,82 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/file.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..189149e
--- /dev/null
@@ -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 (file)
index 0000000..a5a987d
--- /dev/null
@@ -0,0 +1,119 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <stdout>:");
+                       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, "<stdin>");
+       } else {
+               for (; *argv; argc--, argv++) {
+                       if (!strcmp(*argv, "-")) {
+                               *argv = "<stdin>";
+                               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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/fs.h b/source/sbase/fs.h
new file mode 100644 (file)
index 0000000..15ae5f4
--- /dev/null
@@ -0,0 +1,43 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+#include <sys/types.h>
+
+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 (file)
index 0000000..8fcf960
--- /dev/null
@@ -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 (file)
index 0000000..94bff80
--- /dev/null
@@ -0,0 +1,120 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <unistd.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 (executable)
index 0000000..cca5873
--- /dev/null
@@ -0,0 +1,220 @@
+#!/bin/sh
+
+ifdef()
+{
+ awk '{printf("#ifdef %s\n"\
+              "\t{\"%s\",\t%s},\n"\
+              "#endif\n", $2, $1, $2)}' > $1
+}
+
+
+cat <<! | ifdef confstr_l.h
+PATH                           _CS_PATH
+POSIX_V7_ILP32_OFF32_CFLAGS    _CS_POSIX_V7_ILP32_OFF32_CFLAGS
+POSIX_V7_ILP32_OFF32_LDFLAGS   _CS_POSIX_V7_ILP32_OFF32_LDFLAGS
+POSIX_V7_ILP32_OFF32_LIBS      _CS_POSIX_V7_ILP32_OFF32_LIBS
+POSIX_V7_ILP32_OFFBIG_CFLAGS   _CS_POSIX_V7_ILP32_OFFBIG_CFLAGS
+POSIX_V7_ILP32_OFFBIG_LDFLAGS  _CS_POSIX_V7_ILP32_OFFBIG_LDFLAGS
+POSIX_V7_ILP32_OFFBIG_LIBS     _CS_POSIX_V7_ILP32_OFFBIG_LIBS
+POSIX_V7_LP64_OFF64_CFLAGS     _CS_POSIX_V7_LP64_OFF64_CFLAGS
+POSIX_V7_LP64_OFF64_LDFLAGS    _CS_POSIX_V7_LP64_OFF64_LDFLAGS
+POSIX_V7_LP64_OFF64_LIBS       _CS_POSIX_V7_LP64_OFF64_LIBS
+POSIX_V7_LPBIG_OFFBIG_CFLAGS   _CS_POSIX_V7_LPBIG_OFFBIG_CFLAGS
+POSIX_V7_LPBIG_OFFBIG_LDFLAGS  _CS_POSIX_V7_LPBIG_OFFBIG_LDFLAGS
+POSIX_V7_LPBIG_OFFBIG_LIBS     _CS_POSIX_V7_LPBIG_OFFBIG_LIBS
+POSIX_V7_THREADS_CFLAGS        _CS_POSIX_V7_THREADS_CFLAGS
+POSIX_V7_THREADS_LDFLAGS       _CS_POSIX_V7_THREADS_LDFLAGS
+POSIX_V7_WIDTH_RESTRICTED_ENVS _CS_POSIX_V7_WIDTH_RESTRICTED_ENVS
+V7_ENV                         _CS_V7_ENV
+!
+
+cat <<! | ifdef limits_l.h
+_POSIX_CLOCKRES_MIN                 _POSIX_CLOCKRES_MIN
+_POSIX_AIO_LISTIO_MAX               _POSIX_AIO_LISTIO_MAX
+_POSIX_AIO_MAX                      _POSIX_AIO_MAX
+_POSIX_ARG_MAX                      _POSIX_ARG_MAX
+_POSIX_CHILD_MAX                    _POSIX_CHILD_MAX
+_POSIX_DELAYTIMER_MAX               _POSIX_DELAYTIMER_MAX
+_POSIX_HOST_NAME_MAX                _POSIX_HOST_NAME_MAX
+_POSIX_LINK_MAX                     _POSIX_LINK_MAX
+_POSIX_LOGIN_NAME_MAX               _POSIX_LOGIN_NAME_MAX
+_POSIX_MAX_CANON                    _POSIX_MAX_CANON
+_POSIX_MAX_INPUT                    _POSIX_MAX_INPUT
+_POSIX_MQ_OPEN_MAX                  _POSIX_MQ_OPEN_MAX
+_POSIX_MQ_PRIO_MAX                  _POSIX_MQ_PRIO_MAX
+_POSIX_NAME_MAX                     _POSIX_NAME_MAX
+_POSIX_NGROUPS_MAX                  _POSIX_NGROUPS_MAX
+_POSIX_OPEN_MAX                     _POSIX_OPEN_MAX
+_POSIX_PATH_MAX                     _POSIX_PATH_MAX
+_POSIX_PIPE_BUF                     _POSIX_PIPE_BUF
+_POSIX_RE_DUP_MAX                   _POSIX_RE_DUP_MAX
+_POSIX_RTSIG_MAX                    _POSIX_RTSIG_MAX
+_POSIX_SEM_NSEMS_MAX                _POSIX_SEM_NSEMS_MAX
+_POSIX_SEM_VALUE_MAX                _POSIX_SEM_VALUE_MAX
+_POSIX_SIGQUEUE_MAX                 _POSIX_SIGQUEUE_MAX
+_POSIX_SSIZE_MAX                    _POSIX_SSIZE_MAX
+_POSIX_SS_REPL_MAX                  _POSIX_SS_REPL_MAX
+_POSIX_STREAM_MAX                   _POSIX_STREAM_MAX
+_POSIX_SYMLINK_MAX                  _POSIX_SYMLINK_MAX
+_POSIX_SYMLOOP_MAX                  _POSIX_SYMLOOP_MAX
+_POSIX_THREAD_DESTRUCTOR_ITERATIONS _POSIX_THREAD_DESTRUCTOR_ITERATIONS
+_POSIX_THREAD_KEYS_MAX              _POSIX_THREAD_KEYS_MAX
+_POSIX_THREAD_THREADS_MAX           _POSIX_THREAD_THREADS_MAX
+_POSIX_TIMER_MAX                    _POSIX_TIMER_MAX
+_POSIX_TTY_NAME_MAX                 _POSIX_TTY_NAME_MAX
+_POSIX_TZNAME_MAX                   _POSIX_TZNAME_MAX
+_POSIX2_BC_BASE_MAX                 _POSIX2_BC_BASE_MAX
+_POSIX2_BC_DIM_MAX                  _POSIX2_BC_DIM_MAX
+_POSIX2_BC_SCALE_MAX                _POSIX2_BC_SCALE_MAX
+_POSIX2_BC_STRING_MAX               _POSIX2_BC_STRING_MAX
+_POSIX2_CHARCLASS_NAME_MAX          _POSIX2_CHARCLASS_NAME_MAX
+_POSIX2_COLL_WEIGHTS_MAX            _POSIX2_COLL_WEIGHTS_MAX
+_POSIX2_EXPR_NEST_MAX               _POSIX2_EXPR_NEST_MAX
+_POSIX2_LINE_MAX                    _POSIX2_LINE_MAX
+_POSIX2_RE_DUP_MAX                  _POSIX2_RE_DUP_MAX
+!
+
+cat <<! | ifdef sysconf_l.h
+AIO_LISTIO_MAX                    _SC_AIO_LISTIO_MAX
+AIO_MAX                           _SC_AIO_MAX
+AIO_PRIO_DELTA_MAX                _SC_AIO_PRIO_DELTA_MAX
+ARG_MAX                           _SC_ARG_MAX
+ATEXIT_MAX                        _SC_ATEXIT_MAX
+BC_BASE_MAX                       _SC_BC_BASE_MAX
+BC_DIM_MAX                        _SC_BC_DIM_MAX
+BC_SCALE_MAX                      _SC_BC_SCALE_MAX
+BC_STRING_MAX                     _SC_BC_STRING_MAX
+CHILD_MAX                         _SC_CHILD_MAX
+COLL_WEIGHTS_MAX                  _SC_COLL_WEIGHTS_MAX
+DELAYTIMER_MAX                    _SC_DELAYTIMER_MAX
+EXPR_NEST_MAX                     _SC_EXPR_NEST_MAX
+HOST_NAME_MAX                     _SC_HOST_NAME_MAX
+IOV_MAX                           _SC_IOV_MAX
+LINE_MAX                          _SC_LINE_MAX
+LOGIN_NAME_MAX                    _SC_LOGIN_NAME_MAX
+NGROUPS_MAX                       _SC_NGROUPS_MAX
+MQ_OPEN_MAX                       _SC_MQ_OPEN_MAX
+MQ_PRIO_MAX                       _SC_MQ_PRIO_MAX
+OPEN_MAX                          _SC_OPEN_MAX
+_POSIX_ADVISORY_INFO              _SC_ADVISORY_INFO
+_POSIX_BARRIERS                   _SC_BARRIERS
+_POSIX_ASYNCHRONOUS_IO            _SC_ASYNCHRONOUS_IO
+_POSIX_CLOCK_SELECTION            _SC_CLOCK_SELECTION
+_POSIX_CPUTIME                    _SC_CPUTIME
+_POSIX_FSYNC                      _SC_FSYNC
+_POSIX_IPV6                       _SC_IPV6
+_POSIX_JOB_CONTROL                _SC_JOB_CONTROL
+_POSIX_MAPPED_FILES               _SC_MAPPED_FILES
+_POSIX_MEMLOCK                    _SC_MEMLOCK
+_POSIX_MEMLOCK_RANGE              _SC_MEMLOCK_RANGE
+_POSIX_MEMORY_PROTECTION          _SC_MEMORY_PROTECTION
+_POSIX_MESSAGE_PASSING            _SC_MESSAGE_PASSING
+_POSIX_MONOTONIC_CLOCK            _SC_MONOTONIC_CLOCK
+_POSIX_PRIORITIZED_IO             _SC_PRIORITIZED_IO
+_POSIX_PRIORITY_SCHEDULING        _SC_PRIORITY_SCHEDULING
+_POSIX_RAW_SOCKETS                _SC_RAW_SOCKETS
+_POSIX_READER_WRITER_LOCKS        _SC_READER_WRITER_LOCKS
+_POSIX_REALTIME_SIGNALS           _SC_REALTIME_SIGNALS
+_POSIX_REGEXP                     _SC_REGEXP
+_POSIX_SAVED_IDS                  _SC_SAVED_IDS
+_POSIX_SEMAPHORES                 _SC_SEMAPHORES
+_POSIX_SHARED_MEMORY_OBJECTS      _SC_SHARED_MEMORY_OBJECTS
+_POSIX_SHELL                      _SC_SHELL
+_POSIX_SPAWN                      _SC_SPAWN
+_POSIX_SPIN_LOCKS                 _SC_SPIN_LOCKS
+_POSIX_SPORADIC_SERVER            _SC_SPORADIC_SERVER
+_POSIX_SS_REPL_MAX                _SC_SS_REPL_MAX
+_POSIX_SYNCHRONIZED_IO            _SC_SYNCHRONIZED_IO
+_POSIX_THREAD_ATTR_STACKADDR      _SC_THREAD_ATTR_STACKADDR
+_POSIX_THREAD_ATTR_STACKSIZE      _SC_THREAD_ATTR_STACKSIZE
+_POSIX_THREAD_CPUTIME             _SC_THREAD_CPUTIME
+_POSIX_THREAD_PRIO_INHERIT        _SC_THREAD_PRIO_INHERIT
+_POSIX_THREAD_PRIO_PROTECT        _SC_THREAD_PRIO_PROTECT
+_POSIX_THREAD_PRIORITY_SCHEDULING _SC_THREAD_PRIORITY_SCHEDULING
+_POSIX_THREAD_PROCESS_SHARED      _SC_THREAD_PROCESS_SHARED
+_POSIX_THREAD_ROBUST_PRIO_INHERIT _SC_THREAD_ROBUST_PRIO_INHERIT
+_POSIX_THREAD_ROBUST_PRIO_PROTECT _SC_THREAD_ROBUST_PRIO_PROTECT
+_POSIX_THREAD_SAFE_FUNCTIONS      _SC_THREAD_SAFE_FUNCTIONS
+_POSIX_THREAD_SPORADIC_SERVER     _SC_THREAD_SPORADIC_SERVER
+_POSIX_THREADS                    _SC_THREADS
+_POSIX_TIMEOUTS                   _SC_TIMEOUTS
+_POSIX_TIMERS                     _SC_TIMERS
+_POSIX_TRACE                      _SC_TRACE
+_POSIX_TRACE_EVENT_FILTER         _SC_TRACE_EVENT_FILTER
+_POSIX_TRACE_EVENT_NAME_MAX       _SC_TRACE_EVENT_NAME_MAX
+_POSIX_TRACE_INHERIT              _SC_TRACE_INHERIT
+_POSIX_TRACE_LOG                  _SC_TRACE_LOG
+_POSIX_TRACE_NAME_MAX             _SC_TRACE_NAME_MAX
+_POSIX_TRACE_SYS_MAX              _SC_TRACE_SYS_MAX
+_POSIX_TRACE_USER_EVENT_MAX       _SC_TRACE_USER_EVENT_MAX
+_POSIX_TYPED_MEMORY_OBJECTS       _SC_TYPED_MEMORY_OBJECTS
+_POSIX_VERSION                    _SC_VERSION
+_POSIX_V7_ILP32_OFF32             _SC_V7_ILP32_OFF32
+_POSIX_V7_ILP32_OFFBIG            _SC_V7_ILP32_OFFBIG
+_POSIX_V7_LP64_OFF64              _SC_V7_LP64_OFF64
+_POSIX_V7_LPBIG_OFFBIG            _SC_V7_LPBIG_OFFBIG
+_POSIX2_C_BIND                    _SC_2_C_BIND
+_POSIX2_C_DEV                     _SC_2_C_DEV
+_POSIX2_CHAR_TERM                 _SC_2_CHAR_TERM
+_POSIX2_FORT_DEV                  _SC_2_FORT_DEV
+_POSIX2_FORT_RUN                  _SC_2_FORT_RUN
+_POSIX2_LOCALEDEF                 _SC_2_LOCALEDEF
+_POSIX2_PBS                       _SC_2_PBS
+_POSIX2_PBS_ACCOUNTING            _SC_2_PBS_ACCOUNTING
+_POSIX2_PBS_CHECKPOINT            _SC_2_PBS_CHECKPOINT
+_POSIX2_PBS_LOCATE                _SC_2_PBS_LOCATE
+_POSIX2_PBS_MESSAGE               _SC_2_PBS_MESSAGE
+_POSIX2_PBS_TRACK                 _SC_2_PBS_TRACK
+_POSIX2_SW_DEV                    _SC_2_SW_DEV
+_POSIX2_UPE                       _SC_2_UPE
+_POSIX2_VERSION                   _SC_2_VERSION
+PAGE_SIZE                         _SC_PAGE_SIZE
+PAGESIZE                          _SC_PAGESIZE
+PTHREAD_DESTRUCTOR_ITERATIONS     _SC_THREAD_DESTRUCTOR_ITERATIONS
+PTHREAD_KEYS_MAX                  _SC_THREAD_KEYS_MAX
+PTHREAD_STACK_MIN                 _SC_THREAD_STACK_MIN
+PTHREAD_THREADS_MAX               _SC_THREAD_THREADS_MAX
+RE_DUP_MAX                        _SC_RE_DUP_MAX
+RTSIG_MAX                         _SC_RTSIG_MAX
+SEM_NSEMS_MAX                     _SC_SEM_NSEMS_MAX
+SEM_VALUE_MAX                     _SC_SEM_VALUE_MAX
+SIGQUEUE_MAX                      _SC_SIGQUEUE_MAX
+STREAM_MAX                        _SC_STREAM_MAX
+SYMLOOP_MAX                       _SC_SYMLOOP_MAX
+TIMER_MAX                         _SC_TIMER_MAX
+TTY_NAME_MAX                      _SC_TTY_NAME_MAX
+TZNAME_MAX                        _SC_TZNAME_MAX
+_XOPEN_CRYPT                      _SC_XOPEN_CRYPT
+_XOPEN_ENH_I18N                   _SC_XOPEN_ENH_I18N
+_XOPEN_REALTIME                   _SC_XOPEN_REALTIME
+_XOPEN_REALTIME_THREADS           _SC_XOPEN_REALTIME_THREADS
+_XOPEN_SHM                        _SC_XOPEN_SHM
+_XOPEN_STREAMS                    _SC_XOPEN_STREAMS
+_XOPEN_UNIX                       _SC_XOPEN_UNIX
+_XOPEN_UUCP                       _SC_XOPEN_UUCP
+_XOPEN_VERSION                    _SC_XOPEN_VERSION
+!
+
+cat <<! | ifdef pathconf_l.h
+FILESIZEBITS                _PC_FILESIZEBITS
+LINK_MAX                    _PC_LINK_MAX
+MAX_CANON                   _PC_MAX_CANON
+MAX_INPUT                   _PC_MAX_INPUT
+NAME_MAX                    _PC_NAME_MAX
+PATH_MAX                    _PC_PATH_MAX
+PIPE_BUF                    _PC_PIPE_BUF
+POSIX2_SYMLINKS             _PC_2_SYMLINKS
+POSIX_ALLOC_SIZE_MIN        _PC_ALLOC_SIZE_MIN
+POSIX_REC_INCR_XFER_SIZE    _PC_REC_INCR_XFER_SIZE
+POSIX_REC_MAX_XFER_SIZE     _PC_REC_MAX_XFER_SIZE
+POSIX_REC_MIN_XFER_SIZE     _PC_REC_MIN_XFER_SIZE
+POSIX_REC_XFER_ALIGN        _PC_REC_XFER_ALIGN
+SYMLINK_MAX                 _PC_SYMLINK_MAX
+_POSIX_CHOWN_RESTRICTED     _PC_CHOWN_RESTRICTED
+_POSIX_NO_TRUNC             _PC_NO_TRUNC
+_POSIX_VDISABLE             _PC_VDISABLE
+_POSIX_ASYNC_IO             _PC_ASYNC_IO
+_POSIX_PRIO_IO              _PC_PRIO_IO
+_POSIX_SYNC_IO              _PC_SYNC_IO
+_POSIX_TIMESTAMP_RESOLUTION _PC_TIMESTAMP_RESOLUTION
+!
diff --git a/source/sbase/grep.1 b/source/sbase/grep.1
new file mode 100644 (file)
index 0000000..6f80175
--- /dev/null
@@ -0,0 +1,93 @@
+.Dd 2015-10-08
+.Dt GREP 1
+.Os sbase
+.Sh NAME
+.Nm grep
+.Nd search files for patterns
+.Sh SYNOPSIS
+.Nm
+.Op Fl EFHchilnqsvx
+.Op Fl e Ar pattern
+.Op Fl f Ar file
+.Op Ar pattern
+.Op Ar file ...
+.Sh DESCRIPTION
+.Nm
+searches the input files for lines that match the
+.Ar pattern ,
+a regular expression as defined in
+.Xr regex 7 .
+By default each matching line is printed to stdout. If no
+.Ar file
+is given
+.Nm
+reads from stdin.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl E
+Match using extended regex.
+.It Fl F
+Match using fixed strings. Treat each pattern specified as a string instead of
+a regular expression.
+.It Fl H
+Prefix each matching line with its filename in the output. This is the
+default when there is more than one file specified.
+.It Fl c
+Print only a count of matching lines.
+.It Fl e Ar pattern
+Specify a pattern used during the search of the input: an input
+line is selected if it matches any of the specified patterns.
+This option is most useful when multiple -e options are used to
+specify multiple patterns, or when a pattern begins with a dash.
+.It Fl f Ar file
+Read one or more patterns from the file named by the pathname file.
+Patterns in file shall be terminated by a <newline>. A null pattern can be
+specified by an empty line in pattern_file. Unless the -E or -F option is
+also specified, each pattern shall be treated as a BRE.
+(`-').
+.It Fl h
+Do not prefix each line with 'filename:' prefix.
+.It Fl i
+Match lines case insensitively.
+.It Fl l
+Print only the names of files with matching lines.
+.It Fl n
+Prefix each matching line with its line number in the input.
+.It Fl q
+Print nothing, only return status.
+.It Fl s
+Suppress the error messages ordinarily written for nonexistent or unreadable
+files.
+.It Fl v
+Select lines which do
+.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 <newline> to match an entire fixed string or regular expression to
+be matching lines.
+.El
+.Sh EXIT STATUS
+.Bl -tag -width Ds
+.It 0
+One or more lines were matched.
+.It 1
+No lines were matched.
+.It > 1
+An error occurred.
+.El
+.Sh SEE ALSO
+.Xr 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 (file)
index 0000000..64ffbe2
--- /dev/null
@@ -0,0 +1,288 @@
+/* See LICENSE file for copyright and license details. */
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+#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, "<stdin>");
+       } else {
+               for (; *argv; argc--, argv++) {
+                       if (!strcmp(*argv, "-")) {
+                               *argv = "<stdin>";
+                               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, "<stdin>") | fshut(stdout, "<stdout>"))
+               match = Error;
+
+       return match;
+}
diff --git a/source/sbase/head.1 b/source/sbase/head.1
new file mode 100644 (file)
index 0000000..a7df5e2
--- /dev/null
@@ -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 (file)
index 0000000..ae550c0
--- /dev/null
@@ -0,0 +1,77 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, "<stdin>", n);
+       } else {
+               many = argc > 1;
+               for (newline = 0; *argv; argc--, argv++) {
+                       if (!strcmp(*argv, "-")) {
+                               *argv = "<stdin>";
+                               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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/hostname.1 b/source/sbase/hostname.1
new file mode 100644 (file)
index 0000000..a8f15de
--- /dev/null
@@ -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 (file)
index 0000000..a0e3969
--- /dev/null
@@ -0,0 +1,33 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+       eprintf("usage: %s [name]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+       char host[HOST_NAME_MAX + 1];
+
+       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, "<stdout>");
+}
diff --git a/source/sbase/join.1 b/source/sbase/join.1
new file mode 100644 (file)
index 0000000..5a01a42
--- /dev/null
@@ -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 (file)
index 0000000..d3e2343
--- /dev/null
@@ -0,0 +1,529 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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] = "<stdin>";
+                       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, "<stdout>"))
+               ret = 2;
+
+       return ret;
+}
diff --git a/source/sbase/kill.1 b/source/sbase/kill.1
new file mode 100644 (file)
index 0000000..47f9915
--- /dev/null
@@ -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 (file)
index 0000000..f7dd132
--- /dev/null
@@ -0,0 +1,129 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+
+#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, "<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 (file)
index 0000000..aac2d2e
--- /dev/null
@@ -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 (file)
index 0000000..8cd78c6
--- /dev/null
@@ -0,0 +1,36 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 (file)
index 0000000..6a393b5
--- /dev/null
@@ -0,0 +1,27 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 (file)
index 0000000..e4720d0
--- /dev/null
@@ -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 (file)
index 0000000..453be23
--- /dev/null
@@ -0,0 +1,679 @@
+/* Automatically generated by mkrunetype.awk */
+#include <stdlib.h>
+
+#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 (file)
index 0000000..7cf9159
--- /dev/null
@@ -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 (file)
index 0000000..286dce8
--- /dev/null
@@ -0,0 +1,18 @@
+/* Automatically generated by mkrunetype.awk */
+#include <stdlib.h>
+
+#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 (file)
index 0000000..0dfb078
--- /dev/null
@@ -0,0 +1,66 @@
+/* Automatically generated by mkrunetype.awk */
+#include <stdlib.h>
+
+#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 (file)
index 0000000..08770f6
--- /dev/null
@@ -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 (file)
index 0000000..f6e2fa4
--- /dev/null
@@ -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 (file)
index 0000000..d73cb25
--- /dev/null
@@ -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 (file)
index 0000000..bb8fe28
--- /dev/null
@@ -0,0 +1,31 @@
+/* Automatically generated by mkrunetype.awk */
+#include <stdlib.h>
+
+#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 (file)
index 0000000..211a4aa
--- /dev/null
@@ -0,0 +1,31 @@
+/* Automatically generated by mkrunetype.awk */
+#include <stdlib.h>
+
+#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 (file)
index 0000000..0797240
--- /dev/null
@@ -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 (file)
index 0000000..df49f67
--- /dev/null
@@ -0,0 +1,317 @@
+/* Automatically generated by mkrunetype.awk */
+#include <stdlib.h>
+
+#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 (file)
index 0000000..3736e78
--- /dev/null
@@ -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 <stdlib.h>\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 'is<name>rune' 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 (file)
index 0000000..1273f45
--- /dev/null
@@ -0,0 +1,148 @@
+/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#include "../utf.h"
+
+#define MIN(x,y)  ((x) < (y) ? (x) : (y))
+
+#define UTFSEQ(x) ((((x) & 0x80) == 0x00) ? 1 /* 0xxxxxxx */ \
+                 : (((x) & 0xC0) == 0x80) ? 0 /* 10xxxxxx */ \
+                 : (((x) & 0xE0) == 0xC0) ? 2 /* 110xxxxx */ \
+                 : (((x) & 0xF0) == 0xE0) ? 3 /* 1110xxxx */ \
+                 : (((x) & 0xF8) == 0xF0) ? 4 /* 11110xxx */ \
+                 : (((x) & 0xFC) == 0xF8) ? 5 /* 111110xx */ \
+                 : (((x) & 0xFE) == 0xFC) ? 6 /* 1111110x */ \
+                                          : 0 )
+
+#define BADRUNE(x) ((x) < 0 || (x) > Runemax \
+                || ((x) & 0xFFFE) == 0xFFFE \
+                || ((x) >= 0xD800 && (x) <= 0xDFFF) \
+                || ((x) >= 0xFDD0 && (x) <= 0xFDEF))
+
+int
+runetochar(char *s, const Rune *p)
+{
+       Rune r = *p;
+
+       switch(runelen(r)) {
+       case 1: /* 0aaaaaaa */
+               s[0] = r;
+               return 1;
+       case 2: /* 00000aaa aabbbbbb */
+               s[0] = 0xC0 | ((r & 0x0007C0) >>  6); /* 110aaaaa */
+               s[1] = 0x80 |  (r & 0x00003F);        /* 10bbbbbb */
+               return 2;
+       case 3: /* aaaabbbb bbcccccc */
+               s[0] = 0xE0 | ((r & 0x00F000) >> 12); /* 1110aaaa */
+               s[1] = 0x80 | ((r & 0x000FC0) >>  6); /* 10bbbbbb */
+               s[2] = 0x80 |  (r & 0x00003F);        /* 10cccccc */
+               return 3;
+       case 4: /* 000aaabb bbbbcccc ccdddddd */
+               s[0] = 0xF0 | ((r & 0x1C0000) >> 18); /* 11110aaa */
+               s[1] = 0x80 | ((r & 0x03F000) >> 12); /* 10bbbbbb */
+               s[2] = 0x80 | ((r & 0x000FC0) >>  6); /* 10cccccc */
+               s[3] = 0x80 |  (r & 0x00003F);        /* 10dddddd */
+               return 4;
+       default:
+               return 0; /* error */
+       }
+}
+
+int
+chartorune(Rune *p, const char *s)
+{
+       return charntorune(p, s, UTFmax);
+}
+
+int
+charntorune(Rune *p, const char *s, size_t len)
+{
+       unsigned int i, n;
+       Rune r;
+
+       if(len == 0) /* can't even look at s[0] */
+               return 0;
+
+       switch((n = UTFSEQ(s[0]))) {
+       case 1: r = s[0];        break; /* 0xxxxxxx */
+       case 2: r = s[0] & 0x1F; break; /* 110xxxxx */
+       case 3: r = s[0] & 0x0F; break; /* 1110xxxx */
+       case 4: r = s[0] & 0x07; break; /* 11110xxx */
+       case 5: r = s[0] & 0x03; break; /* 111110xx */
+       case 6: r = s[0] & 0x01; break; /* 1111110x */
+       default: /* invalid sequence */
+               *p = Runeerror;
+               return 1;
+       }
+       /* add values from continuation bytes */
+       for(i = 1; i < MIN(n, len); i++)
+               if((s[i] & 0xC0) == 0x80) {
+                       /* add bits from continuation byte to rune value
+                        * cannot overflow: 6 byte sequences contain 31 bits */
+                       r = (r << 6) | (s[i] & 0x3F); /* 10xxxxxx */
+               }
+               else { /* expected continuation */
+                       *p = Runeerror;
+                       return i;
+               }
+
+       if(i < n) /* must have reached len limit */
+               return 0;
+
+       /* reject invalid or overlong sequences */
+       if(BADRUNE(r) || runelen(r) < (int)n)
+               r = Runeerror;
+
+       *p = r;
+       return n;
+}
+
+int
+runelen(Rune r)
+{
+       if(BADRUNE(r))
+               return 0; /* error */
+       else if(r <= 0x7F)
+               return 1;
+       else if(r <= 0x07FF)
+               return 2;
+       else if(r <= 0xFFFF)
+               return 3;
+       else
+               return 4;
+}
+
+size_t
+runenlen(const Rune *p, size_t len)
+{
+       size_t i, n = 0;
+
+       for(i = 0; i < len; i++)
+               n += runelen(p[i]);
+       return n;
+}
+
+int
+fullrune(const char *s, size_t len)
+{
+       Rune r;
+
+       return charntorune(&r, s, len) > 0;
+}
diff --git a/source/sbase/libutf/runetype.c b/source/sbase/libutf/runetype.c
new file mode 100644 (file)
index 0000000..9e8ede8
--- /dev/null
@@ -0,0 +1,41 @@
+/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com>
+ *                            (c) 2015 Laslo Hunhold <dev@frign.de>
+ *
+ * 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 (file)
index 0000000..8d09c34
--- /dev/null
@@ -0,0 +1,26 @@
+/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com>
+ *                            (c) 2015 Laslo Hunhold <dev@frign.de>
+ *
+ * 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 (file)
index 0000000..3d40fb5
--- /dev/null
@@ -0,0 +1,242 @@
+/* Automatically generated by mkrunetype.awk */
+#include <stdlib.h>
+
+#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 (file)
index 0000000..897c5ef
--- /dev/null
@@ -0,0 +1,129 @@
+/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include "../utf.h"
+
+char *
+utfecpy(char *to, char *end, const char *from)
+{
+       Rune r = Runeerror;
+       size_t i, n;
+
+       /* seek through to find final full rune */
+       for(i = 0; r != '\0' && (n = charntorune(&r, &from[i], end - &to[i])); i += n)
+               ;
+       memcpy(to, from, i); /* copy over bytes up to this rune */
+
+       if(i > 0 && r != '\0')
+               to[i] = '\0'; /* terminate if unterminated */
+       return &to[i];
+}
+
+size_t
+utflen(const char *s)
+{
+       const char *p = s;
+       size_t i;
+       Rune r;
+
+       for(i = 0; *p != '\0'; i++)
+               p += chartorune(&r, p);
+       return i;
+}
+
+size_t
+utfnlen(const char *s, size_t len)
+{
+       const char *p = s;
+       size_t i;
+       Rune r;
+       int n;
+
+       for(i = 0; (n = charntorune(&r, p, len-(p-s))) && r != '\0'; i++)
+               p += n;
+       return i;
+}
+
+char *
+utfrune(const char *s, Rune r)
+{
+       if(r < Runeself) {
+               return strchr(s, r);
+       }
+       else if(r == Runeerror) {
+               Rune r0;
+               int n;
+
+               for(; *s != '\0'; s += n) {
+                       n = chartorune(&r0, s);
+                       if(r == r0)
+                               return (char *)s;
+               }
+       }
+       else {
+               char buf[UTFmax+1];
+               int n;
+
+               if(!(n = runetochar(buf, &r)))
+                       return NULL;
+               buf[n] = '\0';
+               return strstr(s, buf);
+       }
+       return NULL;
+}
+
+char *
+utfrrune(const char *s, Rune r)
+{
+       const char *p = NULL;
+       Rune r0;
+       int n;
+
+       if(r < Runeself)
+               return strrchr(s, r);
+
+       for(; *s != '\0'; s += n) {
+               n = chartorune(&r0, s);
+               if(r == r0)
+                       p = s;
+       }
+       return (char *)p;
+}
+
+char *
+utfutf(const char *s, const char *t)
+{
+       const char *p, *q;
+       Rune r0, r1, r2;
+       int n, m;
+
+       for(chartorune(&r0, t); (s = utfrune(s, r0)); s++) {
+               for(p = s, q = t; *q && *p; p += n, q += m) {
+                       n = chartorune(&r1, p);
+                       m = chartorune(&r2, q);
+                       if(r1 != r2)
+                               break;
+               }
+               if(!*q)
+                       return (char *)s;
+       }
+       return NULL;
+}
diff --git a/source/sbase/libutf/utftorunestr.c b/source/sbase/libutf/utftorunestr.c
new file mode 100644 (file)
index 0000000..005fe8a
--- /dev/null
@@ -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 (file)
index 0000000..fad9471
--- /dev/null
@@ -0,0 +1,19 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+
+#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 (file)
index 0000000..c398962
--- /dev/null
@@ -0,0 +1,176 @@
+/* See LICENSE file for copyright and license details. */
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <utime.h>
+
+#include "../fs.h"
+#include "../text.h"
+#include "../util.h"
+
+int cp_aflag  = 0;
+int cp_fflag  = 0;
+int cp_pflag  = 0;
+int cp_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 (file)
index 0000000..3f849ba
--- /dev/null
@@ -0,0 +1,179 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../crypt.h"
+#include "../text.h"
+#include "../util.h"
+
+static int
+hexdec(int c)
+{
+       if (c >= '0' && c <= '9')
+               return c - '0';
+       else if (c >= 'A' && c <= 'F')
+               return c - 'A' + 10;
+       else if (c >= 'a' && c <= 'f')
+               return c - 'a' + 10;
+       return -1; /* unknown character */
+}
+
+static int
+mdcheckline(const char *s, uint8_t *md, size_t sz)
+{
+       size_t i;
+       int b1, b2;
+
+       for (i = 0; i < sz; i++) {
+               if (!*s || (b1 = hexdec(*s++)) < 0)
+                       return -1; /* invalid format */
+               if (!*s || (b2 = hexdec(*s++)) < 0)
+                       return -1; /* invalid format */
+               if ((uint8_t)((b1 << 4) | b2) != md[i])
+                       return 0; /* value mismatch */
+       }
+       return (i == sz) ? 1 : 0;
+}
+
+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, "<stdin>", md);
+               mdprint(md, "<stdin>", sz);
+       } else {
+               for (; *argv; argc--, argv++) {
+                       if ((*argv)[0] == '-' && !(*argv)[1]) {
+                               *argv = "<stdin>";
+                               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 (file)
index 0000000..320865d
--- /dev/null
@@ -0,0 +1,88 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdlib.h>
+#include <string.h>
+
+#include "../util.h"
+
+void *
+ecalloc(size_t nmemb, size_t size)
+{
+       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 (file)
index 0000000..a2e225a
--- /dev/null
@@ -0,0 +1,38 @@
+/* See LICENSE file for copyright and license details. */
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "../util.h"
+
+void
+enmasse(int argc, char *argv[], int (*fn)(const char *, const char *, 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 (file)
index 0000000..673523e
--- /dev/null
@@ -0,0 +1,59 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../util.h"
+
+char *argv0;
+
+static void 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 (file)
index 0000000..02c8698
--- /dev/null
@@ -0,0 +1,27 @@
+#include <sys/types.h>
+
+#include <regex.h>
+#include <stdio.h>
+
+#include "../util.h"
+
+int
+enregcomp(int status, regex_t *preg, const char *regex, int cflags)
+{
+       char errbuf[BUFSIZ] = "";
+       int r;
+
+       if ((r = regcomp(preg, regex, cflags)) == 0)
+               return r;
+
+       regerror(r, preg, errbuf, sizeof(errbuf));
+       enprintf(status, "invalid regex: %s\n", errbuf);
+
+       return r;
+}
+
+int
+eregcomp(regex_t *preg, const char *regex, int cflags)
+{
+       return enregcomp(1, preg, regex, cflags);
+}
diff --git a/source/sbase/libutil/estrtod.c b/source/sbase/libutil/estrtod.c
new file mode 100644 (file)
index 0000000..24e4fdc
--- /dev/null
@@ -0,0 +1,18 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../util.h"
+
+double
+estrtod(const char *s)
+{
+       char *end;
+       double d;
+
+       d = strtod(s, &end);
+       if (end == s || *end != '\0')
+               eprintf("%s: not a real number\n", s);
+       return d;
+}
diff --git a/source/sbase/libutil/fnck.c b/source/sbase/libutil/fnck.c
new file mode 100644 (file)
index 0000000..92da1f4
--- /dev/null
@@ -0,0 +1,21 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include "../util.h"
+
+void
+fnck(const char *a, const char *b,
+     int (*fn)(const char *, const char *, 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 (file)
index 0000000..e596f07
--- /dev/null
@@ -0,0 +1,43 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 (file)
index 0000000..b912769
--- /dev/null
@@ -0,0 +1,32 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../text.h"
+#include "../util.h"
+
+void
+getlines(FILE *fp, struct linebuf *b)
+{
+       char *line = NULL;
+       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 (file)
index 0000000..7e39ba5
--- /dev/null
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+
+#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 (file)
index 0000000..f5a6cf9
--- /dev/null
@@ -0,0 +1,24 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <string.h>
+
+#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 (file)
index 0000000..c7483ac
--- /dev/null
@@ -0,0 +1,148 @@
+/* public domain md5 implementation based on rfc1321 and libtomcrypt */
+#include <stdint.h>
+#include <string.h>
+
+#include "../md5.h"
+
+static uint32_t rol(uint32_t n, int k) { return (n << k) | (n >> (32-k)); }
+#define F(x,y,z) (z ^ (x & (y ^ z)))
+#define G(x,y,z) (y ^ (z & (y ^ x)))
+#define H(x,y,z) (x ^ y ^ z)
+#define I(x,y,z) (y ^ (x | ~z))
+#define FF(a,b,c,d,w,s,t) a += F(b,c,d) + w + t; a = rol(a,s) + b
+#define GG(a,b,c,d,w,s,t) a += G(b,c,d) + w + t; a = rol(a,s) + b
+#define HH(a,b,c,d,w,s,t) a += H(b,c,d) + w + t; a = rol(a,s) + b
+#define II(a,b,c,d,w,s,t) a += I(b,c,d) + w + t; a = rol(a,s) + b
+
+static const uint32_t tab[64] = {
+       0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
+       0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
+       0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
+       0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
+       0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
+       0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+       0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
+       0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
+};
+
+static void
+processblock(struct md5 *s, const uint8_t *buf)
+{
+       uint32_t i, W[16], a, b, c, d;
+
+       for (i = 0; i < 16; i++) {
+               W[i] = buf[4*i];
+               W[i] |= (uint32_t)buf[4*i+1]<<8;
+               W[i] |= (uint32_t)buf[4*i+2]<<16;
+               W[i] |= (uint32_t)buf[4*i+3]<<24;
+       }
+
+       a = s->h[0];
+       b = s->h[1];
+       c = s->h[2];
+       d = s->h[3];
+
+       i = 0;
+       while (i < 16) {
+               FF(a,b,c,d, W[i],  7, tab[i]); i++;
+               FF(d,a,b,c, W[i], 12, tab[i]); i++;
+               FF(c,d,a,b, W[i], 17, tab[i]); i++;
+               FF(b,c,d,a, W[i], 22, tab[i]); i++;
+       }
+       while (i < 32) {
+               GG(a,b,c,d, W[(5*i+1)%16],  5, tab[i]); i++;
+               GG(d,a,b,c, W[(5*i+1)%16],  9, tab[i]); i++;
+               GG(c,d,a,b, W[(5*i+1)%16], 14, tab[i]); i++;
+               GG(b,c,d,a, W[(5*i+1)%16], 20, tab[i]); i++;
+       }
+       while (i < 48) {
+               HH(a,b,c,d, W[(3*i+5)%16],  4, tab[i]); i++;
+               HH(d,a,b,c, W[(3*i+5)%16], 11, tab[i]); i++;
+               HH(c,d,a,b, W[(3*i+5)%16], 16, tab[i]); i++;
+               HH(b,c,d,a, W[(3*i+5)%16], 23, tab[i]); i++;
+       }
+       while (i < 64) {
+               II(a,b,c,d, W[7*i%16],  6, tab[i]); i++;
+               II(d,a,b,c, W[7*i%16], 10, tab[i]); i++;
+               II(c,d,a,b, W[7*i%16], 15, tab[i]); i++;
+               II(b,c,d,a, W[7*i%16], 21, tab[i]); i++;
+       }
+
+       s->h[0] += a;
+       s->h[1] += b;
+       s->h[2] += c;
+       s->h[3] += d;
+}
+
+static void
+pad(struct md5 *s)
+{
+       unsigned r = s->len % 64;
+
+       s->buf[r++] = 0x80;
+       if (r > 56) {
+               memset(s->buf + r, 0, 64 - r);
+               r = 0;
+               processblock(s, s->buf);
+       }
+       memset(s->buf + r, 0, 56 - r);
+       s->len *= 8;
+       s->buf[56] = s->len;
+       s->buf[57] = s->len >> 8;
+       s->buf[58] = s->len >> 16;
+       s->buf[59] = s->len >> 24;
+       s->buf[60] = s->len >> 32;
+       s->buf[61] = s->len >> 40;
+       s->buf[62] = s->len >> 48;
+       s->buf[63] = s->len >> 56;
+       processblock(s, s->buf);
+}
+
+void
+md5_init(void *ctx)
+{
+       struct md5 *s = ctx;
+       s->len = 0;
+       s->h[0] = 0x67452301;
+       s->h[1] = 0xefcdab89;
+       s->h[2] = 0x98badcfe;
+       s->h[3] = 0x10325476;
+}
+
+void
+md5_sum(void *ctx, uint8_t md[MD5_DIGEST_LENGTH])
+{
+       struct md5 *s = ctx;
+       int i;
+
+       pad(s);
+       for (i = 0; i < 4; i++) {
+               md[4*i] = s->h[i];
+               md[4*i+1] = s->h[i] >> 8;
+               md[4*i+2] = s->h[i] >> 16;
+               md[4*i+3] = s->h[i] >> 24;
+       }
+}
+
+void
+md5_update(void *ctx, const void *m, unsigned long len)
+{
+       struct md5 *s = ctx;
+       const uint8_t *p = m;
+       unsigned r = s->len % 64;
+
+       s->len += len;
+       if (r) {
+               if (len < 64 - r) {
+                       memcpy(s->buf + r, p, len);
+                       return;
+               }
+               memcpy(s->buf + r, p, 64 - r);
+               len -= 64 - r;
+               p += 64 - r;
+               processblock(s, s->buf);
+       }
+       for (; len >= 64; len -= 64, p += 64)
+               processblock(s, p);
+       memcpy(s->buf, p, len);
+}
diff --git a/source/sbase/libutil/memmem.c b/source/sbase/libutil/memmem.c
new file mode 100644 (file)
index 0000000..7dfef34
--- /dev/null
@@ -0,0 +1,66 @@
+/*     $OpenBSD: memmem.c,v 1.4 2015/08/31 02:53:57 guenther Exp $ */
+
+/*
+ * Copyright (c) 2005 Pascal Gloor <pascal.gloor@spale.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce 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 <string.h>
+
+#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 (file)
index 0000000..7796e24
--- /dev/null
@@ -0,0 +1,30 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <limits.h>
+
+#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 (file)
index 0000000..365b9ad
--- /dev/null
@@ -0,0 +1,163 @@
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "../util.h"
+
+mode_t
+getumask(void)
+{
+       mode_t mask = umask(0);
+       umask(mask);
+       return mask;
+}
+
+mode_t
+parsemode(const char *str, mode_t mode, mode_t mask)
+{
+       char *end;
+       const char *p = str;
+       int octal, op;
+       mode_t who, perm, clear;
+
+       octal = strtol(str, &end, 8);
+       if (*end == '\0') {
+               if (octal < 0 || octal > 07777) {
+                       eprintf("%s: invalid mode\n", str);
+                       return -1;
+               }
+               mode = 0;
+               if (octal & 04000) mode |= S_ISUID;
+               if (octal & 02000) mode |= S_ISGID;
+               if (octal & 01000) mode |= S_ISVTX;
+               if (octal & 00400) mode |= S_IRUSR;
+               if (octal & 00200) mode |= S_IWUSR;
+               if (octal & 00100) mode |= S_IXUSR;
+               if (octal & 00040) mode |= S_IRGRP;
+               if (octal & 00020) mode |= S_IWGRP;
+               if (octal & 00010) mode |= S_IXGRP;
+               if (octal & 00004) mode |= S_IROTH;
+               if (octal & 00002) mode |= S_IWOTH;
+               if (octal & 00001) mode |= S_IXOTH;
+               return mode;
+       }
+next:
+       /* first, determine which bits we will be modifying */
+       for (who = 0; *p; p++) {
+               switch (*p) {
+               /* masks */
+               case 'u':
+                       who |= S_IRWXU|S_ISUID;
+                       continue;
+               case 'g':
+                       who |= S_IRWXG|S_ISGID;
+                       continue;
+               case 'o':
+                       who |= S_IRWXO;
+                       continue;
+               case 'a':
+                       who |= S_IRWXU|S_ISUID|S_IRWXG|S_ISGID|S_IRWXO;
+                       continue;
+               }
+               break;
+       }
+       if (who) {
+               clear = who;
+       } else {
+               clear = S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO;
+               who = ~mask;
+       }
+       while (*p) {
+               switch (*p) {
+               /* opers */
+               case '=':
+               case '+':
+               case '-':
+                       op = (int)*p;
+                       break;
+               default:
+                       eprintf("%s: invalid mode\n", str);
+                       return -1;
+               }
+
+               perm = 0;
+               switch (*++p) {
+               /* copy */
+               case 'u':
+                       if (mode & S_IRUSR)
+                               perm |= S_IRUSR|S_IRGRP|S_IROTH;
+                       if (mode & S_IWUSR)
+                               perm |= S_IWUSR|S_IWGRP|S_IWOTH;
+                       if (mode & S_IXUSR)
+                               perm |= S_IXUSR|S_IXGRP|S_IXOTH;
+                       if (mode & S_ISUID)
+                               perm |= S_ISUID|S_ISGID;
+                       p++;
+                       break;
+               case 'g':
+                       if (mode & S_IRGRP)
+                               perm |= S_IRUSR|S_IRGRP|S_IROTH;
+                       if (mode & S_IWGRP)
+                               perm |= S_IWUSR|S_IWGRP|S_IWOTH;
+                       if (mode & S_IXGRP)
+                               perm |= S_IXUSR|S_IXGRP|S_IXOTH;
+                       if (mode & S_ISGID)
+                               perm |= S_ISUID|S_ISGID;
+                       p++;
+                       break;
+               case 'o':
+                       if (mode & S_IROTH)
+                               perm |= S_IRUSR|S_IRGRP|S_IROTH;
+                       if (mode & S_IWOTH)
+                               perm |= S_IWUSR|S_IWGRP|S_IWOTH;
+                       if (mode & S_IXOTH)
+                               perm |= S_IXUSR|S_IXGRP|S_IXOTH;
+                       p++;
+                       break;
+               default:
+                       for (; *p; p++) {
+                               switch (*p) {
+                               /* modes */
+                               case 'r':
+                                       perm |= S_IRUSR|S_IRGRP|S_IROTH;
+                                       break;
+                               case 'w':
+                                       perm |= S_IWUSR|S_IWGRP|S_IWOTH;
+                                       break;
+                               case 'x':
+                                       perm |= S_IXUSR|S_IXGRP|S_IXOTH;
+                                       break;
+                               case 's':
+                                       perm |= S_ISUID|S_ISGID;
+                                       break;
+                               case 't':
+                                       perm |= S_ISVTX;
+                                       break;
+                               default:
+                                       goto apply;
+                               }
+                       }
+               }
+
+apply:
+               /* apply */
+               switch (op) {
+               case '=':
+                       mode &= ~clear;
+                       /* fallthrough */
+               case '+':
+                       mode |= perm & who;
+                       break;
+               case '-':
+                       mode &= ~(perm & who);
+                       break;
+               }
+               /* if we hit a comma, move on to the next clause */
+               if (*p == ',') {
+                       p++;
+                       goto next;
+               }
+       }
+       return mode;
+}
diff --git a/source/sbase/libutil/parseoffset.c b/source/sbase/libutil/parseoffset.c
new file mode 100644 (file)
index 0000000..362a782
--- /dev/null
@@ -0,0 +1,61 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 (file)
index 0000000..80a9860
--- /dev/null
@@ -0,0 +1,16 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+
+#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 (file)
index 0000000..c6e5219
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#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 (file)
index 0000000..e2b8a6e
--- /dev/null
@@ -0,0 +1,107 @@
+/* See LICENSE file for copyright and license details. */
+#include <dirent.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "../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 (file)
index 0000000..f6a54b6
--- /dev/null
@@ -0,0 +1,31 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..3d76a1b
--- /dev/null
@@ -0,0 +1,144 @@
+/* public domain sha1 implementation based on rfc3174 and libtomcrypt */
+#include <stdint.h>
+#include <string.h>
+
+#include "../sha1.h"
+
+static uint32_t rol(uint32_t n, int k) { return (n << k) | (n >> (32-k)); }
+#define F0(b,c,d) (d ^ (b & (c ^ d)))
+#define F1(b,c,d) (b ^ c ^ d)
+#define F2(b,c,d) ((b & c) | (d & (b | c)))
+#define F3(b,c,d) (b ^ c ^ d)
+#define G0(a,b,c,d,e,i) e += rol(a,5)+F0(b,c,d)+W[i]+0x5A827999; b = rol(b,30)
+#define G1(a,b,c,d,e,i) e += rol(a,5)+F1(b,c,d)+W[i]+0x6ED9EBA1; b = rol(b,30)
+#define G2(a,b,c,d,e,i) e += rol(a,5)+F2(b,c,d)+W[i]+0x8F1BBCDC; b = rol(b,30)
+#define G3(a,b,c,d,e,i) e += rol(a,5)+F3(b,c,d)+W[i]+0xCA62C1D6; b = rol(b,30)
+
+static void
+processblock(struct sha1 *s, const uint8_t *buf)
+{
+       uint32_t W[80], a, b, c, d, e;
+       int i;
+
+       for (i = 0; i < 16; i++) {
+               W[i] = (uint32_t)buf[4*i]<<24;
+               W[i] |= (uint32_t)buf[4*i+1]<<16;
+               W[i] |= (uint32_t)buf[4*i+2]<<8;
+               W[i] |= buf[4*i+3];
+       }
+       for (; i < 80; i++)
+               W[i] = rol(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16], 1);
+       a = s->h[0];
+       b = s->h[1];
+       c = s->h[2];
+       d = s->h[3];
+       e = s->h[4];
+       for (i = 0; i < 20; ) {
+               G0(a,b,c,d,e,i++);
+               G0(e,a,b,c,d,i++);
+               G0(d,e,a,b,c,i++);
+               G0(c,d,e,a,b,i++);
+               G0(b,c,d,e,a,i++);
+       }
+       while (i < 40) {
+               G1(a,b,c,d,e,i++);
+               G1(e,a,b,c,d,i++);
+               G1(d,e,a,b,c,i++);
+               G1(c,d,e,a,b,i++);
+               G1(b,c,d,e,a,i++);
+       }
+       while (i < 60) {
+               G2(a,b,c,d,e,i++);
+               G2(e,a,b,c,d,i++);
+               G2(d,e,a,b,c,i++);
+               G2(c,d,e,a,b,i++);
+               G2(b,c,d,e,a,i++);
+       }
+       while (i < 80) {
+               G3(a,b,c,d,e,i++);
+               G3(e,a,b,c,d,i++);
+               G3(d,e,a,b,c,i++);
+               G3(c,d,e,a,b,i++);
+               G3(b,c,d,e,a,i++);
+       }
+       s->h[0] += a;
+       s->h[1] += b;
+       s->h[2] += c;
+       s->h[3] += d;
+       s->h[4] += e;
+}
+
+static void
+pad(struct sha1 *s)
+{
+       unsigned r = s->len % 64;
+
+       s->buf[r++] = 0x80;
+       if (r > 56) {
+               memset(s->buf + r, 0, 64 - r);
+               r = 0;
+               processblock(s, s->buf);
+       }
+       memset(s->buf + r, 0, 56 - r);
+       s->len *= 8;
+       s->buf[56] = s->len >> 56;
+       s->buf[57] = s->len >> 48;
+       s->buf[58] = s->len >> 40;
+       s->buf[59] = s->len >> 32;
+       s->buf[60] = s->len >> 24;
+       s->buf[61] = s->len >> 16;
+       s->buf[62] = s->len >> 8;
+       s->buf[63] = s->len;
+       processblock(s, s->buf);
+}
+
+void
+sha1_init(void *ctx)
+{
+       struct sha1 *s = ctx;
+
+       s->len = 0;
+       s->h[0] = 0x67452301;
+       s->h[1] = 0xEFCDAB89;
+       s->h[2] = 0x98BADCFE;
+       s->h[3] = 0x10325476;
+       s->h[4] = 0xC3D2E1F0;
+}
+
+void
+sha1_sum(void *ctx, uint8_t md[SHA1_DIGEST_LENGTH])
+{
+       struct sha1 *s = ctx;
+       int i;
+
+       pad(s);
+       for (i = 0; i < 5; i++) {
+               md[4*i] = s->h[i] >> 24;
+               md[4*i+1] = s->h[i] >> 16;
+               md[4*i+2] = s->h[i] >> 8;
+               md[4*i+3] = s->h[i];
+       }
+}
+
+void
+sha1_update(void *ctx, const void *m, unsigned long len)
+{
+       struct sha1 *s = ctx;
+       const uint8_t *p = m;
+       unsigned r = s->len % 64;
+
+       s->len += len;
+       if (r) {
+               if (len < 64 - r) {
+                       memcpy(s->buf + r, p, len);
+                       return;
+               }
+               memcpy(s->buf + r, p, 64 - r);
+               len -= 64 - r;
+               p += 64 - r;
+               processblock(s, s->buf);
+       }
+       for (; len >= 64; len -= 64, p += 64)
+               processblock(s, p);
+       memcpy(s->buf, p, len);
+}
diff --git a/source/sbase/libutil/sha224.c b/source/sbase/libutil/sha224.c
new file mode 100644 (file)
index 0000000..fce520f
--- /dev/null
@@ -0,0 +1,26 @@
+/* public domain sha224 implementation based on fips180-3 */
+#include <stdint.h>
+#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 (file)
index 0000000..266cfec
--- /dev/null
@@ -0,0 +1,154 @@
+/* public domain sha256 implementation based on fips180-3 */
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../sha256.h"
+
+static uint32_t ror(uint32_t n, int k) { return (n >> k) | (n << (32-k)); }
+#define Ch(x,y,z)  (z ^ (x & (y ^ z)))
+#define Maj(x,y,z) ((x & y) | (z & (x | y)))
+#define S0(x)      (ror(x,2) ^ ror(x,13) ^ ror(x,22))
+#define S1(x)      (ror(x,6) ^ ror(x,11) ^ ror(x,25))
+#define R0(x)      (ror(x,7) ^ ror(x,18) ^ (x>>3))
+#define R1(x)      (ror(x,17) ^ ror(x,19) ^ (x>>10))
+
+static const uint32_t K[64] = {
+0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+};
+
+static void
+processblock(struct sha256 *s, const uint8_t *buf)
+{
+       uint32_t W[64], t1, t2, a, b, c, d, e, f, g, h;
+       int i;
+
+       for (i = 0; i < 16; i++) {
+               W[i] = (uint32_t)buf[4*i]<<24;
+               W[i] |= (uint32_t)buf[4*i+1]<<16;
+               W[i] |= (uint32_t)buf[4*i+2]<<8;
+               W[i] |= buf[4*i+3];
+       }
+       for (; i < 64; i++)
+               W[i] = R1(W[i-2]) + W[i-7] + R0(W[i-15]) + W[i-16];
+       a = s->h[0];
+       b = s->h[1];
+       c = s->h[2];
+       d = s->h[3];
+       e = s->h[4];
+       f = s->h[5];
+       g = s->h[6];
+       h = s->h[7];
+       for (i = 0; i < 64; i++) {
+               t1 = h + S1(e) + Ch(e,f,g) + K[i] + W[i];
+               t2 = S0(a) + Maj(a,b,c);
+               h = g;
+               g = f;
+               f = e;
+               e = d + t1;
+               d = c;
+               c = b;
+               b = a;
+               a = t1 + t2;
+       }
+       s->h[0] += a;
+       s->h[1] += b;
+       s->h[2] += c;
+       s->h[3] += d;
+       s->h[4] += e;
+       s->h[5] += f;
+       s->h[6] += g;
+       s->h[7] += h;
+}
+
+static void
+pad(struct sha256 *s)
+{
+       unsigned r = s->len % 64;
+
+       s->buf[r++] = 0x80;
+       if (r > 56) {
+               memset(s->buf + r, 0, 64 - r);
+               r = 0;
+               processblock(s, s->buf);
+       }
+       memset(s->buf + r, 0, 56 - r);
+       s->len *= 8;
+       s->buf[56] = s->len >> 56;
+       s->buf[57] = s->len >> 48;
+       s->buf[58] = s->len >> 40;
+       s->buf[59] = s->len >> 32;
+       s->buf[60] = s->len >> 24;
+       s->buf[61] = s->len >> 16;
+       s->buf[62] = s->len >> 8;
+       s->buf[63] = s->len;
+       processblock(s, s->buf);
+}
+
+void
+sha256_init(void *ctx)
+{
+       struct sha256 *s = ctx;
+       s->len = 0;
+       s->h[0] = 0x6a09e667;
+       s->h[1] = 0xbb67ae85;
+       s->h[2] = 0x3c6ef372;
+       s->h[3] = 0xa54ff53a;
+       s->h[4] = 0x510e527f;
+       s->h[5] = 0x9b05688c;
+       s->h[6] = 0x1f83d9ab;
+       s->h[7] = 0x5be0cd19;
+}
+
+void
+sha256_sum_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 (file)
index 0000000..0a0e777
--- /dev/null
@@ -0,0 +1,26 @@
+/* public domain sha384 implementation based on fips180-3 */
+#include <stdint.h>
+#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 (file)
index 0000000..a5636c1
--- /dev/null
@@ -0,0 +1,26 @@
+/* public domain sha512/224 implementation based on fips180-3 */
+#include <stdint.h>
+#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 (file)
index 0000000..d4b8449
--- /dev/null
@@ -0,0 +1,26 @@
+/* public domain sha512/256 implementation based on fips180-3 */
+#include <stdint.h>
+#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 (file)
index 0000000..25264c7
--- /dev/null
@@ -0,0 +1,175 @@
+/* public domain sha256 implementation based on fips180-3 */
+
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../sha512.h"
+
+static uint64_t ror(uint64_t n, int k) { return (n >> k) | (n << (64-k)); }
+#define Ch(x,y,z)  (z ^ (x & (y ^ z)))
+#define Maj(x,y,z) ((x & y) | (z & (x | y)))
+#define S0(x)      (ror(x,28) ^ ror(x,34) ^ ror(x,39))
+#define S1(x)      (ror(x,14) ^ ror(x,18) ^ ror(x,41))
+#define R0(x)      (ror(x,1) ^ ror(x,8) ^ (x>>7))
+#define R1(x)      (ror(x,19) ^ ror(x,61) ^ (x>>6))
+
+static const uint64_t K[80] = {
+0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL,
+0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL,
+0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
+0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL,
+0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL,
+0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
+0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL,
+0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL,
+0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
+0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL,
+0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL,
+0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
+0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL,
+0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL,
+0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
+0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL,
+0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL,
+0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
+0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL,
+0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL
+};
+
+static void
+processblock(struct sha512 *s, const uint8_t *buf)
+{
+       uint64_t W[80], t1, t2, a, b, c, d, e, f, g, h;
+       int i;
+
+       for (i = 0; i < 16; i++) {
+               W[i] = (uint64_t)buf[8*i]<<56;
+               W[i] |= (uint64_t)buf[8*i+1]<<48;
+               W[i] |= (uint64_t)buf[8*i+2]<<40;
+               W[i] |= (uint64_t)buf[8*i+3]<<32;
+               W[i] |= (uint64_t)buf[8*i+4]<<24;
+               W[i] |= (uint64_t)buf[8*i+5]<<16;
+               W[i] |= (uint64_t)buf[8*i+6]<<8;
+               W[i] |= buf[8*i+7];
+       }
+       for (; i < 80; i++)
+               W[i] = R1(W[i-2]) + W[i-7] + R0(W[i-15]) + W[i-16];
+       a = s->h[0];
+       b = s->h[1];
+       c = s->h[2];
+       d = s->h[3];
+       e = s->h[4];
+       f = s->h[5];
+       g = s->h[6];
+       h = s->h[7];
+       for (i = 0; i < 80; i++) {
+               t1 = h + S1(e) + Ch(e,f,g) + K[i] + W[i];
+               t2 = S0(a) + Maj(a,b,c);
+               h = g;
+               g = f;
+               f = e;
+               e = d + t1;
+               d = c;
+               c = b;
+               b = a;
+               a = t1 + t2;
+       }
+       s->h[0] += a;
+       s->h[1] += b;
+       s->h[2] += c;
+       s->h[3] += d;
+       s->h[4] += e;
+       s->h[5] += f;
+       s->h[6] += g;
+       s->h[7] += h;
+}
+
+static void
+pad(struct sha512 *s)
+{
+       unsigned r = s->len % 128;
+
+       s->buf[r++] = 0x80;
+       if (r > 112) {
+               memset(s->buf + r, 0, 128 - r);
+               r = 0;
+               processblock(s, s->buf);
+       }
+       memset(s->buf + r, 0, 120 - r);
+       s->len *= 8;
+       s->buf[120] = s->len >> 56;
+       s->buf[121] = s->len >> 48;
+       s->buf[122] = s->len >> 40;
+       s->buf[123] = s->len >> 32;
+       s->buf[124] = s->len >> 24;
+       s->buf[125] = s->len >> 16;
+       s->buf[126] = s->len >> 8;
+       s->buf[127] = s->len;
+       processblock(s, s->buf);
+}
+
+void
+sha512_init(void *ctx)
+{
+       struct sha512 *s = ctx;
+       s->len = 0;
+       s->h[0] = 0x6a09e667f3bcc908ULL;
+       s->h[1] = 0xbb67ae8584caa73bULL;
+       s->h[2] = 0x3c6ef372fe94f82bULL;
+       s->h[3] = 0xa54ff53a5f1d36f1ULL;
+       s->h[4] = 0x510e527fade682d1ULL;
+       s->h[5] = 0x9b05688c2b3e6c1fULL;
+       s->h[6] = 0x1f83d9abfb41bd6bULL;
+       s->h[7] = 0x5be0cd19137e2179ULL;
+}
+
+void
+sha512_sum_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 (file)
index 0000000..26eb6bb
--- /dev/null
@@ -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 <string.h>
+#include <strings.h>
+
+#include "../util.h"
+
+char *
+strcasestr(const char *h, const char *n)
+{
+       size_t l = strlen(n);
+
+       for (; *h; h++)
+               if (!strncasecmp(h, n, l))
+                       return (char *)h;
+
+       return 0;
+}
diff --git a/source/sbase/libutil/strlcat.c b/source/sbase/libutil/strlcat.c
new file mode 100644 (file)
index 0000000..bf263b8
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+#include <sys/types.h>
+
+#include "../util.h"
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t siz)
+{
+       char *d = dst;
+       const char *s = src;
+       size_t n = siz;
+       size_t dlen;
+       /* Find the end of dst and adjust bytes left but don't go past end */
+       while (n-- != 0 && *d != '\0')
+               d++;
+       dlen = d - dst;
+       n = siz - dlen;
+       if (n == 0)
+               return(dlen + strlen(s));
+       while (*s != '\0') {
+               if (n != 1) {
+                       *d++ = *s;
+                       n--;
+               }
+               s++;
+       }
+       *d = '\0';
+       return(dlen + (s - src)); /* count does not include NUL */
+}
+
+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 (file)
index 0000000..44b618a
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+#include <sys/types.h>
+
+#include "../util.h"
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+       char *d = dst;
+       const char *s = src;
+       size_t n = siz;
+       /* Copy as many bytes as will fit */
+       if (n != 0) {
+               while (--n != 0) {
+                       if ((*d++ = *s++) == '\0')
+                               break;
+               }
+       }
+       /* Not enough room in dst, add NUL and traverse rest of src */
+       if (n == 0) {
+               if (siz != 0)
+                       *d = '\0'; /* NUL-terminate dst */
+               while (*s++)
+                       ;
+       }
+       return(s - src - 1); /* count does not include NUL */
+}
+
+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 (file)
index 0000000..d9f0644
--- /dev/null
@@ -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 <string.h>
+
+#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 (file)
index 0000000..c0ac401
--- /dev/null
@@ -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 <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#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 (file)
index 0000000..90a62c3
--- /dev/null
@@ -0,0 +1,74 @@
+/* See LICENSE file for copyright and license details. */
+#include <string.h>
+
+#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 (file)
index 0000000..eaf4242
--- /dev/null
@@ -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 (file)
index 0000000..dc07ea6
--- /dev/null
@@ -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 (file)
index 0000000..e169f0a
--- /dev/null
@@ -0,0 +1,24 @@
+/* See LICENSE file for copyright and license details. */
+#include <unistd.h>
+
+#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 (file)
index 0000000..8d299da
--- /dev/null
@@ -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 (file)
index 0000000..ab1ec4e
--- /dev/null
@@ -0,0 +1,101 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..fee5595
--- /dev/null
@@ -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 (file)
index 0000000..603da04
--- /dev/null
@@ -0,0 +1,91 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#define SYSLOG_NAMES
+#include <syslog.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static int
+decodetable(CODE *table, char *name)
+{
+       CODE *c;
+
+       for (c = table; c->c_name; c++)
+               if (!strcasecmp(name, c->c_name))
+                       return c->c_val;
+       eprintf("invalid priority name: %s\n", name);
+
+       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, "<stdin>");
+}
diff --git a/source/sbase/logname.1 b/source/sbase/logname.1
new file mode 100644 (file)
index 0000000..cac088e
--- /dev/null
@@ -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 (file)
index 0000000..2a591cd
--- /dev/null
@@ -0,0 +1,29 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <unistd.h>
+
+#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, "<stdout>");
+}
diff --git a/source/sbase/ls.1 b/source/sbase/ls.1
new file mode 100644 (file)
index 0000000..2e2586e
--- /dev/null
@@ -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 (file)
index 0000000..3268a99
--- /dev/null
@@ -0,0 +1,483 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <dirent.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#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, "<stdout>") | ret);
+}
diff --git a/source/sbase/md5.h b/source/sbase/md5.h
new file mode 100644 (file)
index 0000000..0b5005e
--- /dev/null
@@ -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 (file)
index 0000000..bd02bb7
--- /dev/null
@@ -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 (file)
index 0000000..86fb40f
--- /dev/null
@@ -0,0 +1,42 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "crypt.h"
+#include "md5.h"
+#include "util.h"
+
+static struct md5 s;
+struct crypt_ops md5_ops = {
+       md5_init,
+       md5_update,
+       md5_sum,
+       &s,
+};
+
+static void
+usage(void)
+{
+       eprintf("usage: %s [-c] [file ...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+       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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/mkdir.1 b/source/sbase/mkdir.1
new file mode 100644 (file)
index 0000000..da6198e
--- /dev/null
@@ -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 (file)
index 0000000..f5da527
--- /dev/null
@@ -0,0 +1,53 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdlib.h>
+
+#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 (file)
index 0000000..0c6ef94
--- /dev/null
@@ -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 (file)
index 0000000..a09f4f5
--- /dev/null
@@ -0,0 +1,47 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <stdlib.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+       eprintf("usage: %s [-m mode] name ...\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+       mode_t mode = 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 (file)
index 0000000..77a0ae8
--- /dev/null
@@ -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 (file)
index 0000000..a3076ba
--- /dev/null
@@ -0,0 +1,92 @@
+/* See LICENSE file for copyright and license details. */
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+       eprintf("usage: %s [-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, "<stdout>");
+       return 0;
+}
diff --git a/source/sbase/mv.1 b/source/sbase/mv.1
new file mode 100644 (file)
index 0000000..c69c367
--- /dev/null
@@ -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 (file)
index 0000000..2cf98d1
--- /dev/null
@@ -0,0 +1,62 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdio.h>
+
+#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 (file)
index 0000000..4be627a
--- /dev/null
@@ -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 (file)
index 0000000..d036e26
--- /dev/null
@@ -0,0 +1,56 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/resource.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..183ada6
--- /dev/null
@@ -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 (file)
index 0000000..9a289b0
--- /dev/null
@@ -0,0 +1,212 @@
+/* See LICENSE file for copyright and license details. */
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, &section)) {
+                       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>", stdin);
+       } else {
+               if (!strcmp(argv[0], "-")) {
+                       argv[0] = "<stdin>";
+                       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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/nohup.1 b/source/sbase/nohup.1
new file mode 100644 (file)
index 0000000..78930a0
--- /dev/null
@@ -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 (file)
index 0000000..080b210
--- /dev/null
@@ -0,0 +1,45 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+       eprintf("usage: %s cmd [arg ...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+       int 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 (file)
index 0000000..99f3e32
--- /dev/null
@@ -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 (file)
index 0000000..5a625b6
--- /dev/null
@@ -0,0 +1,293 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, "<stdin>", 1);
+       } else {
+               for (; *argv; argc--, argv++) {
+                       if (!strcmp(*argv, "-")) {
+                               *argv = "<stdin>";
+                               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, "<stdin>") | fshut(stdout, "<stdout>") |
+              fshut(stderr, "<stderr>");
+
+       return ret;
+}
diff --git a/source/sbase/paste.1 b/source/sbase/paste.1
new file mode 100644 (file)
index 0000000..4ca3651
--- /dev/null
@@ -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 (file)
index 0000000..77fc090
--- /dev/null
@@ -0,0 +1,141 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdlib.h>
+#include <string.h>
+
+#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, "<stdout>");
+                               d = (d + 1) % delimlen;
+                       }
+
+                       if (c != '\n')
+                               efputrune(&c, stdout, "<stdout>");
+                       last = c;
+               }
+
+               if (last == '\n')
+                       efputrune(&last, stdout, "<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, "<stdout>");
+                       last = i;
+                       if (c == '\n') {
+                               if (i != fdescrlen - 1)
+                                       c = d;
+                               efputrune(&c, stdout, "<stdout>");
+                               break;
+                       }
+                       efputrune(&c, stdout, "<stdout>");
+               }
+
+               if (c == 0 && last != -1) {
+                       if (i == fdescrlen - 1)
+                               putchar('\n');
+                       else
+                               efputrune(&d, stdout, "<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] = "<stdin>";
+                       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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/pathchk.1 b/source/sbase/pathchk.1
new file mode 100644 (file)
index 0000000..4cd0e80
--- /dev/null
@@ -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 (file)
index 0000000..a72e8fd
--- /dev/null
@@ -0,0 +1,114 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <stdint.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#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 (file)
index 0000000..0cd3f85
--- /dev/null
@@ -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 (file)
index 0000000..a61efe9
--- /dev/null
@@ -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 (file)
index 0000000..2c1e711
--- /dev/null
@@ -0,0 +1,30 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+
+#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, "<stdout>") || ret;
+}
diff --git a/source/sbase/printf.1 b/source/sbase/printf.1
new file mode 100644 (file)
index 0000000..78ffb1e
--- /dev/null
@@ -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 (file)
index 0000000..2e24817
--- /dev/null
@@ -0,0 +1,182 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, "<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, "<stdout>") | ret;
+}
diff --git a/source/sbase/pwd.1 b/source/sbase/pwd.1
new file mode 100644 (file)
index 0000000..921a0e1
--- /dev/null
@@ -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 (file)
index 0000000..c6a4497
--- /dev/null
@@ -0,0 +1,50 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#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, "<stdout>");
+}
diff --git a/source/sbase/queue.h b/source/sbase/queue.h
new file mode 100644 (file)
index 0000000..f8f09bf
--- /dev/null
@@ -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 (file)
index 0000000..46b4cad
--- /dev/null
@@ -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 (file)
index 0000000..d059584
--- /dev/null
@@ -0,0 +1,54 @@
+/* See LICENSE file for copyright and license details. */
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+       eprintf("usage: %s [-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, "<stdout>");
+}
diff --git a/source/sbase/renice.1 b/source/sbase/renice.1
new file mode 100644 (file)
index 0000000..ad37b43
--- /dev/null
@@ -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 (file)
index 0000000..358c560
--- /dev/null
@@ -0,0 +1,93 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/resource.h>
+
+#include <errno.h>
+#include <pwd.h>
+#include <stdlib.h>
+
+#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 (file)
index 0000000..831a6e4
--- /dev/null
@@ -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 (file)
index 0000000..42bc022
--- /dev/null
@@ -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 (file)
index 0000000..0cbb5a3
--- /dev/null
@@ -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 (file)
index 0000000..4422454
--- /dev/null
@@ -0,0 +1,49 @@
+/* See LICENSE file for copyright and license details. */
+#include <libgen.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..94e7f9f
--- /dev/null
@@ -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 (file)
index 0000000..6cfd121
--- /dev/null
@@ -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 <ctype.h>
+#include <errno.h>
+#include <regex.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <backslash><newline>");
+               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>");
+       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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/seq.1 b/source/sbase/seq.1
new file mode 100644 (file)
index 0000000..f8f7ee9
--- /dev/null
@@ -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 (file)
index 0000000..70763d1
--- /dev/null
@@ -0,0 +1,147 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, "<stdout>");
+}
diff --git a/source/sbase/setsid.1 b/source/sbase/setsid.1
new file mode 100644 (file)
index 0000000..d43bcfc
--- /dev/null
@@ -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 (file)
index 0000000..4c885a4
--- /dev/null
@@ -0,0 +1,40 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+       eprintf("usage: %s cmd [arg ...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+       int savederrno;
+
+       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 (file)
index 0000000..8631777
--- /dev/null
@@ -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 (file)
index 0000000..c459918
--- /dev/null
@@ -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 (file)
index 0000000..4f3ae77
--- /dev/null
@@ -0,0 +1,41 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+
+#include "crypt.h"
+#include "sha1.h"
+#include "util.h"
+
+static struct sha1 s;
+struct crypt_ops sha1_ops = {
+       sha1_init,
+       sha1_update,
+       sha1_sum,
+       &s,
+};
+
+static void
+usage(void)
+{
+       eprintf("usage: %s [-c] [file ...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+       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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/sha224.h b/source/sbase/sha224.h
new file mode 100644 (file)
index 0000000..d7f4053
--- /dev/null
@@ -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 (file)
index 0000000..ff7ec97
--- /dev/null
@@ -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 (file)
index 0000000..5c4a6cb
--- /dev/null
@@ -0,0 +1,41 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+
+#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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/sha256.h b/source/sbase/sha256.h
new file mode 100644 (file)
index 0000000..5968b8e
--- /dev/null
@@ -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 (file)
index 0000000..8a93a02
--- /dev/null
@@ -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 (file)
index 0000000..d863539
--- /dev/null
@@ -0,0 +1,41 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+
+#include "crypt.h"
+#include "sha256.h"
+#include "util.h"
+
+static struct sha256 s;
+struct crypt_ops sha256_ops = {
+       sha256_init,
+       sha256_update,
+       sha256_sum,
+       &s,
+};
+
+static void
+usage(void)
+{
+       eprintf("usage: %s [-c] [file ...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+       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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/sha384.h b/source/sbase/sha384.h
new file mode 100644 (file)
index 0000000..2ab9bc4
--- /dev/null
@@ -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 (file)
index 0000000..c0aa5b6
--- /dev/null
@@ -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 (file)
index 0000000..f975b61
--- /dev/null
@@ -0,0 +1,41 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+
+#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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/sha512-224.h b/source/sbase/sha512-224.h
new file mode 100644 (file)
index 0000000..8364fc5
--- /dev/null
@@ -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 (file)
index 0000000..5092582
--- /dev/null
@@ -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 (file)
index 0000000..6e4a9d6
--- /dev/null
@@ -0,0 +1,41 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+
+#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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/sha512-256.h b/source/sbase/sha512-256.h
new file mode 100644 (file)
index 0000000..eb0b731
--- /dev/null
@@ -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 (file)
index 0000000..db0406d
--- /dev/null
@@ -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 (file)
index 0000000..c2d582e
--- /dev/null
@@ -0,0 +1,41 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+
+#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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/sha512.h b/source/sbase/sha512.h
new file mode 100644 (file)
index 0000000..c761712
--- /dev/null
@@ -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 (file)
index 0000000..724e99d
--- /dev/null
@@ -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 (file)
index 0000000..65a0c2c
--- /dev/null
@@ -0,0 +1,41 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+
+#include "crypt.h"
+#include "sha512.h"
+#include "util.h"
+
+static struct sha512 s;
+struct crypt_ops sha512_ops = {
+       sha512_init,
+       sha512_update,
+       sha512_sum,
+       &s,
+};
+
+static void
+usage(void)
+{
+       eprintf("usage: %s [-c] [file ...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+       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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/sleep.1 b/source/sbase/sleep.1
new file mode 100644 (file)
index 0000000..2b78bfb
--- /dev/null
@@ -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 (file)
index 0000000..b1028ed
--- /dev/null
@@ -0,0 +1,27 @@
+/* See LICENSE file for copyright and license details. */
+#include <unistd.h>
+
+#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 (file)
index 0000000..7bd6d62
--- /dev/null
@@ -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 (file)
index 0000000..90ee911
--- /dev/null
@@ -0,0 +1,434 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, "<stdin>") && !ret)
+                               ret = 1;
+               } else {
+                       getlines(stdin, &linebuf);
+               }
+       } else for (; *argv; argc--, argv++) {
+               if (!strcmp(*argv, "-")) {
+                       *argv = "<stdin>";
+                       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, "<stdin>") | fshut(stdout, "<stdout>") |
+           fshut(stderr, "<stderr>"))
+               ret = 2;
+
+       return ret;
+}
diff --git a/source/sbase/split.1 b/source/sbase/split.1
new file mode 100644 (file)
index 0000000..3cd434f
--- /dev/null
@@ -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 (file)
index 0000000..7033a28
--- /dev/null
@@ -0,0 +1,111 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, "<file>");
+       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, "<infile>");
+       ret |= out && (out != stdout) && fshut(out, "<outfile>");
+       ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/sponge.1 b/source/sbase/sponge.1
new file mode 100644 (file)
index 0000000..5249567
--- /dev/null
@@ -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 (file)
index 0000000..baeac7f
--- /dev/null
@@ -0,0 +1,36 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+
+#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, "<stdin>", tmpfp, "<tmpfile>");
+       rewind(tmpfp);
+
+       if (!(fp = fopen(argv[0], "w")))
+               eprintf("fopen %s:", argv[0]);
+       concat(tmpfp, "<tmpfile>", fp, argv[0]);
+
+       ret |= fshut(fp, argv[0]) | fshut(tmpfp, "<tmpfile>");
+
+       return ret;
+}
diff --git a/source/sbase/strings.1 b/source/sbase/strings.1
new file mode 100644 (file)
index 0000000..21ad833
--- /dev/null
@@ -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 (file)
index 0000000..76b3316
--- /dev/null
@@ -0,0 +1,100 @@
+/* See LICENSE file for copyright and license details. */
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, "<stdout>");
+                       continue;
+               }
+               printf(format, (long)off - i);
+               for (i = 0; i < len; i++)
+                       efputrune(rbuf + i, stdout, "<stdout>");
+               efputrune(&r, stdout, "<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, "<stdin>", len);
+       } else {
+               for (; *argv; argc--, argv++) {
+                       if (!strcmp(*argv, "-")) {
+                               *argv = "<stdin>";
+                               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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/sync.1 b/source/sbase/sync.1
new file mode 100644 (file)
index 0000000..d24bb5c
--- /dev/null
@@ -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 (file)
index 0000000..15e53f4
--- /dev/null
@@ -0,0 +1,22 @@
+/* See LICENSE file for copyright and license details. */
+#include <unistd.h>
+
+#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 (file)
index 0000000..5bceb33
--- /dev/null
@@ -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 (file)
index 0000000..433404d
--- /dev/null
@@ -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 (file)
index 0000000..711707f
--- /dev/null
@@ -0,0 +1,169 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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, "<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, "<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, "<stdin>", n);
+       else {
+               if ((many = argc > 1) && fflag)
+                       usage();
+               for (newline = 0; *argv; argc--, argv++) {
+                       if (!strcmp(*argv, "-")) {
+                               *argv = "<stdin>";
+                               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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/tar.1 b/source/sbase/tar.1
new file mode 100644 (file)
index 0000000..7cc34b4
--- /dev/null
@@ -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 (file)
index 0000000..71719b0
--- /dev/null
@@ -0,0 +1,593 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <libgen.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..6fb4e5a
--- /dev/null
@@ -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 (file)
index 0000000..399824c
--- /dev/null
@@ -0,0 +1,53 @@
+/* See LICENSE file for copyright and license details. */
+#include <signal.h>
+#include <stdio.h>
+
+#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] : "<stdout>");
+               }
+       }
+
+       ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/test.1 b/source/sbase/test.1
new file mode 100644 (file)
index 0000000..539016a
--- /dev/null
@@ -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 (file)
index 0000000..3fe12c3
--- /dev/null
@@ -0,0 +1,243 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..bceda52
--- /dev/null
@@ -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 (file)
index 0000000..424519e
--- /dev/null
@@ -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 (file)
index 0000000..0a099ff
--- /dev/null
@@ -0,0 +1,309 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netdb.h>
+#include <netinet/in.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..d5327ff
--- /dev/null
@@ -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 (file)
index 0000000..60a8c8d
--- /dev/null
@@ -0,0 +1,73 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/times.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..38c99e2
--- /dev/null
@@ -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 (file)
index 0000000..b957fa5
--- /dev/null
@@ -0,0 +1,164 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <utime.h>
+
+#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(&current);
+       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, &times[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 (file)
index 0000000..a9b25c6
--- /dev/null
@@ -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 (file)
index 0000000..361ac40
--- /dev/null
@@ -0,0 +1,277 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdlib.h>
+
+#include "utf.h"
+#include "util.h"
+
+static int cflag = 0;
+static int dflag = 0;
+static int sflag = 0;
+
+struct range {
+       Rune   start;
+       Rune   end;
+       size_t quant;
+};
+
+static struct {
+       char    *name;
+       int    (*check)(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, "<stdin>")) {
+               ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<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, "<stdout>");
+       goto read;
+}
diff --git a/source/sbase/true.1 b/source/sbase/true.1
new file mode 100644 (file)
index 0000000..8f09c40
--- /dev/null
@@ -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 (file)
index 0000000..cb081ec
--- /dev/null
@@ -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 (file)
index 0000000..2f8f0a1
--- /dev/null
@@ -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 (file)
index 0000000..d093df8
--- /dev/null
@@ -0,0 +1,209 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#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 = "<stdin>";
+       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, "<stdout>") | fshut(stderr, "<stderr>"))
+               ret = 2;
+
+       return ret;
+}
diff --git a/source/sbase/tty.1 b/source/sbase/tty.1
new file mode 100644 (file)
index 0000000..e63d28c
--- /dev/null
@@ -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 (file)
index 0000000..5afec79
--- /dev/null
@@ -0,0 +1,27 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <unistd.h>
+
+#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, "<stdout>") || !tty;
+}
diff --git a/source/sbase/uname.1 b/source/sbase/uname.1
new file mode 100644 (file)
index 0000000..5a58ca2
--- /dev/null
@@ -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 (file)
index 0000000..dfc979e
--- /dev/null
@@ -0,0 +1,59 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/utsname.h>
+
+#include <stdio.h>
+
+#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, "<stdout>");
+}
diff --git a/source/sbase/unexpand.1 b/source/sbase/unexpand.1
new file mode 100644 (file)
index 0000000..62e33d0
--- /dev/null
@@ -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 (file)
index 0000000..1818691
--- /dev/null
@@ -0,0 +1,174 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, "<stdout>");
+               r = ' ';
+               for (; last < col; last++)
+                       efputrune(&r, stdout, "<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, "<stdout>");
+                       last = tablist[j];
+               }
+               r = ' ';
+               for (; last < col; last++)
+                       efputrune(&r, stdout, "<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, "<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>", stdin);
+       } else {
+               for (; *argv; argc--, argv++) {
+                       if (!strcmp(*argv, "-")) {
+                               *argv = "<stdin>";
+                               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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/uniq.1 b/source/sbase/uniq.1
new file mode 100644 (file)
index 0000000..993fe44
--- /dev/null
@@ -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 (file)
index 0000000..f1ad6a7
--- /dev/null
@@ -0,0 +1,144 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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] = { "<stdin>", "<stdout>" };
+
+       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 (file)
index 0000000..a327b24
--- /dev/null
@@ -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 (file)
index 0000000..bdc6061
--- /dev/null
@@ -0,0 +1,24 @@
+/* See LICENSE file for copyright and license details. */
+#include <unistd.h>
+
+#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 (file)
index 0000000..a74be94
--- /dev/null
@@ -0,0 +1,67 @@
+/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#include <stdio.h>
+
+typedef int Rune;
+
+enum {
+       UTFmax    = 6,       /* maximum bytes per rune */
+       Runeself  = 0x80,    /* rune and utf are equal (<) */
+       Runeerror = 0xFFFD,  /* decoding error in utf */
+       Runemax   = 0x10FFFF /* maximum rune value */
+};
+
+int runetochar(char *, const Rune *);
+int chartorune(Rune *, const char *);
+int charntorune(Rune *, const char *, size_t);
+int runelen(const Rune);
+size_t runenlen(const Rune *, size_t);
+int fullrune(const char *, size_t);
+char *utfecpy(char *, char *, const char *);
+size_t utflen(const char *);
+size_t utfnlen(const char *, size_t);
+char *utfrune(const char *, Rune);
+char *utfrrune(const char *, Rune);
+char *utfutf(const char *, const char *);
+
+int 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 (file)
index 0000000..b5860dc
--- /dev/null
@@ -0,0 +1,80 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/types.h>
+
+#include <regex.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#include "arg.h"
+#include "compat.h"
+
+#define UTF8_POINT(c) (((c) & 0xc0) != 0x80)
+
+#undef MIN
+#define MIN(x,y)  ((x) < (y) ? (x) : (y))
+#undef MAX
+#define MAX(x,y)  ((x) > (y) ? (x) : (y))
+#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 (file)
index 0000000..05b5deb
--- /dev/null
@@ -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 (file)
index 0000000..1d0bf72
--- /dev/null
@@ -0,0 +1,282 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 = "<stdin>";
+       } 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) ? "<stdin>" : argv[0]);
+       ret |= fshut(nfp, (nfp == stdout) ? "<stdout>" : fname);
+
+       return ret;
+}
diff --git a/source/sbase/uuencode.1 b/source/sbase/uuencode.1
new file mode 100644 (file)
index 0000000..2b3729c
--- /dev/null
@@ -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 (file)
index 0000000..b531720
--- /dev/null
@@ -0,0 +1,129 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#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], "<stdin>");
+       } 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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/wc.1 b/source/sbase/wc.1
new file mode 100644 (file)
index 0000000..0cf7708
--- /dev/null
@@ -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 (file)
index 0000000..56f2fda
--- /dev/null
@@ -0,0 +1,117 @@
+/* See LICENSE file for copyright and license details. */
+#include <string.h>
+
+#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 = "<stdin>";
+                               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, "<stdin>") | fshut(stdout, "<stdout>");
+
+       return ret;
+}
diff --git a/source/sbase/which.1 b/source/sbase/which.1
new file mode 100644 (file)
index 0000000..ececa45
--- /dev/null
@@ -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 (file)
index 0000000..1975710
--- /dev/null
@@ -0,0 +1,89 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..535c927
--- /dev/null
@@ -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 (file)
index 0000000..11ee7e2
--- /dev/null
@@ -0,0 +1,37 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <pwd.h>
+
+#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, "<stdout>");
+}
diff --git a/source/sbase/xargs.1 b/source/sbase/xargs.1
new file mode 100644 (file)
index 0000000..47cc194
--- /dev/null
@@ -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 (file)
index 0000000..0d5dd53
--- /dev/null
@@ -0,0 +1,280 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 <stdin>:");
+
+       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, "<stdin>") | fshut(stdout, "<stdout>")))
+               ret = 123;
+
+       return ret;
+}
diff --git a/source/sbase/xinstall.1 b/source/sbase/xinstall.1
new file mode 100644 (file)
index 0000000..1a727d3
--- /dev/null
@@ -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 (file)
index 0000000..f5f6956
--- /dev/null
@@ -0,0 +1,256 @@
+/* See LICENSE file for copyright and license details. */
+#include <grp.h>
+#include <pwd.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#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 (file)
index 0000000..87e390a
--- /dev/null
@@ -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 (file)
index 0000000..cbd81dc
--- /dev/null
@@ -0,0 +1,19 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+
+#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 */
+}
index fce53698cc779d98c2cb57ce3203ba28f78cd43c..c14ef9bf0bb7752c1eea5638fa045ea007573394 100644 (file)
@@ -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        \