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)
--- /dev/null
+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>
--- /dev/null
+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
--- /dev/null
+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/
--- /dev/null
+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)
+
--- /dev/null
+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/
--- /dev/null
+/*
+ * 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
--- /dev/null
+.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.
--- /dev/null
+/* 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>");
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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>");
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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
--- /dev/null
+/* 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));
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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".
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* See LICENSE file for copyright and license details. */
+#include <limits.h>
+
+#ifndef HOST_NAME_MAX
+#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
+#endif
--- /dev/null
+# 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
--- /dev/null
+#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
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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>");
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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>");
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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>");
+}
--- /dev/null
+.Dd 2015-12-14
+.Dt ED 1
+.Os sbase
+.Sh NAME
+.Nm ed
+.Nd text editor
+.Sh SYNOPSIS
+.Nm
+is the standard text editor.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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>");
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* See LICENSE file for copyright and license details. */
+int
+main(void)
+{
+ return 1;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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 *);
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+#!/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
+!
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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
--- /dev/null
+/* 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>");
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+AWK = awk
+UNICODE = http://unicode.org/Public/UCD/latest/ucd/UnicodeData.txt
+
+default:
+ @echo Downloading and parsing $(UNICODE)
+ @curl -\# $(UNICODE) | $(AWK) -f mkrunetype.awk
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* Automatically generated by mkrunetype.awk */
+#include "../utf.h"
+#include "runetype.h"
+
+int
+isalnumrune(Rune r)
+{
+ return isalpharune(r) || isdigitrune(r);
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* Automatically generated by mkrunetype.awk */
+#include "../utf.h"
+#include "runetype.h"
+
+int
+isblankrune(Rune r)
+{
+ return r == ' ' || r == '\t';
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* Automatically generated by mkrunetype.awk */
+#include "../utf.h"
+#include "runetype.h"
+
+int
+isgraphrune(Rune r)
+{
+ return !isspacerune(r) && isprintrune(r);
+}
--- /dev/null
+/* 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));
+}
--- /dev/null
+/* Automatically generated by mkrunetype.awk */
+#include "../utf.h"
+#include "runetype.h"
+
+int
+ispunctrune(Rune r)
+{
+ return isgraphrune(r) && !isalnumrune(r);
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+# 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";
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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];
+}
--- /dev/null
+/* 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 *);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+ }
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
+ }
+}
--- /dev/null
+/* 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);
+ }
+}
--- /dev/null
+#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);
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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++;
+ }
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* $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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+#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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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;
+ }
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/* $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);
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+#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
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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>");
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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>");
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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]);
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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));
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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, §ion)) {
+ if ((section >= oldsection) && !pflag)
+ number = startnum;
+ continue;
+ }
+
+ switch (type[section]) {
+ case 't':
+ if (line.data[0] != '\n')
+ donumber = 1;
+ break;
+ case 'p':
+ if (!regexec(preg + section, line.data, 0, NULL, 0))
+ donumber = 1;
+ break;
+ case 'a':
+ if (line.data[0] == '\n' && bl < blines) {
+ ++bl;
+ } else {
+ donumber = 1;
+ bl = 1;
+ }
+ }
+
+ if (donumber) {
+ printf(format, width, number);
+ fwrite(sep, 1, seplen, stdout);
+ number += incr;
+ }
+ fwrite(line.data, 1, line.len, stdout);
+ }
+ free(line.data);
+ if (ferror(fp))
+ eprintf("getline %s:", fname);
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-p] [-b type] [-d delim] [-f type]\n"
+ " [-h type] [-i num] [-l num] [-n format]\n"
+ " [-s sep] [-v num] [-w num] [file]\n", argv0);
+}
+
+static char
+getlinetype(char *type, regex_t *preg)
+{
+ if (type[0] == 'p')
+ eregcomp(preg, type + 1, REG_NOSUB);
+ else if (!type[0] || !strchr("ant", type[0]))
+ usage();
+
+ return type[0];
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp = NULL;
+ size_t s;
+ int ret = 0;
+ char *d, *formattype, *formatblit;
+
+ ARGBEGIN {
+ case 'd':
+ switch (utflen((d = EARGF(usage())))) {
+ case 0:
+ eprintf("empty logical page delimiter\n");
+ case 1:
+ s = strlen(d);
+ delim = emalloc(s + 1 + 1);
+ estrlcpy(delim, d, s + 1 + 1);
+ estrlcat(delim, ":", s + 1 + 1);
+ delimlen = s + 1;
+ break;
+ default:
+ delim = d;
+ delimlen = strlen(delim);
+ break;
+ }
+ break;
+ case 'f':
+ type[0] = getlinetype(EARGF(usage()), preg);
+ break;
+ case 'b':
+ type[1] = getlinetype(EARGF(usage()), preg + 1);
+ break;
+ case 'h':
+ type[2] = getlinetype(EARGF(usage()), preg + 2);
+ break;
+ case 'i':
+ incr = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, SIZE_MAX));
+ break;
+ case 'l':
+ blines = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, SIZE_MAX));
+ break;
+ case 'n':
+ formattype = EARGF(usage());
+ estrlcpy(format, "%", sizeof(format));
+
+ if (!strcmp(formattype, "ln")) {
+ formatblit = "-";
+ } else if (!strcmp(formattype, "rn")) {
+ formatblit = "";
+ } else if (!strcmp(formattype, "rz")) {
+ formatblit = "0";
+ } else {
+ eprintf("%s: bad format\n", formattype);
+ }
+
+ estrlcat(format, formatblit, sizeof(format));
+ estrlcat(format, "*ld", sizeof(format));
+ break;
+ case 'p':
+ pflag = 1;
+ break;
+ case 's':
+ sep = EARGF(usage());
+ seplen = unescape(sep);
+ break;
+ case 'v':
+ startnum = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, SIZE_MAX));
+ break;
+ case 'w':
+ width = estrtonum(EARGF(usage()), 1, INT_MAX);
+ break;
+ default:
+ usage();
+ } ARGEND
+
+ if (argc > 1)
+ usage();
+
+ if (!argc) {
+ nl("<stdin>", 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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));
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+#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
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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>");
+}
--- /dev/null
+/* $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_ */
--- /dev/null
+.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
--- /dev/null
+/* 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>");
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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
--- /dev/null
+/* 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>");
+}
--- /dev/null
+.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
--- /dev/null
+/* 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));
+}
--- /dev/null
+/* 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]);
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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]);
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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]);
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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]);
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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]);
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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]);
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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]);
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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 .
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+#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
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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 *);
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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(¤t);
+ t.tm_isdst = -1;
+
+ switch (len) {
+ /* -t flag argument */
+ case 8:
+ t.tm_sec = 0;
+ t.tm_year = cur->tm_year;
+ format = "%m%d%H%M";
+ break;
+ case 10:
+ t.tm_sec = 0;
+ format = "%y%m%d%H%M";
+ break;
+ case 11:
+ t.tm_year = cur->tm_year;
+ format = "%m%d%H%M.%S";
+ break;
+ case 12:
+ t.tm_sec = 0;
+ format = "%Y%m%d%H%M";
+ break;
+ case 13:
+ format = "%y%m%d%H%M.%S";
+ break;
+ case 15:
+ format = "%Y%m%d%H%M.%S";
+ break;
+ /* -d flag argument */
+ case 19:
+ format = "%Y-%m-%dT%H:%M:%S";
+ break;
+ case 20:
+ /* only Zulu-timezone supported */
+ if (str[19] != 'Z')
+ eprintf("Invalid time zone\n");
+ str[19] = 0;
+ zulu = 1;
+ format = "%Y-%m-%dT%H:%M:%S";
+ break;
+ default:
+ eprintf("Invalid date format length\n", str);
+ }
+
+ if (!strptime(str, format, &t))
+ weprintf("strptime %s: Invalid date format\n", str);
+ if (zulu) {
+ t.tm_hour += t.tm_gmtoff / 60;
+ t.tm_gmtoff = 0;
+ t.tm_zone = "Z";
+ }
+
+ return mktime(&t);
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-acm] [-d time | -r ref_file | -t time | -T time] "
+ "file ...\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct stat st;
+ char *ref = NULL;
+ clock_gettime(CLOCK_REALTIME, ×[0]);
+
+ ARGBEGIN {
+ case 'a':
+ aflag = 1;
+ break;
+ case 'c':
+ cflag = 1;
+ break;
+ case 'd':
+ case 't':
+ times[0].tv_sec = parsetime(EARGF(usage()), times[0].tv_sec);
+ break;
+ case 'm':
+ mflag = 1;
+ break;
+ case 'r':
+ ref = EARGF(usage());
+ if (stat(ref, &st) < 0)
+ eprintf("stat '%s':", ref);
+ times[0] = st.st_atim;
+ times[1] = st.st_mtim;
+ break;
+ case 'T':
+ times[0].tv_sec = estrtonum(EARGF(usage()), 0, LLONG_MAX);
+ break;
+ default:
+ usage();
+ } ARGEND
+
+ if (!argc)
+ usage();
+ if (!aflag && !mflag)
+ aflag = mflag = 1;
+ if (!ref)
+ times[1] = times[0];
+
+ for (; *argv; argc--, argv++)
+ touch(*argv);
+
+ return 0;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* See LICENSE file for copyright and license details. */
+int
+main(void)
+{
+ return 0;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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>");
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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 *);
--- /dev/null
+/* 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);
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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>");
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+.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.
--- /dev/null
+/* 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 */
+}
ctrlaltdel \
dd \
df \
- dmesg \
eject \
fallocate \
free \
freeramdisk \
fsfreeze \
- getty \
halt \
hwclock \
id \
killall5 \
last \
lastlog \
- login \
lsmod \
lsusb \
mesg \