diff options
Diffstat (limited to 'util')
282 files changed, 28424 insertions, 3 deletions
diff --git a/util/nvmutil/nvmutil.c b/util/nvmutil/nvmutil.c index 12aabe4d..05459bb7 100644 --- a/util/nvmutil/nvmutil.c +++ b/util/nvmutil/nvmutil.c @@ -40,7 +40,7 @@ size_t partsize, gbe[2];  uint8_t nvmPartChanged[2] = {0, 0}, do_read[2] = {1, 1};  int flags, rfd, fd, part; -const char *strMac = NULL, *strRMac = "??:??:??:??:??:??", *filename = NULL; +const char *strMac = NULL, *strRMac = "xx:xx:xx:xx:xx:xx", *filename = NULL;  typedef struct op {  	char *str; @@ -278,8 +278,11 @@ parseMacString(const char *strMac, uint16_t *mac)  				err(errno = EINVAL, "Invalid character '%c'",  				    strMac[i + nib]); +			/* If random, ensure that local/unicast bits are set */  			if ((byte == 0) && (nib == 1)) -				if (strMac[i + nib] == '?') /* ?=random */ +				if ((strMac[i + nib] == '?') || +				    (strMac[i + nib] == 'x') || +				    (strMac[i + nib] == 'X')) /* random */  					h = (h & 0xE) | 2; /* local, unicast */  			mac[byte >> 1] |= ((uint16_t ) h) @@ -302,7 +305,10 @@ hextonum(char ch)  		return ch - 'A' + 10;  	else if ((ch >= 'a') && (ch <= 'f'))  		return ch - 'a' + 10; -	return (ch == '?') ? rhex() : 16; +	else if ((ch == '?') || (ch == 'x') || (ch == 'X')) +		return rhex(); /* random hex value */ +	else +		return 16; /* error: invalid character */  }  uint8_t diff --git a/util/sbase/.gitignore b/util/sbase/.gitignore new file mode 100644 index 00000000..247ecd36 --- /dev/null +++ b/util/sbase/.gitignore @@ -0,0 +1,103 @@ +*.o +/build +/getconf.h +/libutf.a +/libutil.a +/basename +/cal +/cat +/chgrp +/chmod +/chown +/chroot +/cksum +/cmp +/cols +/comm +/cp +/cron +/cut +/date +/dd +/dirname +/du +/echo +/ed +/env +/expand +/expr +/false +/find +/flock +/fold +/getconf +/grep +/head +/hostname +/join +/kill +/link +/ln +/logger +/logname +/ls +/md5sum +/mkdir +/mkfifo +/mknod +/mktemp +/mv +/nice +/nl +/nohup +/od +/paste +/pathchk +/printenv +/printf +/pwd +/readlink +/renice +/rev +/rm +/rmdir +/sbase-box +/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 diff --git a/util/sbase/LICENSE b/util/sbase/LICENSE new file mode 100644 index 00000000..bbbfa8c5 --- /dev/null +++ b/util/sbase/LICENSE @@ -0,0 +1,63 @@ +MIT 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@sciops.net> +© 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 <wcm@sigwinch.xyz> +© 2016 Mattias Andrée <maandree@kth.se> +© 2016 Eivind Uggedal <eivind@uggedal.com> diff --git a/util/sbase/Makefile b/util/sbase/Makefile new file mode 100644 index 00000000..2d409ff3 --- /dev/null +++ b/util/sbase/Makefile @@ -0,0 +1,256 @@ +.POSIX: +include config.mk + +.SUFFIXES: +.SUFFIXES: .o .c + +CPPFLAGS =\ +	-D_DEFAULT_SOURCE \ +	-D_NETBSD_SOURCE \ +	-D_BSD_SOURCE \ +	-D_XOPEN_SOURCE=700 \ +	-D_FILE_OFFSET_BITS=64 + +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 + +LIBUTFOBJ =\ +	libutf/fgetrune.o\ +	libutf/fputrune.o\ +	libutf/isalnumrune.o\ +	libutf/isalpharune.o\ +	libutf/isblankrune.o\ +	libutf/iscntrlrune.o\ +	libutf/isdigitrune.o\ +	libutf/isgraphrune.o\ +	libutf/isprintrune.o\ +	libutf/ispunctrune.o\ +	libutf/isspacerune.o\ +	libutf/istitlerune.o\ +	libutf/isxdigitrune.o\ +	libutf/lowerrune.o\ +	libutf/rune.o\ +	libutf/runetype.o\ +	libutf/upperrune.o\ +	libutf/utf.o\ +	libutf/utftorunestr.o + +LIBUTILOBJ =\ +	libutil/concat.o\ +	libutil/cp.o\ +	libutil/crypt.o\ +	libutil/confirm.o\ +	libutil/ealloc.o\ +	libutil/enmasse.o\ +	libutil/eprintf.o\ +	libutil/eregcomp.o\ +	libutil/estrtod.o\ +	libutil/fnck.o\ +	libutil/fshut.o\ +	libutil/getlines.o\ +	libutil/human.o\ +	libutil/linecmp.o\ +	libutil/md5.o\ +	libutil/memmem.o\ +	libutil/mkdirp.o\ +	libutil/mode.o\ +	libutil/parseoffset.o\ +	libutil/putword.o\ +	libutil/reallocarray.o\ +	libutil/recurse.o\ +	libutil/rm.o\ +	libutil/sha1.o\ +	libutil/sha224.o\ +	libutil/sha256.o\ +	libutil/sha384.o\ +	libutil/sha512.o\ +	libutil/sha512-224.o\ +	libutil/sha512-256.o\ +	libutil/strcasestr.o\ +	libutil/strlcat.o\ +	libutil/strlcpy.o\ +	libutil/strsep.o\ +	libutil/strnsubst.o\ +	libutil/strtonum.o\ +	libutil/unescape.o\ +	libutil/writeall.o + +LIB = libutf.a libutil.a + +BIN =\ +	basename\ +	cal\ +	cat\ +	chgrp\ +	chmod\ +	chown\ +	chroot\ +	cksum\ +	cmp\ +	cols\ +	comm\ +	cp\ +	cron\ +	cut\ +	date\ +	dd\ +	dirname\ +	du\ +	echo\ +	ed\ +	env\ +	expand\ +	expr\ +	false\ +	find\ +	flock\ +	fold\ +	getconf\ +	grep\ +	head\ +	hostname\ +	join\ +	kill\ +	link\ +	ln\ +	logger\ +	logname\ +	ls\ +	md5sum\ +	mkdir\ +	mkfifo\ +	mknod\ +	mktemp\ +	mv\ +	nice\ +	nl\ +	nohup\ +	od\ +	paste\ +	pathchk\ +	printenv\ +	printf\ +	pwd\ +	readlink\ +	renice\ +	rev\ +	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 + +OBJ = $(LIBUTFOBJ) $(LIBUTILOBJ) + +all: $(BIN) + +$(BIN): $(LIB) + +$(OBJ) $(BIN): $(HDR) + +.o: +	$(CC) $(LDFLAGS) -o $@ $< $(LIB) + +.c.o: +	$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -c $< + +.c: +	$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< $(LIB) + +libutf.a: $(LIBUTFOBJ) +	$(AR) $(ARFLAGS) $@ $? +	$(RANLIB) $@ + +libutil.a: $(LIBUTILOBJ) +	$(AR) $(ARFLAGS) $@ $? +	$(RANLIB) $@ + +getconf: getconf.h + +getconf.h: +	scripts/getconf.sh > $@ + +proto: $(BIN) +	scripts/mkproto $(DESTDIR)$(PREFIX) $(DESTDIR)$(MANPREFIX) proto + +install uninstall: proto +	scripts/$@ proto + +sbase-box-install: sbase-box proto +	scripts/install proto +	$(DESTDIR)$(PREFIX)/bin/sbase-box -i $(DESTDIR)$(PREFIX)/bin/ + +sbase-box-uninstall: sbase-box proto +	$(DESTDIR)$(PREFIX)/bin/sbase-box -d $(DESTDIR)$(PREFIX)/bin/ +	scripts/uninstall proto + +dist: clean +	mkdir -p sbase +	cp -R LICENSE Makefile README TODO config.mk *.c *.1 *.h libutf libutil sbase +	mv sbase sbase-$(VERSION) +	tar -cf sbase-$(VERSION).tar sbase-$(VERSION) +	gzip sbase-$(VERSION).tar +	rm -rf sbase-$(VERSION) + +sbase-box: $(BIN) +	scripts/mkbox +	$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ build/*.c $(LIB) + +clean: +	rm -f $(BIN) $(OBJ) $(LIB) sbase-box sbase-$(VERSION).tar.gz +	rm -f getconf.h +	rm -f proto +	rm -rf build + +.PHONY: all install uninstall dist sbase-box-install sbase-box-uninstall clean diff --git a/util/sbase/README b/util/sbase/README new file mode 100644 index 00000000..a3390a95 --- /dev/null +++ b/util/sbase/README @@ -0,0 +1,147 @@ +sbase - suckless unix tools +=========================== + +sbase is a collection of unix tools that are inherently portable across +UNIX and UNIX-like systems. + +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 + +To run the tools for sbase-box directly use: sbase-box cmd [args] + +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. + +Status +------ + +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              . +0=*|x cron            . +0#*|o cut             . +0=*|o date            . +0=*|o dd              . +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 mknod           . +0=*|x mktemp          . +0=*|o mv              (-i) +0=*|o nice            . +0#*|o nl              . +0=*|o nohup           . +0=*|o od              . + #*|o paste           . +0#* o pathchk         . +0=*|x printenv        . +0#*|o printf          . +0=*|o pwd             . +0=*|x readlink        . +0=*|o renice          . +0#* x rev             . +0=*|o rm              . +0=*|o rmdir           . + #    sed             . +0=*|x seq             . +0=*|x setsid          . +0=*|x sha1sum         . +0=* x sha224sum       . +0=*|x sha256sum       . +0=* x sha384sum       . +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           . +0=*|x yes             . + +[1] http://git.suckless.org/ubase/ +[2] http://www.musl-libc.org/ diff --git a/util/sbase/TODO b/util/sbase/TODO new file mode 100644 index 00000000..38c9f868 --- /dev/null +++ b/util/sbase/TODO @@ -0,0 +1,92 @@ +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 +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. + +The return values of mdcheckline() in crypt.c need to be fixed (0 -> success, +1 -> error). + +[0] http://landley.net/toybox/roadmap.html +[1] http://git.suckless.org/ubase/ + +Bugs +==== + +ed +-- +* cat <<EOF | ed +  i +  LLL +  . +  s/$/\\ + +* cat <<EOF | ed +    0a +    int radix = 16; +    int Pflag; +    int Aflag; +    int vflag; +    int gflag; +    int uflag; +    int arflag; + +    . +    ?radix?;/^$/-s/^/static / +* cat <<EOF | ed +    0a +       Line +    . +    s# *## +* cat <<EOF | ed +    0a +    line +    . +    1g/^$/p + +* cat <<EOF | ed +    0a +    line1 +    line2 +    line3 +    . +    g/^$/d +    ,p + +* Editing huge files doesn't work well. + + +printf +------ +* Flags for string conversion-specifier (%s) are not supported. +* Escape sequences that expand to '%' are treated as beginning of +  conversion specification. +* An trailing '%' at the end of a format string causes a read past +  the end of the string. + +tr +-- +* When a character class is present, all other characters in the +  string are ignored. + +sbase-box +--------- +* List of commands does not contain `install` (only `xinstall`). + + +xargs +----- +* Add -L. diff --git a/util/sbase/arg.h b/util/sbase/arg.h new file mode 100644 index 00000000..0b23c53a --- /dev/null +++ b/util/sbase/arg.h @@ -0,0 +1,65 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN	for (argv0 = *argv, argv++, argc--;\ +					argv[0] && argv[0][0] == '-'\ +					&& argv[0][1];\ +					argc--, argv++) {\ +				char argc_;\ +				char **argv_;\ +				int brk_;\ +				if (argv[0][1] == '-' && argv[0][2] == '\0') {\ +					argv++;\ +					argc--;\ +					break;\ +				}\ +				for (brk_ = 0, argv[0]++, argv_ = argv;\ +						argv[0][0] && !brk_;\ +						argv[0]++) {\ +					if (argv_ != argv)\ +						break;\ +					argc_ = argv[0][0];\ +					switch (argc_) + +/* Handles obsolete -NUM syntax */ +#define ARGNUM				case '0':\ +					case '1':\ +					case '2':\ +					case '3':\ +					case '4':\ +					case '5':\ +					case '6':\ +					case '7':\ +					case '8':\ +					case '9' + +#define ARGEND			}\ +			} + +#define ARGC()		argc_ + +#define ARGNUMF()	(brk_ = 1, estrtonum(argv[0], 0, INT_MAX)) + +#define EARGF(x)	((argv[0][1] == '\0' && argv[1] == NULL)?\ +				((x), abort(), (char *)0) :\ +				(brk_ = 1, (argv[0][1] != '\0')?\ +					(&argv[0][1]) :\ +					(argc--, argv++, argv[0]))) + +#define ARGF()		((argv[0][1] == '\0' && argv[1] == NULL)?\ +				(char *)0 :\ +				(brk_ = 1, (argv[0][1] != '\0')?\ +					(&argv[0][1]) :\ +					(argc--, argv++, argv[0]))) + +#define LNGARG()	&argv[0][0] + +#endif diff --git a/util/sbase/basename.1 b/util/sbase/basename.1 new file mode 100644 index 00000000..2717c580 --- /dev/null +++ b/util/sbase/basename.1 @@ -0,0 +1,22 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/basename.c b/util/sbase/basename.c new file mode 100644 index 00000000..94a2848f --- /dev/null +++ b/util/sbase/basename.c @@ -0,0 +1,37 @@ +/* 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; + +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	if (argc != 1 && argc != 2) +		usage(); + +	p = basename(argv[0]); +	if (argc == 2) { +		off = strlen(p) - strlen(argv[1]); +		if (off > 0 && !strcmp(p + off, argv[1])) +			p[off] = '\0'; +	} +	puts(p); + +	return fshut(stdout, "<stdout>"); +} diff --git a/util/sbase/cal.1 b/util/sbase/cal.1 new file mode 100644 index 00000000..2918ea98 --- /dev/null +++ b/util/sbase/cal.1 @@ -0,0 +1,68 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +.Pp +The flags +.Op Fl 13cfmnsy +are an extension to that specification. diff --git a/util/sbase/cal.c b/util/sbase/cal.c new file mode 100644 index 00000000..a8c91f19 --- /dev/null +++ b/util/sbase/cal.c @@ -0,0 +1,226 @@ +/* See LICENSE file for copyright and license details. */ +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.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, pad; +	char month_year[sizeof("Su Mo Tu We Th Fr Sa")]; + +	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; +			} +			snprintf(month_year, sizeof(month_year), "%s %zu", smon[cur_month], cur_year); +			pad = sizeof(month_year) - 1 - strlen(month_year); +			printf("%*s%s%*s   ", pad / 2 + pad % 2, "", month_year, pad / 2, ""); +		} +		putchar('\n'); +		for (col = 0, m = n; m < nmons && col < ncols; ++col, ++m) { +			for (dow = fday; dow < (fday + 7); ++dow) +				printf("%s ", days[dow % 7]); +			printf("  "); +		} +		putchar('\n'); +		for (line = 1; line <= 6; ++line) { +			for (col = 0, m = n; m < nmons && col < ncols; ++col, ++m) { +				cur_year = year + m / 12; +				cur_month = month + m % 12; +				if (cur_month > 11) { +					cur_month -= 12; +					cur_year += 1; +				} +				printgrid(cur_year, cur_month, fday, line); +				printf("  "); +			} +			putchar('\n'); +		} +	} +} + +static void +usage(void) +{ +	eprintf("usage: %s [-1 | -3 | -y | -n num] " +	        "[-s | -m | -f num] [-c num] [[month] year]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	time_t now; +	size_t year, ncols, nmons; +	int fday, month; + +	now   = time(NULL); +	ltime = localtime(&now); +	year  = ltime->tm_year + 1900; +	month = ltime->tm_mon + 1; +	fday  = 0; + +	if (!isatty(STDOUT_FILENO)) +		ltime = NULL; /* don't highlight today's date */ + +	ncols = 3; +	nmons = 0; + +	ARGBEGIN { +	case '1': +		nmons = 1; +		break; +	case '3': +		nmons = 3; +		if (--month == 0) { +			month = 12; +			year--; +		} +		break; +	case 'c': +		ncols = estrtonum(EARGF(usage()), 0, MIN(SIZE_MAX, LLONG_MAX)); +		break; +	case 'f': +		fday = estrtonum(EARGF(usage()), 0, 6); +		break; +	case 'm': /* Monday */ +		fday = 1; +		break; +	case 'n': +		nmons = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX)); +		break; +	case 's': /* Sunday */ +		fday = 0; +		break; +	case 'y': +		month = 1; +		nmons = 12; +		break; +	default: +		usage(); +	} ARGEND + +	if (nmons == 0) { +		if (argc == 1) { +			month = 1; +			nmons = 12; +		} else { +			nmons = 1; +		} +	} + +	switch (argc) { +	case 2: +		month = estrtonum(argv[0], 1, 12); +		argv++; +	case 1: /* fallthrough */ +		year = estrtonum(argv[0], 0, INT_MAX); +		break; +	case 0: +		break; +	default: +		usage(); +	} + +	drawcal(year, month - 1, ncols, nmons, fday); + +	return fshut(stdout, "<stdout>"); +} diff --git a/util/sbase/cat.1 b/util/sbase/cat.1 new file mode 100644 index 00000000..e6172298 --- /dev/null +++ b/util/sbase/cat.1 @@ -0,0 +1,27 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/cat.c b/util/sbase/cat.c new file mode 100644 index 00000000..211e8d11 --- /dev/null +++ b/util/sbase/cat.c @@ -0,0 +1,52 @@ +/* See LICENSE file for copyright and license details. */ +#include <fcntl.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s [-u] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	int fd, ret = 0; + +	ARGBEGIN { +	case 'u': +		break; +	default: +		usage(); +	} ARGEND + +	if (!argc) { +		if (concat(0, "<stdin>", 1, "<stdout>") < 0) +			ret = 1; +	} else { +		for (; *argv; argc--, argv++) { +			if (!strcmp(*argv, "-")) { +				*argv = "<stdin>"; +				fd = 0; +			} else if ((fd = open(*argv, O_RDONLY)) < 0) { +				weprintf("open %s:", *argv); +				ret = 1; +				continue; +			} +			switch (concat(fd, *argv, 1, "<stdout>")) { +			case -1: +				ret = 1; +				break; +			case -2: +				return 1;  /* exit on write error */ +			} +			if (fd != 0) +				close(fd); +		} +	} + +	return ret; +} diff --git a/util/sbase/chgrp.1 b/util/sbase/chgrp.1 new file mode 100644 index 00000000..ee44a54c --- /dev/null +++ b/util/sbase/chgrp.1 @@ -0,0 +1,47 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/chgrp.c b/util/sbase/chgrp.c new file mode 100644 index 00000000..4042a0dd --- /dev/null +++ b/util/sbase/chgrp.c @@ -0,0 +1,75 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.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(int dirfd, const char *name, struct stat *st, void *data, struct recursor *r) +{ +	int flags = 0; + +	if ((r->maxdepth == 0 && r->follow == 'P') || (r->follow == 'H' && r->depth) || (hflag && !(r->depth))) +		flags |= AT_SYMLINK_NOFOLLOW; +	if (fchownat(dirfd, name, -1, gid, flags) < 0) { +		weprintf("chown %s:", r->path); +		ret = 1; +	} else if (S_ISDIR(st->st_mode)) { +		recurse(dirfd, name, 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, .maxdepth = 1, .follow = 'P' }; + +	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]))) { +		gid = gr->gr_gid; +	} else { +		if (errno) +			eprintf("getgrnam %s:", argv[0]); +		gid = estrtonum(argv[0], 0, UINT_MAX); +	} + +	for (argc--, argv++; *argv; argc--, argv++) +		recurse(AT_FDCWD, *argv, NULL, &r); + +	return ret || recurse_status; +} diff --git a/util/sbase/chmod.1 b/util/sbase/chmod.1 new file mode 100644 index 00000000..f579b3fa --- /dev/null +++ b/util/sbase/chmod.1 @@ -0,0 +1,74 @@ +.Dd December 21, 2019 +.Dt CHMOD 1 +.Os sbase +.Sh NAME +.Nm chmod +.Nd change file modes +.Sh SYNOPSIS +.Nm +.Op Fl R +.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]*[+-=][rwxXst]*" +.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 +.It X +execute, if directory or at least one execute bit is already set +.El +.Pp +Symbolic links are followed if they are passed as operands, and ignored +if they are encountered during directory traversal. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl R +Change modes recursively. +.El +.Sh SEE ALSO +.Xr chgrp 1 , +.Xr umask 1 +.Sh STANDARDS +POSIX.1-2013. +.Pp +The +.Op Fl HLP +flags are an extension to that specification. diff --git a/util/sbase/chmod.c b/util/sbase/chmod.c new file mode 100644 index 00000000..c79488bb --- /dev/null +++ b/util/sbase/chmod.c @@ -0,0 +1,77 @@ +/* See LICENSE file for copyright and license details. */ +#include <fcntl.h> +#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(int dirfd, const char *name, struct stat *st, void *data, struct recursor *r) +{ +	mode_t m; + +	m = parsemode(modestr, st->st_mode, mask); +	if (!S_ISLNK(st->st_mode) && fchmodat(dirfd, name, m, 0) < 0) { +		weprintf("chmod %s:", r->path); +		ret = 1; +	} else if (S_ISDIR(st->st_mode)) { +		recurse(dirfd, name, NULL, r); +	} +} + +static void +usage(void) +{ +	eprintf("usage: %s [-R] mode file ...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	struct recursor r = { .fn = chmodr, .maxdepth = 1, .follow = 'H', .flags = DIRFIRST }; +	size_t i; + +	argv0 = *argv, argv0 ? (argc--, argv++) : (void *)0; + +	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 'r': case 'w': case 'x': case 'X': case 's': case 't': +				/* -[rwxXst] 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(AT_FDCWD, *argv, NULL, &r); + +	return ret || recurse_status; +} diff --git a/util/sbase/chown.1 b/util/sbase/chown.1 new file mode 100644 index 00000000..8afdfcab --- /dev/null +++ b/util/sbase/chown.1 @@ -0,0 +1,57 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/chown.c b/util/sbase/chown.c new file mode 100644 index 00000000..71628eb6 --- /dev/null +++ b/util/sbase/chown.c @@ -0,0 +1,104 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <fcntl.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(int dirfd, const char *name, struct stat *st, void *data, struct recursor *r) +{ +	int flags = 0; + +	if ((r->maxdepth == 0 && r->follow == 'P') || (r->follow == 'H' && r->depth) || (hflag && !(r->depth))) +		flags |= AT_SYMLINK_NOFOLLOW; + +	if (fchownat(dirfd, name, uid, gid, flags) < 0) { +		weprintf("chown %s:", r->path); +		ret = 1; +	} else if (S_ISDIR(st->st_mode)) { +		recurse(dirfd, name, 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, .maxdepth = 1, .follow = 'P' }; +	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(AT_FDCWD, *argv, NULL, &r); + +	return ret || recurse_status; +} diff --git a/util/sbase/chroot.1 b/util/sbase/chroot.1 new file mode 100644 index 00000000..ff49fe91 --- /dev/null +++ b/util/sbase/chroot.1 @@ -0,0 +1,25 @@ +.Dd October 8, 2015 +.Dt CHROOT 1 +.Os sbase +.Sh NAME +.Nm chroot +.Nd run a command or shell with a different root directory +.Sh SYNOPSIS +.Nm +.Ar dir +.Op Ar cmd Op Ar arg ... +.Sh DESCRIPTION +.Nm +runs +.Ar cmd +after changing the root directory to +.Ar dir +with the +.Xr chroot 2 +system call and after changing the working directory to the new root. +If +.Ar cmd +is not specified, an interactive shell is started in the new root. +.Sh SEE ALSO +.Xr chdir 2 , +.Xr chroot 2 diff --git a/util/sbase/chroot.c b/util/sbase/chroot.c new file mode 100644 index 00000000..45f2dc7a --- /dev/null +++ b/util/sbase/chroot.c @@ -0,0 +1,49 @@ +/* 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; + +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	if (!argc) +		usage(); + +	if ((aux = getenv("SHELL"))) +		shell[0] = aux; + +	if (chroot(argv[0]) < 0) +		eprintf("chroot %s:", argv[0]); + +	if (chdir("/") < 0) +		eprintf("chdir:"); + +	if (argc == 1) { +		cmd = *shell; +		execvp(cmd, shell); +	} else { +		cmd = argv[1]; +		execvp(cmd, argv + 1); +	} + +	savederrno = errno; +	weprintf("execvp %s:", cmd); + +	_exit(126 + (savederrno == ENOENT)); +} diff --git a/util/sbase/cksum.1 b/util/sbase/cksum.1 new file mode 100644 index 00000000..6e2657a4 --- /dev/null +++ b/util/sbase/cksum.1 @@ -0,0 +1,24 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/cksum.c b/util/sbase/cksum.c new file mode 100644 index 00000000..50107b2b --- /dev/null +++ b/util/sbase/cksum.c @@ -0,0 +1,132 @@ +/* See LICENSE file for copyright and license details. */ +#include <fcntl.h> +#include <inttypes.h> +#include <stdio.h> +#include <string.h> +#include <unistd.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(int fd, const char *s) +{ +	ssize_t n; +	size_t len = 0, i; +	uint32_t ck = 0; +	unsigned char buf[BUFSIZ]; + +	while ((n = read(fd, buf, sizeof(buf))) > 0) { +		for (i = 0; i < n; i++) +			ck = (ck << 8) ^ crctab[(ck >> 24) ^ buf[i]]; +		len += n; +	} +	if (n < 0) { +		weprintf("read %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'); +} + +static void +usage(void) +{ +	eprintf("usage: %s [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	int fd; + +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	if (!argc) { +		cksum(0, NULL); +	} else { +		for (; *argv; argc--, argv++) { +			if (!strcmp(*argv, "-")) { +				*argv = "<stdin>"; +				fd = 0; +			} else if ((fd = open(*argv, O_RDONLY)) < 0) { +				weprintf("open %s:", *argv); +				ret = 1; +				continue; +			} +			cksum(fd, *argv); +			if (fd != 0) +				close(fd); +		} +	} + +	ret |= fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/cmp.1 b/util/sbase/cmp.1 new file mode 100644 index 00000000..750d11a8 --- /dev/null +++ b/util/sbase/cmp.1 @@ -0,0 +1,49 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +.Pp +The "char" in the default result format has been replaced with "byte". diff --git a/util/sbase/cmp.c b/util/sbase/cmp.c new file mode 100644 index 00000000..83ab149e --- /dev/null +++ b/util/sbase/cmp.c @@ -0,0 +1,82 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +static void +usage(void) +{ +	enprintf(2, "usage: %s [-l | -s] file1 file2\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	FILE *fp[2]; +	size_t line = 1, n; +	int ret = 0, lflag = 0, sflag = 0, same = 1, b[2]; + +	ARGBEGIN { +	case 'l': +		lflag = 1; +		break; +	case 's': +		sflag = 1; +		break; +	default: +		usage(); +	} ARGEND + +	if (argc != 2 || (lflag && sflag)) +		usage(); + +	for (n = 0; n < 2; n++) { +		if (!strcmp(argv[n], "-")) { +			argv[n] = "<stdin>"; +			fp[n] = stdin; +		} else { +			if (!(fp[n] = fopen(argv[n], "r"))) { +				if (!sflag) +					weprintf("fopen %s:", argv[n]); +				return 2; +			} +		} +	} + +	for (n = 1; ; n++) { +		b[0] = getc(fp[0]); +		b[1] = getc(fp[1]); + +		if (b[0] == b[1]) { +			if (b[0] == EOF) +				break; +			else if (b[0] == '\n') +				line++; +			continue; +		} else if (b[0] == EOF || b[1] == EOF) { +			if (!sflag) +				weprintf("EOF on %s\n", argv[(b[0] != EOF)]); +			same = 0; +			break; +		} else if (!lflag) { +			if (!sflag) +				printf("%s %s differ: byte %zu, line %zu\n", +				       argv[0], argv[1], n, line); +			same = 0; +			break; +		} else { +			printf("%zu %o %o\n", n, b[0], b[1]); +			same = 0; +		} +	} + +	if (!ret) +		ret = !same; +	if (fshut(fp[0], argv[0]) | (fp[0] != fp[1] && fshut(fp[1], argv[1])) | +	    fshut(stdout, "<stdout>")) +		ret = 2; + +	return ret; +} diff --git a/util/sbase/cols.1 b/util/sbase/cols.1 new file mode 100644 index 00000000..67c2e8ea --- /dev/null +++ b/util/sbase/cols.1 @@ -0,0 +1,56 @@ +.Dd October 8, 2015 +.Dt COLS 1 +.Os sbase +.Sh NAME +.Nm cols +.Nd columnize output +.Sh SYNOPSIS +.Nm +.Op Fl c Ar num +.Op Ar file ... +.Sh DESCRIPTION +.Nm +reads each +.Ar file +in sequence and writes them to stdout, in as many vertical +columns as will fit in +.Ar num +character columns. +If no +.Ar file +is given, +.Nm +reads from stdin. +.Pp +By default +.Nm cols +tries to figure out the width of the output device. +If that fails, it defaults to 65 chars. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c Ar num +Set maximum number of character columns to +.Ar num , +unless input lines exceed this limit. +.El +.Sh ENVIRONMENT +.Bl -tag -width Ds +.It COLUMNS +The width of the output device. +.El +.Sh HISTORY +.Nm +is similar to +.Xr mc 1 +in Plan 9. It was renamed to +.Nm +to avoid the name collision with the popular file manager +Midnight Commander. +.Sh CAVEATS +This implementation of +.Nm +assumes that each UTF-8 code point occupies one character cell, +and thus mishandles TAB characters (among others). +.Pp +.Nm +currently mangles files which contain embedded NULs. diff --git a/util/sbase/cols.c b/util/sbase/cols.c new file mode 100644 index 00000000..428cd79d --- /dev/null +++ b/util/sbase/cols.c @@ -0,0 +1,98 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/ioctl.h> + +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "text.h" +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s [-c num] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	FILE *fp; +	struct winsize w; +	struct linebuf b = EMPTY_LINEBUF; +	size_t chars = 65, maxlen = 0, i, j, k, len, cols, rows; +	int cflag = 0, ret = 0; +	char *p; + +	ARGBEGIN { +	case 'c': +		cflag = 1; +		chars = estrtonum(EARGF(usage()), 1, MIN(LLONG_MAX, SIZE_MAX)); +		break; +	default: +		usage(); +	} ARGEND + +	if (!cflag) { +		if ((p = getenv("COLUMNS"))) +			chars = estrtonum(p, 1, MIN(LLONG_MAX, SIZE_MAX)); +		else if (!ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) && w.ws_col > 0) +			chars = w.ws_col; +	} + +	if (!argc) { +		getlines(stdin, &b); +	} else { +		for (; *argv; argc--, argv++) { +			if (!strcmp(*argv, "-")) { +				*argv = "<stdin>"; +				fp = stdin; +			} else if (!(fp = fopen(*argv, "r"))) { +				weprintf("fopen %s:", *argv); +				ret = 1; +				continue; +			} +			getlines(fp, &b); +			if (fp != stdin && fshut(fp, *argv)) +				ret = 1; +		} +	} + +	for (i = 0; i < b.nlines; i++) { +		for (j = 0, len = 0; j < b.lines[i].len; j++) { +			if (UTF8_POINT(b.lines[i].data[j])) +				len++; +		} +		if (len && b.lines[i].data[b.lines[i].len - 1] == '\n') { +			b.lines[i].data[--(b.lines[i].len)] = '\0'; +			len--; +		} +		if (len > maxlen) +			maxlen = len; +	} + +	for (cols = 1; (cols + 1) * maxlen + cols <= chars; cols++); +	rows = b.nlines / cols + (b.nlines % cols > 0); + +	for (i = 0; i < rows; i++) { +		for (j = 0; j < cols && i + j * rows < b.nlines; j++) { +			for (k = 0, len = 0; k < b.lines[i + j * rows].len; k++) { +				if (UTF8_POINT(b.lines[i + j * rows].data[k])) +					len++; +			} +			fwrite(b.lines[i + j * rows].data, 1, +			       b.lines[i + j * rows].len, stdout); +			if (j < cols - 1) +				for (k = len; k < maxlen + 1; k++) +					putchar(' '); +		} +		putchar('\n'); +	} + +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/comm.1 b/util/sbase/comm.1 new file mode 100644 index 00000000..5df70c2c --- /dev/null +++ b/util/sbase/comm.1 @@ -0,0 +1,40 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/comm.c b/util/sbase/comm.c new file mode 100644 index 00000000..fbd50d9b --- /dev/null +++ b/util/sbase/comm.c @@ -0,0 +1,97 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "text.h" +#include "util.h" + +static int show = 0x07; + +static void +printline(int pos, struct line *line) +{ +	int i; + +	if (!(show & (0x1 << pos))) +		return; + +	for (i = 0; i < pos; i++) { +		if (show & (0x1 << i)) +			putchar('\t'); +	} +	fwrite(line->data, 1, line->len, stdout); +} + +static void +usage(void) +{ +	eprintf("usage: %s [-123] file1 file2\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	FILE *fp[2]; +	static struct line line[2]; +	size_t linecap[2] = { 0, 0 }; +	ssize_t len; +	int ret = 0, i, diff = 0, seenline = 0; + +	ARGBEGIN { +	case '1': +	case '2': +	case '3': +		show &= 0x07 ^ (1 << (ARGC() - '1')); +		break; +	default: +		usage(); +	} ARGEND + +	if (argc != 2) +		usage(); + +	for (i = 0; i < 2; i++) { +		if (!strcmp(argv[i], "-")) { +			argv[i] = "<stdin>"; +			fp[i] = stdin; +		} else if (!(fp[i] = fopen(argv[i], "r"))) { +			eprintf("fopen %s:", argv[i]); +		} +	} + +	for (;;) { +		for (i = 0; i < 2; i++) { +			if (diff && i == (diff < 0)) +				continue; +			if ((len = getline(&(line[i].data), &linecap[i], +			                   fp[i])) > 0) { +				line[i].len = len; +				seenline = 1; +				continue; +			} +			if (ferror(fp[i])) +				eprintf("getline %s:", argv[i]); +			if ((diff || seenline) && line[!i].data[0]) +				printline(!i, &line[!i]); +			while ((len = getline(&(line[!i].data), &linecap[!i], +			                      fp[!i])) > 0) { +				line[!i].len = len; +				printline(!i, &line[!i]); +			} +			if (ferror(fp[!i])) +				eprintf("getline %s:", argv[!i]); +			goto end; +		} +		diff = linecmp(&line[0], &line[1]); +		LIMIT(diff, -1, 1); +		seenline = 0; +		printline((2 - diff) % 3, &line[MAX(0, diff)]); +	} +end: +	ret |= fshut(fp[0], argv[0]); +	ret |= (fp[0] != fp[1]) && fshut(fp[1], argv[1]); +	ret |= fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/compat.h b/util/sbase/compat.h new file mode 100644 index 00000000..e2154a62 --- /dev/null +++ b/util/sbase/compat.h @@ -0,0 +1,6 @@ +/* See LICENSE file for copyright and license details. */ +#include <limits.h> + +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX +#endif diff --git a/util/sbase/config.mk b/util/sbase/config.mk new file mode 100644 index 00000000..69dda343 --- /dev/null +++ b/util/sbase/config.mk @@ -0,0 +1,15 @@ +# sbase version +VERSION = 0.1 + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +# tools +#CC = +#AR = +RANLIB = ranlib + +# -lrt might be needed on some systems +# CFLAGS   = +# LDFLAGS  = diff --git a/util/sbase/cp.1 b/util/sbase/cp.1 new file mode 100644 index 00000000..74027eaa --- /dev/null +++ b/util/sbase/cp.1 @@ -0,0 +1,71 @@ +.Dd April 22, 2025 +.Dt CP 1 +.Os sbase +.Sh NAME +.Nm cp +.Nd copy files and directories +.Sh SYNOPSIS +.Nm +.Op Fl afipv +.Oo +.Fl R +.Op Fl H | L | P +.Oc +.Ar source ... +.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 i +Interactive prompt before overwrite. +.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 without +.Fl R . +.It Fl P +Preserve symbolic links. +This is the default with +.Fl R . +.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 +POSIX.1-2013. +.Pp +The +.Op Fl av +flags are an extension to that specification. diff --git a/util/sbase/cp.c b/util/sbase/cp.c new file mode 100644 index 00000000..af0fa610 --- /dev/null +++ b/util/sbase/cp.c @@ -0,0 +1,63 @@ +/* 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 [-afipv] [-R [-H | -L | -P]] source ... dest\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	struct stat st; + +	ARGBEGIN { +	case 'i': +		cp_iflag = 1; +		break; +	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 (!cp_follow) +		cp_follow = cp_rflag ? 'P' : 'L'; + +	if (argc > 2) { +		if (stat(argv[argc - 1], &st) < 0) +			eprintf("stat %s:", argv[argc - 1]); +		if (!S_ISDIR(st.st_mode)) +			eprintf("%s: not a directory\n", argv[argc - 1]); +	} +	enmasse(argc, argv, cp); + +	return fshut(stdout, "<stdout>") || cp_status; +} diff --git a/util/sbase/cron.1 b/util/sbase/cron.1 new file mode 100644 index 00000000..1cb90a44 --- /dev/null +++ b/util/sbase/cron.1 @@ -0,0 +1,23 @@ +.Dd October 8, 2015 +.Dt CRON 1 +.Os sbase +.Sh NAME +.Nm cron +.Nd clock daemon +.Sh SYNOPSIS +.Nm +.Op Fl f Ar file +.Op Fl n +.Sh DESCRIPTION +.Nm +schedules commands to be run at specified dates and times. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl f Ar file +Use the specified +.Ar file +instead of the default +.Pa /etc/crontab . +.It Fl n +Do not daemonize. +.El diff --git a/util/sbase/cron.c b/util/sbase/cron.c new file mode 100644 index 00000000..77304ccf --- /dev/null +++ b/util/sbase/cron.c @@ -0,0 +1,566 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/types.h> +#include <sys/wait.h> + +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> + +#include "queue.h" +#include "util.h" + +struct field { +	enum { +		ERROR, +		WILDCARD, +		NUMBER, +		RANGE, +		REPEAT, +		LIST +	} type; +	long *val; +	int len; +}; + +struct ctabentry { +	struct field min; +	struct field hour; +	struct field mday; +	struct field mon; +	struct field wday; +	char *cmd; +	TAILQ_ENTRY(ctabentry) entry; +}; + +struct jobentry { +	char *cmd; +	pid_t pid; +	TAILQ_ENTRY(jobentry) entry; +}; + +static sig_atomic_t chldreap; +static sig_atomic_t reload; +static sig_atomic_t quit; +static TAILQ_HEAD(, ctabentry) ctabhead = TAILQ_HEAD_INITIALIZER(ctabhead); +static TAILQ_HEAD(, jobentry) jobhead = TAILQ_HEAD_INITIALIZER(jobhead); +static char *config = "/etc/crontab"; +static char *pidfile = "/var/run/crond.pid"; +static int nflag; + +static void +loginfo(const char *fmt, ...) +{ +	va_list ap; +	va_start(ap, fmt); +	if (nflag == 0) +		vsyslog(LOG_INFO, fmt, ap); +	else +		vfprintf(stdout, fmt, ap); +	fflush(stdout); +	va_end(ap); +} + +static void +logwarn(const char *fmt, ...) +{ +	va_list ap; +	va_start(ap, fmt); +	if (nflag == 0) +		vsyslog(LOG_WARNING, fmt, ap); +	else +		vfprintf(stderr, fmt, ap); +	va_end(ap); +} + +static void +logerr(const char *fmt, ...) +{ +	va_list ap; +	va_start(ap, fmt); +	if (nflag == 0) +		vsyslog(LOG_ERR, fmt, ap); +	else +		vfprintf(stderr, fmt, ap); +	va_end(ap); +} + +static void +runjob(char *cmd) +{ +	struct jobentry *je; +	time_t t; +	pid_t pid; + +	t = time(NULL); + +	/* If command is already running, skip it */ +	TAILQ_FOREACH(je, &jobhead, entry) { +		if (strcmp(je->cmd, cmd) == 0) { +			loginfo("already running %s pid: %d at %s", +				je->cmd, je->pid, ctime(&t)); +			return; +		} +	} + +	switch ((pid = fork())) { +	case -1: +		logerr("error: failed to fork job: %s time: %s", +		       cmd, ctime(&t)); +		return; +	case 0: +		setsid(); +		loginfo("run: %s pid: %d at %s", +			cmd, getpid(), ctime(&t)); +		execl("/bin/sh", "/bin/sh", "-c", cmd, (char *)NULL); +		logerr("error: failed to execute job: %s time: %s", +		       cmd, ctime(&t)); +		_exit(1); +	default: +		je = emalloc(sizeof(*je)); +		je->cmd = estrdup(cmd); +		je->pid = pid; +		TAILQ_INSERT_TAIL(&jobhead, je, entry); +	} +} + +static void +waitjob(void) +{ +	struct jobentry *je, *tmp; +	int status; +	time_t t; +	pid_t pid; + +	t = time(NULL); + +	while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { +		je = NULL; +		TAILQ_FOREACH(tmp, &jobhead, entry) { +			if (tmp->pid == pid) { +				je = tmp; +				break; +			} +		} +		if (je) { +			TAILQ_REMOVE(&jobhead, je, entry); +			free(je->cmd); +			free(je); +		} +		if (WIFEXITED(status) == 1) +			loginfo("complete: pid: %d returned: %d time: %s", +				pid, WEXITSTATUS(status), ctime(&t)); +		else if (WIFSIGNALED(status) == 1) +			loginfo("complete: pid: %d terminated by signal: %s time: %s", +				pid, strsignal(WTERMSIG(status)), ctime(&t)); +		else if (WIFSTOPPED(status) == 1) +			loginfo("complete: pid: %d stopped by signal: %s time: %s", +				pid, strsignal(WSTOPSIG(status)), ctime(&t)); +	} +} + +static int +isleap(int year) +{ +	if (year % 400 == 0) +		return 1; +	if (year % 100 == 0) +		return 0; +	return (year % 4 == 0); +} + +static int +daysinmon(int mon, int year) +{ +	int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; +	if (year < 1900) +		year += 1900; +	if (isleap(year)) +		days[1] = 29; +	return days[mon]; +} + +static int +matchentry(struct ctabentry *cte, struct tm *tm) +{ +	struct { +		struct field *f; +		int tm; +		int len; +	} matchtbl[] = { +		{ .f = &cte->min,  .tm = tm->tm_min,  .len = 60 }, +		{ .f = &cte->hour, .tm = tm->tm_hour, .len = 24 }, +		{ .f = &cte->mday, .tm = tm->tm_mday, .len = daysinmon(tm->tm_mon, tm->tm_year) }, +		{ .f = &cte->mon,  .tm = tm->tm_mon,  .len = 12 }, +		{ .f = &cte->wday, .tm = tm->tm_wday, .len = 7  }, +	}; +	size_t i; +	int j; + +	for (i = 0; i < LEN(matchtbl); i++) { +		switch (matchtbl[i].f->type) { +		case WILDCARD: +			continue; +		case NUMBER: +			if (matchtbl[i].f->val[0] == matchtbl[i].tm) +				continue; +			break; +		case RANGE: +			if (matchtbl[i].f->val[0] <= matchtbl[i].tm) +				if (matchtbl[i].f->val[1] >= matchtbl[i].tm) +					continue; +			break; +		case REPEAT: +			if (matchtbl[i].tm > 0) { +				if (matchtbl[i].tm % matchtbl[i].f->val[0] == 0) +					continue; +			} else { +				if (matchtbl[i].len % matchtbl[i].f->val[0] == 0) +					continue; +			} +			break; +		case LIST: +			for (j = 0; j < matchtbl[i].f->len; j++) +				if (matchtbl[i].f->val[j] == matchtbl[i].tm) +					break; +			if (j < matchtbl[i].f->len) +				continue; +			break; +		default: +			break; +		} +		break; +	} +	if (i != LEN(matchtbl)) +		return 0; +	return 1; +} + +static int +parsefield(const char *field, long low, long high, struct field *f) +{ +	int i; +	char *e1, *e2; +	const char *p; + +	p = field; +	while (isdigit(*p)) +		p++; + +	f->type = ERROR; + +	switch (*p) { +	case '*': +		if (strcmp(field, "*") == 0) { +			f->val = NULL; +			f->len = 0; +			f->type = WILDCARD; +		} else if (strncmp(field, "*/", 2) == 0) { +			f->val = emalloc(sizeof(*f->val)); +			f->len = 1; + +			errno = 0; +			f->val[0] = strtol(field + 2, &e1, 10); +			if (e1[0] != '\0' || errno != 0 || f->val[0] == 0) +				break; + +			f->type = REPEAT; +		} +		break; +	case '\0': +		f->val = emalloc(sizeof(*f->val)); +		f->len = 1; + +		errno = 0; +		f->val[0] = strtol(field, &e1, 10); +		if (e1[0] != '\0' || errno != 0) +			break; + +		f->type = NUMBER; +		break; +	case '-': +		f->val = emalloc(2 * sizeof(*f->val)); +		f->len = 2; + +		errno = 0; +		f->val[0] = strtol(field, &e1, 10); +		if (e1[0] != '-' || errno != 0) +			break; + +		errno = 0; +		f->val[1] = strtol(e1 + 1, &e2, 10); +		if (e2[0] != '\0' || errno != 0) +			break; + +		f->type = RANGE; +		break; +	case ',': +		for (i = 1; isdigit(*p) || *p == ','; p++) +			if (*p == ',') +				i++; +		f->val = emalloc(i * sizeof(*f->val)); +		f->len = i; + +		errno = 0; +		f->val[0] = strtol(field, &e1, 10); +		if (f->val[0] < low || f->val[0] > high) +			break; + +		for (i = 1; *e1 == ',' && errno == 0; i++) { +			errno = 0; +			f->val[i] = strtol(e1 + 1, &e2, 10); +			e1 = e2; +		} +		if (e1[0] != '\0' || errno != 0) +			break; + +		f->type = LIST; +		break; +	default: +		return -1; +	} + +	for (i = 0; i < f->len; i++) +		if (f->val[i] < low || f->val[i] > high) +			f->type = ERROR; + +	if (f->type == ERROR) { +		free(f->val); +		return -1; +	} + +	return 0; +} + +static void +freecte(struct ctabentry *cte, int nfields) +{ +	switch (nfields) { +	case 6: +		free(cte->cmd); +	case 5: +		free(cte->wday.val); +	case 4: +		free(cte->mon.val); +	case 3: +		free(cte->mday.val); +	case 2: +		free(cte->hour.val); +	case 1: +		free(cte->min.val); +	} +	free(cte); +} + +static void +unloadentries(void) +{ +	struct ctabentry *cte, *tmp; + +	for (cte = TAILQ_FIRST(&ctabhead); cte; cte = tmp) { +		tmp = TAILQ_NEXT(cte, entry); +		TAILQ_REMOVE(&ctabhead, cte, entry); +		freecte(cte, 6); +	} +} + +static int +loadentries(void) +{ +	struct ctabentry *cte; +	FILE *fp; +	char *line = NULL, *p, *col; +	int r = 0, y; +	size_t size = 0; +	ssize_t len; +	struct fieldlimits { +		char *name; +		long min; +		long max; +		struct field *f; +	} flim[] = { +		{ "min",  0, 59, NULL }, +		{ "hour", 0, 23, NULL }, +		{ "mday", 1, 31, NULL }, +		{ "mon",  1, 12, NULL }, +		{ "wday", 0, 6,  NULL } +	}; +	size_t x; + +	if ((fp = fopen(config, "r")) == NULL) { +		logerr("error: can't open %s: %s\n", config, strerror(errno)); +		return -1; +	} + +	for (y = 0; (len = getline(&line, &size, fp)) != -1; y++) { +		p = line; +		if (line[0] == '#' || line[0] == '\n' || line[0] == '\0') +			continue; + +		cte = emalloc(sizeof(*cte)); +		flim[0].f = &cte->min; +		flim[1].f = &cte->hour; +		flim[2].f = &cte->mday; +		flim[3].f = &cte->mon; +		flim[4].f = &cte->wday; + +		for (x = 0; x < LEN(flim); x++) { +			do +				col = strsep(&p, "\t\n "); +			while (col && col[0] == '\0'); + +			if (!col || parsefield(col, flim[x].min, flim[x].max, flim[x].f) < 0) { +				logerr("error: failed to parse `%s' field on line %d\n", +						flim[x].name, y + 1); +				freecte(cte, x); +				r = -1; +				break; +			} +		} + +		if (r == -1) +			break; + +		col = strsep(&p, "\n"); +		if (col) +			while (col[0] == '\t' || col[0] == ' ') +				col++; +		if (!col || col[0] == '\0') { +			logerr("error: missing `cmd' field on line %d\n", +			       y + 1); +			freecte(cte, 5); +			r = -1; +			break; +		} +		cte->cmd = estrdup(col); + +		TAILQ_INSERT_TAIL(&ctabhead, cte, entry); +	} + +	if (r < 0) +		unloadentries(); + +	free(line); +	fclose(fp); + +	return r; +} + +static void +reloadentries(void) +{ +	unloadentries(); +	if (loadentries() < 0) +		logwarn("warning: discarding old crontab entries\n"); +} + +static void +sighandler(int sig) +{ +	switch (sig) { +	case SIGCHLD: +		chldreap = 1; +		break; +	case SIGHUP: +		reload = 1; +		break; +	case SIGTERM: +		quit = 1; +		break; +	} +} + +static void +usage(void) +{ +	eprintf("usage: %s [-f file] [-n]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	FILE *fp; +	struct ctabentry *cte; +	time_t t; +	struct tm *tm; +	struct sigaction sa; + +	ARGBEGIN { +	case 'n': +		nflag = 1; +		break; +	case 'f': +		config = EARGF(usage()); +		break; +	default: +		usage(); +	} ARGEND + +	if (argc > 0) +		usage(); + +	if (nflag == 0) { +		openlog(argv[0], LOG_CONS | LOG_PID, LOG_CRON); +		if (daemon(1, 0) < 0) { +			logerr("error: failed to daemonize %s\n", strerror(errno)); +			return 1; +		} +		if ((fp = fopen(pidfile, "w"))) { +			fprintf(fp, "%d\n", getpid()); +			fclose(fp); +		} +	} + +	sa.sa_handler = sighandler; +	sigfillset(&sa.sa_mask); +	sa.sa_flags = SA_RESTART; +	sigaction(SIGCHLD, &sa, NULL); +	sigaction(SIGHUP, &sa, NULL); +	sigaction(SIGTERM, &sa, NULL); + +	loadentries(); + +	while (1) { +		t = time(NULL); +		sleep(60 - t % 60); + +		if (quit == 1) { +			if (nflag == 0) +				unlink(pidfile); +			unloadentries(); +			/* Don't wait or kill forked processes, just exit */ +			break; +		} + +		if (reload == 1 || chldreap == 1) { +			if (reload == 1) { +				reloadentries(); +				reload = 0; +			} +			if (chldreap == 1) { +				waitjob(); +				chldreap = 0; +			} +			continue; +		} + +		TAILQ_FOREACH(cte, &ctabhead, entry) { +			t = time(NULL); +			tm = localtime(&t); +			if (matchentry(cte, tm) == 1) +				runjob(cte->cmd); +		} +	} + +	if (nflag == 0) +		closelog(); + +	return 0; +} diff --git a/util/sbase/crypt.h b/util/sbase/crypt.h new file mode 100644 index 00000000..2fd2932e --- /dev/null +++ b/util/sbase/crypt.h @@ -0,0 +1,12 @@ +/* See LICENSE file for copyright and license details. */ +struct crypt_ops { +	void (*init)(void *); +	void (*update)(void *, const void *, unsigned long); +	void (*sum)(void *, uint8_t *); +	void *s; +}; + +int cryptcheck(int, char **, struct crypt_ops *, uint8_t *, size_t); +int cryptmain(int, char **, struct crypt_ops *, uint8_t *, size_t); +int cryptsum(struct crypt_ops *, int, const char *, uint8_t *); +void mdprint(const uint8_t *, const char *, size_t); diff --git a/util/sbase/cut.1 b/util/sbase/cut.1 new file mode 100644 index 00000000..7a5174e6 --- /dev/null +++ b/util/sbase/cut.1 @@ -0,0 +1,69 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +.Pp +The possibility of separating numbers and ranges with a space and specifying +multibyte delimiters of arbitrary length is an extension to that specification. diff --git a/util/sbase/cut.c b/util/sbase/cut.c new file mode 100644 index 00000000..a50bdcb5 --- /dev/null +++ b/util/sbase/cut.c @@ -0,0 +1,215 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "text.h" +#include "utf.h" +#include "util.h" + +typedef struct Range { +	size_t min, max; +	struct Range *next; +} Range; + +static Range *list     = NULL; +static char   mode     = 0; +static char  *delim    = "\t"; +static size_t delimlen = 1; +static int    nflag    = 0; +static int    sflag    = 0; + +static void +insert(Range *r) +{ +	Range *l, *p, *t; + +	for (p = NULL, l = list; l; p = l, l = l->next) { +		if (r->max && r->max + 1 < l->min) { +			r->next = l; +			break; +		} else if (!l->max || r->min < l->max + 2) { +			l->min = MIN(r->min, l->min); +			for (p = l, t = l->next; t; p = t, t = t->next) +				if (r->max && r->max + 1 < t->min) +					break; +			l->max = (p->max && r->max) ? MAX(p->max, r->max) : 0; +			l->next = t; +			return; +		} +	} +	if (p) +		p->next = r; +	else +		list = r; +} + +static void +parselist(char *str) +{ +	char *s; +	size_t n = 1; +	Range *r; + +	if (!*str) +		eprintf("empty list\n"); +	for (s = str; *s; s++) { +		if (*s == ' ') +			*s = ','; +		if (*s == ',') +			n++; +	} +	r = ereallocarray(NULL, n, sizeof(*r)); +	for (s = str; n; n--, s++) { +		r->min = (*s == '-') ? 1 : strtoul(s, &s, 10); +		r->max = (*s == '-') ? strtoul(s + 1, &s, 10) : r->min; +		r->next = NULL; +		if (!r->min || (r->max && r->max < r->min) || (*s && *s != ',')) +			eprintf("bad list value\n"); +		insert(r++); +	} +} + +static size_t +seek(struct line *s, size_t pos, size_t *prev, size_t count) +{ +	size_t n = pos - *prev, i, j; + +	if (mode == 'b') { +		if (n >= s->len) +			return s->len; +		if (nflag) +			while (n && !UTF8_POINT(s->data[n])) +				n--; +		*prev += n; +		return n; +	} else if (mode == 'c') { +		for (n++, i = 0; i < s->len; i++) +			if (UTF8_POINT(s->data[i]) && !--n) +				break; +	} else { +		for (i = (count < delimlen + 1) ? 0 : delimlen; n && i < s->len; ) { +			if ((s->len - i) >= delimlen && +			    !memcmp(s->data + i, delim, delimlen)) { +				if (!--n && count) +					break; +				i += delimlen; +				continue; +			} +			for (j = 1; j + i <= s->len && !fullrune(s->data + i, j); j++); +			i += j; +		} +	} +	*prev = pos; + +	return i; +} + +static void +cut(FILE *fp, const char *fname) +{ +	Range *r; +	struct line s; +	static struct line line; +	static size_t size; +	size_t i, n, p; +	ssize_t len; + +	while ((len = getline(&line.data, &size, fp)) > 0) { +		line.len = len; +		if (line.data[line.len - 1] == '\n') +			line.data[--line.len] = '\0'; +		if (mode == 'f' && !memmem(line.data, line.len, delim, delimlen)) { +			if (!sflag) { +				fwrite(line.data, 1, line.len, stdout); +				fputc('\n', stdout); +			} +			continue; +		} +		for (i = 0, p = 1, s = line, r = list; r; r = r->next) { +			n = seek(&s, r->min, &p, i); +			s.data += n; +			s.len -= n; +			i += (mode == 'f') ? delimlen : 1; +			if (!s.len) +				break; +			if (!r->max) { +				fwrite(s.data, 1, s.len, stdout); +				break; +			} +			n = seek(&s, r->max + 1, &p, i); +			i += (mode == 'f') ? delimlen : 1; +			if (fwrite(s.data, 1, n, stdout) != n) +				eprintf("fwrite <stdout>:"); +			s.data += n; +			s.len -= n; +		} +		putchar('\n'); +	} +	if (ferror(fp)) +		eprintf("getline %s:", fname); +} + +static void +usage(void) +{ +	eprintf("usage: %s -b list [-n] [file ...]\n" +	        "       %s -c list [file ...]\n" +	        "       %s -f list [-d delim] [-s] [file ...]\n", +		argv0, argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ +	FILE *fp; +	int ret = 0; + +	ARGBEGIN { +	case 'b': +	case 'c': +	case 'f': +		mode = ARGC(); +		parselist(EARGF(usage())); +		break; +	case 'd': +		delim = EARGF(usage()); +		if (!*delim) +			eprintf("empty delimiter\n"); +		delimlen = unescape(delim); +		break; +	case 'n': +		nflag = 1; +		break; +	case 's': +		sflag = 1; +		break; +	default: +		usage(); +	} ARGEND + +	if (!mode) +		usage(); + +	if (!argc) +		cut(stdin, "<stdin>"); +	else { +		for (; *argv; argc--, argv++) { +			if (!strcmp(*argv, "-")) { +				*argv = "<stdin>"; +				fp = stdin; +			} else if (!(fp = fopen(*argv, "r"))) { +				weprintf("fopen %s:", *argv); +				ret = 1; +				continue; +			} +			cut(fp, *argv); +			if (fp != stdin && fshut(fp, *argv)) +				ret = 1; +		} +	} + +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/date.1 b/util/sbase/date.1 new file mode 100644 index 00000000..fb9fb31b --- /dev/null +++ b/util/sbase/date.1 @@ -0,0 +1,81 @@ +.Dd October 8, 2015 +.Dt DATE 1 +.Os sbase +.Sh NAME +.Nm date +.Nd print or set date and time +.Sh SYNOPSIS +.Nm +.Op Fl d Ar time +.Op Fl u +.Oo +.Cm + Ns Ar format | +.Sm off +.Ar mmddHHMM Oo Oo Ar CC Oc Ar yy Oc +.Sm on +.Oc +.Sh DESCRIPTION +.Nm +prints the date and time according to +.Xr locale 7 +or +.Ar format +using +.Xr strftime 3 +or sets the date. +.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 or set UTC time instead of local time. +.El +.Pp +An operand with a leading plus +.Pq Cm + +sign signals a user-defined format string using +.Xr strftime 3 +conversion specifications. +.Pp +An operand without a leading plus sign is interpreted as a value +for setting the system's current date and time. +The canonical representation for setting the date and time is: +.Pp +.Bl -tag -width Ds -compact -offset indent +.It Ar mm +The month of the year, from 01 to 12. +.It Ar dd +The day of the month, from 01 to 31. +.It Ar HH +The hour of the day, from 00 to 23. +.It Ar MM +The minute of the hour, from 00 to 59. +.It Ar CC +The first two digits of the year (the century). +.It Ar yy +The second two digits of the year. +If +.Ar yy +is specified, but +.Ar CC +is not, a value for +.Ar yy +between 69 and 99 results in a +.Ar CC +value of 19. +Otherwise, a +.Ar CC +value of 20 is used. +.El +.Pp +The century and year are optional. +The default is the current year. +.Sh STANDARDS +POSIX.1-2013. +.Pp +The +.Op Fl d +flag is an extension to that specification. diff --git a/util/sbase/date.c b/util/sbase/date.c new file mode 100644 index 00000000..109f3710 --- /dev/null +++ b/util/sbase/date.c @@ -0,0 +1,103 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s [-u] [-d time] [+format | mmddHHMM[[CC]yy]]\n", argv0); +} + +static int +datefield(const char *s, size_t i) +{ +	if (!isdigit(s[i]) || !isdigit(s[i+1])) +		eprintf("invalid date format: %s\n", s); + +	return (s[i] - '0') * 10 + (s[i+1] - '0'); +} + +static void +setdate(const char *s, struct tm *now) +{ +	struct tm date; +	struct timespec ts; + +	switch (strlen(s)) { +	case 8: +		date.tm_year = now->tm_year; +		break; +	case 10: +		date.tm_year = datefield(s, 8); +		if (date.tm_year < 69) +			date.tm_year += 100; +		break; +	case 12: +		date.tm_year = ((datefield(s, 8) - 19) * 100) + datefield(s, 10); +		break; +	default: +		eprintf("invalid date format: %s\n", s); +		break; +	} + +	date.tm_mon = datefield(s, 0) - 1; +	date.tm_mday = datefield(s, 2); +	date.tm_hour = datefield(s, 4); +	date.tm_min = datefield(s, 6); +	date.tm_sec = 0; +	date.tm_isdst = -1; + +	ts.tv_sec = mktime(&date); +	if (ts.tv_sec == -1) +		eprintf("mktime:"); +	ts.tv_nsec = 0; + +	if (clock_settime(CLOCK_REALTIME, &ts) == -1) +		eprintf("clock_settime:"); +} + +int +main(int argc, char *argv[]) +{ +	struct tm *now; +	time_t t; +	char buf[BUFSIZ], *fmt = "%a %b %e %H:%M:%S %Z %Y"; + +	t = time(NULL); +	if (t == -1) +		eprintf("time:"); + +	ARGBEGIN { +	case 'd': +		t = estrtonum(EARGF(usage()), 0, LLONG_MAX); +		break; +	case 'u': +		if (setenv("TZ", "UTC0", 1) < 0) +			eprintf("setenv:"); +		break; +	default: +		usage(); +	} ARGEND + +	if (!(now = localtime(&t))) +		eprintf("localtime:"); +	if (argc) { +		if (argc != 1) +			usage(); +		if (argv[0][0] != '+') { +			setdate(argv[0], now); +			return 0; +		} +		fmt = &argv[0][1]; +	} + +	strftime(buf, sizeof(buf), fmt, now); +	puts(buf); + +	return fshut(stdout, "<stdout>"); +} diff --git a/util/sbase/dd.1 b/util/sbase/dd.1 new file mode 100644 index 00000000..39c5c228 --- /dev/null +++ b/util/sbase/dd.1 @@ -0,0 +1,91 @@ +.Dd April 28, 2020 +.Dt DD 1 +.Os sbase +.Sh NAME +.Nm dd +.Nd convert and copy a file +.Sh SYNOPSIS +.Nm +.Op Ar operand Ns ... +.Sh DESCRIPTION +.Nm +copies its input to its output, possibly after conversion, using +the specified block sizes, +.Pp +The following operands are available: +.Bl -tag -width ibs=expr +.It Cm if= Ns Ar file +Read from the file named by +.Ar file +instead of standard input. +.It Cm of= Ns Ar file +Write to the file named by +.Ar file +instead of standard output. +.It Cm ibs= Ns Ar expr +Set the input block size to +.Ar expr +(defaults to 512). +.It Cm obs= Ns Ar expr +Set the output block size to +.Ar expr +(defaults to 512). +.It Cm bs= Ns Ar expr +Set the input and output block sizes to +.Ar expr . +Additionally, if no conversion other than +.Cm noerror , +.Cm notrunc , +or +.Cm sync +is specified, input blocks are copied as single output blocks, even +when the input block is short. +.It Cm skip= Ns Ar n +Skip +.Ar n +input blocks before starting to copy. +.It Cm seek= Ns Ar n +Skip +.Ar n +output blocks before starting to copy. +.It Cm count= Ns Ar n +Copy at most +.Ar n +input blocks. +.It Cm conv= Ns Ar value Ns Op , Ns Ar value Ns ... +Apply the conversions specified by +.Ar value . +.Bl -tag -width Ds +.It Cm lcase +Map uppercase characters to the corresponding lowercase character +using +.Fn tolower . +.It Cm ucase +Map lowercase characters to the corresponding uppercase character +using +.Fn toupper . +.It Cm swab +Swap each pair of bytes in the input block. +If there is an odd number of bytes in a block, the last one is +unmodified. +.It Cm noerror +In case of an error reading from the input, do not fail. +Instead, print a diagnostic message and a summary of the current +status. +.It Cm notrunc +Do not truncate the output file. +.It Cm sync +In case of a partial input block, pad with null bytes to form a +complete block. +.El +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification, except that it does not implement the +.Cm block +and +.Cm unblock +conversions. diff --git a/util/sbase/dd.c b/util/sbase/dd.c new file mode 100644 index 00000000..36eb4094 --- /dev/null +++ b/util/sbase/dd.c @@ -0,0 +1,237 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static off_t ifull, ofull, ipart, opart; + +static void +usage(void) +{ +	eprintf("usage: %s [operand...]\n", argv0); +} + +static size_t +parsesize(char *expr) +{ +	char *s = expr; +	size_t n = 1; + +	for (;;) { +		n *= strtoumax(s, &s, 10); +		switch (*s) { +		case 'k': n <<= 10; s++; break; +		case 'b': n <<= 9; s++; break; +		} +		if (*s != 'x' || !s[1]) +			break; +		s++; +	} +	if (*s || n == 0) +		eprintf("invalid block size expression '%s'\n", expr); + +	return n; +} + +static void +bswap(unsigned char *buf, size_t len) +{ +	int c; + +	for (len &= ~1; len > 0; buf += 2, len -= 2) { +		c = buf[0]; +		buf[0] = buf[1]; +		buf[1] = c; +	} +} + +static void +lcase(unsigned char *buf, size_t len) +{ +	for (; len > 0; buf++, len--) +		buf[0] = tolower(buf[0]); +} + +static void +ucase(unsigned char *buf, size_t len) +{ +	for (; len > 0; buf++, len--) +		buf[0] = toupper(buf[0]); +} + +static void +summary(void) +{ +	fprintf(stderr, "%"PRIdMAX"+%"PRIdMAX" records in\n", (intmax_t)ifull, (intmax_t)ipart); +	fprintf(stderr, "%"PRIdMAX"+%"PRIdMAX" records out\n", (intmax_t)ofull, (intmax_t)opart); +} + +int +main(int argc, char *argv[]) +{ +	enum { +		LCASE   = 1 << 0, +		UCASE   = 1 << 1, +		SWAB    = 1 << 2, +		NOERROR = 1 << 3, +		NOTRUNC = 1 << 4, +		SYNC    = 1 << 5, +	} conv = 0; +	char *arg, *val, *end; +	const char *iname = "-", *oname = "-"; +	int ifd = 0, ofd = 1, eof = 0; +	size_t len, bs = 0, ibs = 512, obs = 512, ipos = 0, opos = 0; +	off_t skip = 0, seek = 0, count = -1; +	ssize_t ret; +	unsigned char *buf; + +	argv0 = argc ? (argc--, *argv++) : "dd"; +	for (; argc > 0; argc--, argv++) { +		arg = *argv; +		val = strchr(arg, '='); +		if (!val) +			usage(); +		*val++ = '\0'; +		if (strcmp(arg, "if") == 0) { +			iname = val; +		} else if (strcmp(arg, "of") == 0) { +			oname = val; +		} else if (strcmp(arg, "ibs") == 0) { +			ibs = parsesize(val); +		} else if (strcmp(arg, "obs") == 0) { +			obs = parsesize(val); +		} else if (strcmp(arg, "bs") == 0) { +			bs = parsesize(val); +		} else if (strcmp(arg, "skip") == 0) { +			skip = estrtonum(val, 0, LLONG_MAX); +		} else if (strcmp(arg, "seek") == 0) { +			seek = estrtonum(val, 0, LLONG_MAX); +		} else if (strcmp(arg, "count") == 0) { +			count = estrtonum(val, 0, LLONG_MAX); +		} else if (strcmp(arg, "conv") == 0) { +			do { +				end = strchr(val, ','); +				if (end) +					*end++ = '\0'; +				if (strcmp(val, "lcase") == 0) +					conv |= LCASE; +				else if (strcmp(val, "ucase") == 0) +					conv |= UCASE; +				else if (strcmp(val, "swab") == 0) +					conv |= SWAB; +				else if (strcmp(val, "noerror") == 0) +					conv |= NOERROR; +				else if (strcmp(val, "notrunc") == 0) +					conv |= NOTRUNC; +				else if (strcmp(val, "sync") == 0) +					conv |= SYNC; +				else +					eprintf("unknown conv flag '%s'\n", val); +				val = end; +			} while (val); +		} else { +			weprintf("unknown operand '%s'\n", arg); +			usage(); +		} +	} + +	if (bs) +		ibs = obs = bs; +	if (strcmp(iname, "-") != 0) { +		ifd = open(iname, O_RDONLY); +		if (ifd < 0) +			eprintf("open %s:", iname); +	} +	if (strcmp(oname, "-") != 0) { +		ofd = open(oname, O_WRONLY | O_CREAT | (conv & NOTRUNC || seek ? 0 : O_TRUNC), 0666); +		if (ofd < 0) +			eprintf("open %s:", oname); +	} + +	len = MAX(ibs, obs) + ibs; +	buf = emalloc(len); +	if (skip && lseek(ifd, skip * ibs, SEEK_SET) < 0) { +		while (skip--) { +			ret = read(ifd, buf, ibs); +			if (ret < 0) +				eprintf("read:"); +			if (ret == 0) { +				eof = 1; +				break; +			} +		} +	} +	if (seek) { +		if (!(conv & NOTRUNC) && ftruncate(ofd, seek * ibs) != 0) +			eprintf("ftruncate:"); +		if (lseek(ofd, seek * ibs, SEEK_SET) < 0) +			eprintf("lseek:"); +		/* XXX: handle non-seekable files */ +	} +	while (!eof) { +		while (ipos - opos < obs) { +			if (ifull + ipart == count) { +				eof = 1; +				break; +			} +			ret = read(ifd, buf + ipos, ibs); +			if (ret == 0) { +				eof = 1; +				break; +			} +			if (ret < 0) { +				weprintf("read:"); +				if (!(conv & NOERROR)) +					return 1; +				summary(); +				if (!(conv & SYNC)) +					continue; +				ret = 0; +			} +			if (ret < ibs) { +				ipart++; +				if (conv & SYNC) { +					memset(buf + ipos + ret, 0, ibs - ret); +					ret = ibs; +				} +			} else { +				ifull++; +			} +			if (conv & SWAB) +				bswap(buf + ipos, ret); +			if (conv & LCASE) +				lcase(buf + ipos, ret); +			if (conv & UCASE) +				ucase(buf + ipos, ret); +			ipos += ret; +			if (bs && !(conv & (SWAB | LCASE | UCASE))) +				break; +		} +		if (ipos == opos) +			break; +		do { +			ret = write(ofd, buf + opos, MIN(obs, ipos - opos)); +			if (ret < 0) +				eprintf("write:"); +			if (ret == 0) +				eprintf("write returned 0\n"); +			if (ret < obs) +				opart++; +			else +				ofull++; +			opos += ret; +		} while (ipos - opos >= (eof ? 1 : obs)); +		if (opos < ipos) +			memmove(buf, buf + opos, ipos - opos); +		ipos -= opos; +		opos = 0; +	} +	summary(); + +	return 0; +} diff --git a/util/sbase/dirname.1 b/util/sbase/dirname.1 new file mode 100644 index 00000000..742c9912 --- /dev/null +++ b/util/sbase/dirname.1 @@ -0,0 +1,19 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/dirname.c b/util/sbase/dirname.c new file mode 100644 index 00000000..45e1a7e2 --- /dev/null +++ b/util/sbase/dirname.c @@ -0,0 +1,27 @@ +/* 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[]) +{ +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	if (argc != 1) +		usage(); + +	puts(dirname(argv[0])); + +	return fshut(stdout, "<stdout>"); +} diff --git a/util/sbase/du.1 b/util/sbase/du.1 new file mode 100644 index 00000000..ef49052e --- /dev/null +++ b/util/sbase/du.1 @@ -0,0 +1,57 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +.Pp +The +.Op Fl dhP +flags are an extension to that specification. diff --git a/util/sbase/du.c b/util/sbase/du.c new file mode 100644 index 00000000..782b09a2 --- /dev/null +++ b/util/sbase/du.c @@ -0,0 +1,167 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> +#include <sys/types.h> + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <search.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; + +struct file { +	dev_t devno; +	ino_t inode; +}; + +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 int +cmp(const void *p1, const void *p2) +{ +	const struct file *f1 = p1, *f2 = p2; + +	if (f1->devno > f2->devno) +		return -1; +	if (f1->devno < f2->devno) +		return 1; + +	/* f1->devno == f2->devno */ +	if (f1->inode < f2->inode) +		return -1; +	if (f1->inode > f2->inode) +		return 1; + +	return 0; +} + +static int +duplicated(dev_t dev, ino_t ino) +{ +	static void *tree; +	struct file **fpp, *fp, file = {dev, ino}; + +	if ((fpp = tsearch(&file, &tree, cmp)) == NULL) +		eprintf("%s:", argv0); + +	if (*fpp != &file) +		return 1; + +	/* new file added */ +	fp = emalloc(sizeof(*fp)); +	*fp = file; +	*fpp = fp; + +	return 0; +} + +static void +du(int dirfd, const char *path, struct stat *st, void *data, struct recursor *r) +{ +	off_t *total = data, subtotal; + +	subtotal = nblks(st->st_blocks); +	if (S_ISDIR(st->st_mode)) { +		recurse(dirfd, path, &subtotal, r); +	} else if (r->follow != 'P' || st->st_nlink > 1) { +		if (duplicated(st->st_dev, st->st_ino)) +			goto print; +	} + +	*total += subtotal; + +print: +	if (!r->depth) +		printpath(*total, r->path); +	else if (!sflag && r->depth <= maxdepth && (S_ISDIR(st->st_mode) || aflag)) +		printpath(subtotal, r->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, .follow = 'P' }; +	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(AT_FDCWD, ".", &n, &r); +	} else { +		for (; *argv; argc--, argv++) { +			n = 0; +			recurse(AT_FDCWD, *argv, &n, &r); +		} +	} + +	return fshut(stdout, "<stdout>") || recurse_status; +} diff --git a/util/sbase/echo.1 b/util/sbase/echo.1 new file mode 100644 index 00000000..04ba8b9b --- /dev/null +++ b/util/sbase/echo.1 @@ -0,0 +1,27 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +.Pp +The +.Op Fl n +flag is an extension to that specification. diff --git a/util/sbase/echo.c b/util/sbase/echo.c new file mode 100644 index 00000000..a5526311 --- /dev/null +++ b/util/sbase/echo.c @@ -0,0 +1,24 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <string.h> +#include "util.h" + +int +main(int argc, char *argv[]) +{ +	int nflag = 0; + +	argv0 = *argv, argv0 ? (argc--, argv++) : (void *)0; + +	if (*argv && !strcmp(*argv, "-n")) { +		nflag = 1; +		argc--, argv++; +	} + +	for (; *argv; argc--, argv++) +		putword(stdout, *argv); +	if (!nflag) +		putchar('\n'); + +	return fshut(stdout, "<stdout>"); +} diff --git a/util/sbase/ed.1 b/util/sbase/ed.1 new file mode 100644 index 00000000..14e88346 --- /dev/null +++ b/util/sbase/ed.1 @@ -0,0 +1,238 @@ +.Dd December 27, 2016 +.Dt ED 1 +.Os sbase +.Sh NAME +.Nm ed +.Nd text editor +.Sh SYNOPSIS +.Nm +.Op Fl s +.Op Fl p Ar string +.Op Ar file +.Sh DESCRIPTION +.Nm +is the standard text editor. +It performs line-oriented operations on a buffer; The buffer's contents are +manipulated in command mode and text is written to the buffer in input mode. +Command mode is the default. +To exit input mode enter a dot ('.') on a line of its own. +.Pp +If +.Nm +is invoked with a file as an argument, it will simulate an edit command and read +the file's contents into a buffer. +Changes to this buffer are local to +.Nm +until a write command is given. +.Pp +.Nm +uses the basic regular expression syntax and allows any character but space and +newline to be used as a delimiter in regular expressions. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl s +Suppress diagnostic messages +.It Fl p Ar string +Use +.Ar string +as a prompt when in command mode +.El +.Sh EXTENDED DESCRIPTION +.Ss Addresses +Commands operate on addresses. +Addresses are used to refer to lines within the buffer. +Address ranges may have spaces before and after the separator. +Unless otherwise specified, 0 is an invalid address. +The following symbols are valid addresses: +.Bl -tag -width Ds +.It n +The nth line. +.It . +The current line, or "dot". +.It $ +The last line. +.It + +The next line. +.It +n +The nth next line. +.It ^ or - +The previous line. +.It ^n or -n +The nth previous line. +.It x,y +The range of lines from x to y. +The default value of x is 1, and the default value of y is $. +.It x;y +As above, except that the current line is set to x. +Omitting x in this case uses the current line as the default value. +.It /re/ +The next line matching re. +.It ?re? +The last line matching re. +.It 'c +The line marked by c. See k below. +.El +.Ss Commands +.Nm +expects to see one command per line, with the following exception: commands may +be suffixed with either a list, number, or print command. +These suffixed commands are run after the command they're suffixed to has +executed. +.Pp +The following is the list of commands that +.Nm +knows about. +The parentheses contain the default addresses that a command uses. +.Bl -tag -width Ds +.It (.)a +Append text after the addressed line. +The dot is set to the last line entered. +If no text was entered, the dot is set to the addressed line. +An address of 0 appends to the start of the buffer. +.It (.,.)c +Delete the addressed lines and then accept input to replace them. +The dot is set to the last line entered. +If no text was entered, the dot is set to the line before the deleted lines. +.It (.,.)d +Delete the addressed lines. +If there is a line after the deleted range, the dot is set to it. +Otherwise, the dot is set to the line before the deleted range. +.It e Ar file +Delete the contents of the buffer and load in +.Ar file +for editing, printing the bytes read to standard output. +If no filename is given, +.Nm +uses the currently remembered filename. +The remembered filename is set to +.Ar file +for later use. +.It E Ar file +As above, but without warning if the current buffer has unsaved changes. +.It f Ar file +Set the currently remembered filename to +.Ar file +, or print the currently remembered filename if +.Ar file +is omitted. +.It (1,$)g/re/command +Apply command to lines matching re. +The dot is set to the matching line before command is executed. +When each matching line has been operated on, the dot is set to the last line +operated on. +If no lines match then the dot remains unchanged. +The command used may not be g, G, v, or V. +.It (1,$)G/re/ +Interactively edit the range of line addresses that match re. +The dot is set to the matching line and printed before a command is input. +When each matching line has been operated on, the dot is set to the last line +operated on. +If no lines match then the dot remains unchanged. +The command used may not be a, c, i, g, G, v, or V. +.It h +Print the reason for the most recent error. +.It H +Toggle error explanations. +If on, the above behaviour is produced on all subsequent errors. +.It (.)i +Insert text into the buffer before the addressed line. +The dot is set to the last line entered. +If no text was entered, the dot is set to the addressed line +.It (.,.+1)j +Join two lines together. +If only one address is given, nothing happens. +The dot is set to the newly joined line. +.It (.)kc +Mark the line with the lower case character c. The dot is unchanged. +.It (.,.)l +Unambiguously print the addressed lines. +The dot is set to the last line written. +.It (.,.)m(.) +Move lines in the buffer to the line address on the right hand side. +An address of 0 on the right hand side moves to the start of the buffer. +The dot is set to the last line moved. +.It (.,.)n +Print the addressed lines and their numbers. +The dot is set to the last line printed. +.It (.,.)p +Print the addressed lines. +The dot is set to the last line printed. +.It P +Toggle the prompt. +Defaults to off, but is switched on if the -p flag is used. +.It q +Quit +.Nm +, warning if there are unsaved changes. +.It Q +As above, but without warning if the current buffer has unsaved changes. +.It ($)r Ar file +Read in +.Ar file +and append it to the current buffer, printing the bytes read to standard output. +The currently remembered filename isn't changed unless it's empty. +An address of 0 reads the file into the start of the buffer. +.It (.,.)s/re/replacement/flags +Substitute re for replacement in lines matching re. +An & within replacement is replaced with the whole string matched by re. +Backrefs can be used with the form \\n, where n is a positive non-zero integer. +When % is the only character in replacement, it is substituted for the +replacement string from the last substitute command. +If a newline is part of replacement then the matched string is split into two +lines; this cannot be done as part of a g or v command. +If flags contains an integer n, then the nth match is replaced. +If flags contains g, all matches are replaced. +The dot is set to the last line matched. +.It (.,.)t(.) +As m, but copying instead of moving. +The dot is set to the last line added. +.It u +Undo the last change. +The dot is set to whatever it was before the undone command was performed. +.It (1.$)v/re/command +As with g, but operating on lines that don't match re. +.It (1.$)V/re/ +As with G, but operating on lines that don't match re. +.It (1,$)w Ar file +Write the addressed lines to +.Ar file +, overwriting its previous contents if the file exists, and print the number of +bytes written. +If no filename is given the currently remembered filename will be used instead. +The dot is unchanged. +.It (1,$)W Ar file +As above, but instead of overwriting the contents of +.Ar file +the addressed lines are appended to +.Ar file +instead. +.It (.+1) +Print the addressed line. +Sets the dot to that line. +.It ($)= +Print the line number of the addressed line. +The dot is unchanged. +.It & +Repeat the last command. +.It ! Ar command +Execute +.Ar command +using sh. +If the first character of +.Ar command +is '!' then it is replaced with the text of the previous command. +An unescaped % is replaced with the currently remembered filename. +! does not process escape characters. +When +.Ar command +returns a '!' is printed. +The dot is unchanged. +.El +.Sh SEE ALSO +.Xr sed 1 , +.Xr regexp 3 +.Sh STANDARDS +POSIX.1-2013. +Except where noted here: +g and v operate on single commands rather than lists delimited with '\e'. +e, E, r, w, and W commands cannot accept shell escapes. diff --git a/util/sbase/ed.c b/util/sbase/ed.c new file mode 100644 index 00000000..bec9b2da --- /dev/null +++ b/util/sbase/ed.c @@ -0,0 +1,1650 @@ +/* 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 +#define AFTER     0 +#define BEFORE    1 + +typedef struct { +	char *str; +	size_t cap; +	size_t siz; +} String; + +struct hline { +	off_t seek; +	char  global; +	int   next, prev; +}; + +struct undo { +	int curln, lastln; +	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 String lastre; + +static int optverbose, optprompt, exstatus, optdiag = 1; +static int marks['z' - 'a']; +static int nlines, line1, line2; +static int curln, lastln, ocurln, olastln; +static jmp_buf savesp; +static char *lasterr; +static size_t idxsize, lastidx; +static struct hline *zero; +static String text; +static char savfname[FILENAME_MAX]; +static char tmpname[FILENAME_MAX]; +static int scratch; +static int pflag, modflag, uflag, gflag; +static size_t csize; +static String cmdline; +static char *ocmdline; +static int inputidx; +static char *rhs; +static char *lastmatch; +static struct undo udata; +static int newcmd; +static int eol, bol; + +static sig_atomic_t intr, hup; + +static void undo(void); + +static void +error(char *msg) +{ +	exstatus = 1; +	lasterr = msg; +	puts("?"); + +	if (optverbose) +		puts(msg); +	if (!newcmd) +		undo(); + +	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 String * +copystring(String *s, char *from) +{ +	size_t len; +	char *t; + +	if ((t = strdup(from)) == NULL) +		error("out of memory"); +	len = strlen(t); + +	free(s->str); +	s->str = t; +	s->siz = len; +	s->cap = len; + +	return s; +} + +static String * +string(String *s) +{ +	free(s->str); +	s->str = NULL; +	s->siz = 0; +	s->cap = 0; + +	return s; +} + +static char * +addchar(char c, String *s) +{ +	size_t cap = s->cap, siz = s->siz; +	char *t = s->str; + +	if (siz >= cap && +	    (cap > SIZE_MAX - LINESIZE || +	     (t = realloc(t, cap += LINESIZE)) == NULL)) +			error("out of memory"); +	t[siz++] = c; +	s->siz = siz; +	s->cap = cap; +	s->str = t; +	return t; +} + +static void chksignals(void); + +static int +input(void) +{ +	int ch; + +	chksignals(); + +	ch = cmdline.str[inputidx]; +	if (ch != '\0') +		inputidx++; +	return ch; +} + +static int +back(int c) +{ +	if (c == '\0') +		return c; +	return cmdline.str[--inputidx] = c; +} + +static int +makeline(char *s, int *off) +{ +	struct hline *lp; +	size_t len; +	char *begin = s; +	int c; + +	if (lastidx >= idxsize) { +		lp = NULL; +		if (idxsize <= SIZE_MAX - NUMLINES) +			lp = reallocarray(zero, idxsize + NUMLINES, sizeof(*lp)); +		if (!lp) +			error("out of memory"); +		idxsize += NUMLINES; +		zero = lp; +	} +	lp = zero + lastidx; +	lp->global = 0; + +	if (!s) { +		lp->seek = -1; +		len = 0; +	} else { +		while ((c = *s++) && c != '\n') +			; +		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; + +	if (line == -1) +		line = 0; +	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); +	text.siz = 0; +	off = lp->seek; + +	if (off == (off_t) -1) +		return addchar('\0', &text); + +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; +		addchar(*p, &text); +	} +	if (csize && p == buf + csize) +		goto repeat; + +	addchar('\n', &text); +	addchar('\0', &text); +	return text.str; +} + +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 +newundo(int from1, int from2) +{ +	struct link *p; + +	if (newcmd) { +		clearundo(); +		udata.curln = ocurln; +		udata.lastln = olastln; +	} +	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; +} + +/* + * relink: to1   <- from1 + *         from2 -> to2 + */ +static void +relink(int to1, int from1, int from2, int to2) +{ +	newundo(from1, from2); +	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 > 0; --p) { +		--udata.nr; +		zero[p->from1].next = p->to1; +		zero[p->from2].prev = p->to2; +	} +	free(udata.vec); +	udata.vec = NULL; +	udata.cap = 0; +	curln = udata.curln; +	lastln = udata.lastln; +} + +static void +inject(char *s, int where) +{ +	int off, k, begin, end; + +	if (where == BEFORE) { +		begin = getindex(curln-1); +		end = getindex(nextln(curln-1)); +	} else { +		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(void) +{ +	if (scratch) +		close(scratch); +	remove(tmpname); +	free(zero); +	zero = NULL; +	scratch = csize = idxsize = lastidx = curln = lastln = 0; +	modflag = lastln = curln = 0; +} + +static void +setscratch(void) +{ +	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 char buf[BUFSIZ]; + +	if (!isgraph(delim)) +		error("invalid pattern delimiter"); + +	eol = bol = bracket = lastre.siz = 0; +	for (n = 0;; ++n) { +		c = input(); +		if (c == delim && !bracket || c == '\0') { +			break; +		} else if (c == '^') { +			bol = 1; +		} else if (c == '$') { +			eol = 1; +		} else if (c == '\\') { +			addchar(c, &lastre); +			c = input(); +		} else if (c == '[') { +			bracket = 1; +		} else if (c == ']') { +			bracket = 0; +		} +		addchar(c, &lastre); +	} +	if (n == 0) { +		if (!pattern) +			error("no previous pattern"); +		return; +	} +	addchar('\0', &lastre); + +	if (pattern) +		regfree(pattern); +	if (!pattern && (!(pattern = malloc(sizeof(*pattern))))) +		error("out of memory"); +	if ((ret = regcomp(pattern, lastre.str, 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 { +		chksignals(); + +		i = (way == '?') ? prevln(i) : nextln(i); +		if (i > 0 && 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') +		; +	back(c); +} + +static void +ensureblank(void) +{ +	char c; + +	switch ((c = input())) { +	case ' ': +	case '\t': +		skipblank(); +	case '\0': +		back(c); +		break; +	default: +		error("unknown command"); +	} +} + +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 (!islower(c = input())) +			error("invalid mark character"); +		if (!(ln = marks[c - 'a'])) +			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(void) +{ +	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 +quit(void) +{ +	clearbuf(); +	exit(exstatus); +} + +static void +setinput(char *s) +{ +	copystring(&cmdline, s); +	inputidx = 0; +} + +static void +getinput(void) +{ +	int ch; + +	string(&cmdline); + +	while ((ch = getchar()) != '\n' && ch != EOF) { +		if (ch == '\\') { +			if ((ch = getchar()) == EOF) +				break; +			if (ch != '\n') { +				ungetc(ch, stdin); +				ch = '\\'; +			} +		} +		addchar(ch, &cmdline); +	} + +	addchar('\0', &cmdline); +	inputidx = 0; + +	if (ch == EOF) { +		chksignals(); +		if (ferror(stdin)) { +			exstatus = 1; +			fputs("ed: error reading input\n", stderr); +		} +		quit(); +	} +} + +static int +moreinput(void) +{ +	if (!uflag) +		return cmdline.str[inputidx] != '\0'; + +	getinput(); +	return 1; +} + +static void dowrite(const char *, int); + +static void +dump(void) +{ +	char *home; + +	if (modflag) +		return; + +	line1 = nextln(0); +	line2 = lastln; + +	if (!setjmp(savesp)) { +		dowrite("ed.hup", 1); +		return; +	} + +	home = getenv("HOME"); +	if (!home || chdir(home) < 0) +		return; + +	if (!setjmp(savesp)) +		dowrite("ed.hup", 1); +} + +static void +chksignals(void) +{ +	if (hup) { +		exstatus = 1; +		dump(); +		quit(); +	} + +	if (intr) { +		intr = 0; +		newcmd = 1; +		clearerr(stdin); +		error("Interrupt"); +	} +} + +static void +dowrite(const char *fname, int trunc) +{ +	size_t bytecount = 0; +	int i, r, line; +	FILE *aux; +	static int sh; +	static FILE *fp; + +	if (fp) { +		sh ? pclose(fp) : fclose(fp); +		fp = NULL; +	} + +	if(fname[0] == '!') { +		sh = 1; +		fname++; +		if((fp = popen(fname, "w")) == NULL) +			error("bad exec"); +	} else { +		sh = 0; +		if ((fp = fopen(fname, "w")) == NULL) +			error("cannot open input file"); +	} + +	line = curln; +	for (i = line1; i <= line2; ++i) { +		chksignals(); + +		gettxt(i); +		bytecount += text.siz - 1; +		fwrite(text.str, 1, text.siz - 1, fp); +	} + +	curln = line2; + +	aux = fp; +	fp = NULL; +	r = sh ? pclose(aux) : fclose(aux); +	if (r) +		error("input/output error"); +	strcpy(savfname, fname); +	modflag = 0; +	curln = line; +	if (optdiag) +		printf("%zu\n", bytecount); +} + +static void +doread(const 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) { +		chksignals(); +		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, AFTER); +	} +	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) { +		chksignals(); +		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) +		puts(lasterr); +} + +static void +chkprint(int flag) +{ +	int c; + +	if (flag) { +		if ((c = input()) == 'p' || c == 'l' || c == 'n') +			pflag = c; +		else +			back(c); +	} +	if ((c = input()) != '\0' && c != '\n') +		error("invalid command suffix"); +} + +static char * +getfname(int comm) +{ +	int c; +	char *bp; +	static char fname[FILENAME_MAX]; + +	skipblank(); +	for (bp = fname; bp < &fname[FILENAME_MAX]; *bp++ = c) { +		if ((c = input()) == '\0') +			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) +{ +	int ch; +	static String line; + +	curln = num; +	while (moreinput()) { +		string(&line); +		while ((ch = input()) != '\n' && ch != '\0') +			addchar(ch, &line); +		addchar('\n', &line); +		addchar('\0', &line); + +		if (!strcmp(line.str, ".\n") || !strcmp(line.str, ".")) +			break; +		inject(line.str, AFTER); +	} +} + +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; +	static String s; + +	string(&s); +	for (i = line1;; i = nextln(i)) { +		chksignals(); +		for (t = gettxt(i); (c = *t) != '\n'; ++t) +			addchar(*t, &s); +		if (i == line2) +			break; +	} + +	addchar('\n', &s); +	addchar('\0', &s); +	delete(line1, line2); +	inject(s.str, BEFORE); +	free(s.str); +} + +static void +scroll(int num) +{ +	int max, ln, cnt; + +	if (!line1 || line1 == lastln) +		error("incorrect address"); + +	ln = line1; +	max = line1 + num; +	if (max > lastln) +		max = lastln; +	for (cnt = line1; cnt < max; cnt++) { +		chksignals(); +		fputs(gettxt(ln), stdout); +		ln = nextln(ln); +	} +	curln = ln; +} + +static void +copy(int where) +{ + +	if (!line1) +		error("incorrect address"); +	curln = where; + +	while (line1 <= line2) { +		chksignals(); +		inject(gettxt(line1), AFTER); +		if (line2 >= curln) +			line2 = nextln(line2); +		line1 = nextln(line1); +		if (line1 >= curln) +			line1 = nextln(line1); +	} +} + +static void +execsh(void) +{ +	static String cmd; +	char *p; +	int c, repl = 0; + +	skipblank(); +	if ((c = input()) != '!') { +		back(c); +		string(&cmd); +	} else if (cmd.siz) { +		--cmd.siz; +		repl = 1; +	} else { +		error("no previous command"); +	} + +	while ((c = input()) != '\0') { +		switch (c) { +		case '%': +			if (savfname[0] == '\0') +				error("no current filename"); +			repl = 1; +			for (p = savfname; *p; ++p) +				addchar(*p, &cmd); +			break; +		case '\\': +			c = input(); +			if (c != '%') { +				back(c); +				c = '\\'; +			} +		default: +			addchar(c, &cmd); +		} +	} +	addchar('\0', &cmd); + +	if (repl) +		puts(cmd.str); +	system(cmd.str); +	if (optdiag) +		puts("!"); +} + +static void +getrhs(int delim) +{ +	int c; +	static String s; + +	string(&s); +	while ((c = input()) != '\0' && c != delim) +		addchar(c, &s); +	addchar('\0', &s); +	if (c == '\0') { +		pflag = 'p'; +		back(c); +	} + +	if (!strcmp("%", s.str)) { +		if (!rhs) +			error("no previous substitution"); +		free(s.str); +	} else { +		free(rhs); +		rhs = s.str; +	} +	s.str = 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(String *s) +{ +	char *p; + +	for (p = lastmatch; p < lastmatch + matchs[0].rm_so; ++p) +		addchar(*p, s); +} + +static void +addpost(String *s) +{ +	char c, *p; + +	for (p = lastmatch + matchs[0].rm_eo; (c = *p); ++p) +		addchar(c, s); +	addchar('\0', s); +} + +static int +addsub(String *s, 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) +			addchar(*q++, s); +		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) +				addchar(*q++, s); +			break; +		default: +		copy_char: +			addchar(c, s); +			break; +		} +	} +	return 1; +} + +static void +subline(int num, int nth) +{ +	int i, m, changed; +	static String s; + +	string(&s); +	i = changed = 0; +	for (m = match(num); m; m = rematch(num)) { +		chksignals(); +		addpre(&s); +		changed |= addsub(&s, nth, ++i); +		if (eol || bol) +			break; +	} +	if (!changed) +		return; +	addpost(&s); +	delete(num, num); +	curln = prevln(num); +	inject(s.str, AFTER); +} + +static void +subst(int nth) +{ +	int i, line, next; + +	line = line1; +	for (i = 0; i < line2 - line1 + 1; i++) { +		chksignals(); + +		next = getindex(nextln(line)); +		subline(line, nth); + +		/* +		 * The substitution command can add lines, so +		 * we have to skip lines until we find the +		 * index that we saved before the substitution +		 */ +		do +			line = nextln(line); +		while (getindex(line) != next); +	} +} + +static void +docmd(void) +{ +	int cmd, c, line3, num, trunc; + +repeat: +	skipblank(); +	cmd = input(); +	trunc = pflag = 0; +	switch (cmd) { +	case '&': +		skipblank(); +		chkprint(0); +		if (!ocmdline) +			error("no previous command"); +		setinput(ocmdline); +		getlst(); +		goto repeat; +	case '!': +		execsh(); +		break; +	case '\0': +		num = gflag ? curln : curln+1; +		deflines(num, num); +		line1 = line2; +		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': +		ensureblank(); +		deflines(nextln(0), lastln); +		dowrite(getfname(cmd), trunc); +		break; +	case 'r': +		ensureblank(); +		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) +			line1++; +		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 != line2 && curln != 0) +	      		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 - 'a'] = 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': +		ensureblank(); +		if (nlines > 0) +			goto unexpected; +		if (back(input()) != '\0') +			getfname(cmd); +		else +			puts(savfname); +		chkprint(0); +		break; +	case 'E': +		modflag = 0; +	case 'e': +		ensureblank(); +		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) +		return; +	line1 = line2 = curln; + +print: +	doprint(); +} + +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) { +		chksignals(); +		if (i >= line1 && i <= line2) +			v = match(i) == dir; +		else +			v = 0; +		setglobal(i, v); +	} + +	return 1; +} + +static void +savecmd(void) +{ +	int ch; + +	skipblank(); +	ch = input(); +	if (ch != '&') { +		ocmdline = strdup(cmdline.str); +		if (ocmdline == NULL) +			error("out of memory"); +	} +	back(ch); +} + +static void +doglobal(void) +{ +	int cnt, ln, k, idx; + +	skipblank(); +	gflag = 1; +	if (uflag) +		chkprint(0); + +	ln = line1; +	for (cnt = 0; cnt < lastln; ) { +		chksignals(); +		k = getindex(ln); +		if (zero[k].global) { +			zero[k].global = 0; +			curln = ln; +			nlines = 0; + +			if (!uflag) { +				idx = inputidx; +				getlst(); +				docmd(); +				inputidx = idx; +				continue; +			} + +			line1 = line2 = ln; +			pflag = 0; +			doprint(); + +			for (;;) { +				getinput(); +				if (strcmp(cmdline.str, "") == 0) +					break; +				savecmd(); +				getlst(); +				docmd(); +			} + +		} else { +			cnt++; +			ln = nextln(ln); +		} +	} +} + +static void +usage(void) +{ +	eprintf("usage: %s [-s] [-p] [file]\n", argv0); +} + +static void +sigintr(int n) +{ +	intr = 1; +} + +static void +sighup(int dummy) +{ +	hup = 1; +} + +static void +edit(void) +{ +	for (;;) { +		newcmd = 1; +		ocurln = curln; +		olastln = lastln; +		if (optprompt) { +			fputs(prompt, stdout); +			fflush(stdout); +		} + +		getinput(); +		getlst(); +		chkglobal() ? doglobal() : docmd(); +	} +} + +static void +init(char *fname) +{ +	size_t len; + +	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(); + +	if (!setjmp(savesp)) { +		sigaction(SIGINT, +		          &(struct sigaction) {.sa_handler = sigintr}, +		          NULL); +		sigaction(SIGHUP, +		          &(struct sigaction) {.sa_handler = sighup}, +		          NULL); +		sigaction(SIGQUIT, +		          &(struct sigaction) {.sa_handler = SIG_IGN}, +		          NULL); +		init(*argv); +	} +	edit(); + +	/* not reached */ +	return 0; +} diff --git a/util/sbase/env.1 b/util/sbase/env.1 new file mode 100644 index 00000000..f25f54b1 --- /dev/null +++ b/util/sbase/env.1 @@ -0,0 +1,47 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +.Pp +The +.Op Fl u +flag is an extension to that specification. diff --git a/util/sbase/env.c b/util/sbase/env.c new file mode 100644 index 00000000..5d7e8a55 --- /dev/null +++ b/util/sbase/env.c @@ -0,0 +1,49 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +extern char **environ; + +static void +usage(void) +{ +	eprintf("usage: %s [-i] [-u var] ... [var=value] ... [cmd [arg ...]]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	int savederrno; + +	ARGBEGIN { +	case 'i': +		*environ = NULL; +		break; +	case 'u': +		if (unsetenv(EARGF(usage())) < 0) +			eprintf("unsetenv:"); +		break; +	default: +		usage(); +	} ARGEND + +	for (; *argv && strchr(*argv, '='); argc--, argv++) +		putenv(*argv); + +	if (*argv) { +		execvp(*argv, argv); +		savederrno = errno; +		weprintf("execvp %s:", *argv); +		_exit(126 + (savederrno == ENOENT)); +	} + +	for (; *environ; environ++) +		puts(*environ); + +	return fshut(stdout, "<stdout>"); +} diff --git a/util/sbase/expand.1 b/util/sbase/expand.1 new file mode 100644 index 00000000..9cf20fb4 --- /dev/null +++ b/util/sbase/expand.1 @@ -0,0 +1,47 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +.Pp +The +.Op Fl i +flag is an extension to that specification. diff --git a/util/sbase/expand.c b/util/sbase/expand.c new file mode 100644 index 00000000..f534134f --- /dev/null +++ b/util/sbase/expand.c @@ -0,0 +1,131 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "utf.h" +#include "util.h" + +static int     iflag      = 0; +static size_t *tablist    = NULL; +static size_t  tablistlen = 0; + +static size_t +parselist(const char *s) +{ +	size_t i; +	char  *p, *tmp; + +	tmp = estrdup(s); +	for (i = 0; (p = strsep(&tmp, " ,")); i++) { +		if (*p == '\0') +			eprintf("empty field in tablist\n"); +		tablist = ereallocarray(tablist, i + 1, sizeof(*tablist)); +		tablist[i] = estrtonum(p, 1, MIN(LLONG_MAX, SIZE_MAX)); +		if (i > 0 && tablist[i - 1] >= tablist[i]) +			eprintf("tablist must be ascending\n"); +	} +	tablist = ereallocarray(tablist, i + 1, sizeof(*tablist)); +	/* tab length = 1 for the overflowing case later in the matcher */ +	tablist[i] = 1; + +	return i; +} + +static int +expand(const char *file, FILE *fp) +{ +	size_t bol = 1, col = 0, i; +	Rune r; + +	while (efgetrune(&r, fp, file)) { +		switch (r) { +		case '\t': +			if (tablistlen == 1) +				i = 0; +			else for (i = 0; i < tablistlen; i++) +				if (col < tablist[i]) +					break; +			if (bol || !iflag) { +				do { +					col++; +					putchar(' '); +				} while (col % tablist[i]); +			} else { +				putchar('\t'); +				col = tablist[i]; +			} +			break; +		case '\b': +			bol = 0; +			if (col) +				col--; +			putchar('\b'); +			break; +		case '\n': +			bol = 1; +			col = 0; +			putchar('\n'); +			break; +		default: +			col++; +			if (r != ' ') +				bol = 0; +			efputrune(&r, stdout, "<stdout>"); +			break; +		} +	} + +	return 0; +} + +static void +usage(void) +{ +	eprintf("usage: %s [-i] [-t tablist] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	FILE *fp; +	int ret = 0; +	char *tl = "8"; + +	ARGBEGIN { +	case 'i': +		iflag = 1; +		break; +	case 't': +		tl = EARGF(usage()); +		if (!*tl) +			eprintf("tablist cannot be empty\n"); +		break; +	default: +		usage(); +	} ARGEND + +	tablistlen = parselist(tl); + +	if (!argc) { +		expand("<stdin>", stdin); +	} else { +		for (; *argv; argc--, argv++) { +			if (!strcmp(*argv, "-")) { +				*argv = "<stdin>"; +				fp = stdin; +			} else if (!(fp = fopen(*argv, "r"))) { +				weprintf("fopen %s:", *argv); +				ret = 1; +				continue; +			} +			expand(*argv, fp); +			if (fp != stdin && fshut(fp, *argv)) +				ret = 1; +		} +	} + +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/expr.1 b/util/sbase/expr.1 new file mode 100644 index 00000000..4710fc40 --- /dev/null +++ b/util/sbase/expr.1 @@ -0,0 +1,101 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/expr.c b/util/sbase/expr.c new file mode 100644 index 00000000..044c6c1a --- /dev/null +++ b/util/sbase/expr.c @@ -0,0 +1,244 @@ +/* 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 +tonum(struct val *v) +{ +	const char *errstr; +	long long d; + +	/* check if val is the result of an earlier calculation */ +	if (!v->str) +		return; + +	d = strtonum(v->str, LLONG_MIN, LLONG_MAX, &errstr); +	if (errstr) +		enprintf(2, "error: expected integer, got %s\n", v->str); +	v->num = d; +} + +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; +	const char *err1, *err2; +	long long d1, d2; + +	d1 = strtonum(a->str, LLONG_MIN, LLONG_MAX, &err1); +	d2 = strtonum(b->str, LLONG_MIN, LLONG_MAX, &err2); + +	if (!err1 && !err2) { +		ret = (d1 > d2) - (d1 < d2); +	} 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]; +	size_t anchlen; +	char *s, *p, *anchreg; +	char *str = vstr->str, *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'; +		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 '+': tonum(a); tonum(b);           ret.num = a->num + b->num; break; +	case '-': tonum(a); tonum(b);           ret.num = a->num - b->num; break; +	case '*': tonum(a); tonum(b);           ret.num = a->num * b->num; break; +	case '/': tonum(a); tonum(b); ezero(b); ret.num = a->num / b->num; break; +	case '%': tonum(a); tonum(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) +{ +	int type = VAL; +	char *ops = "|&=><+-*/%():"; + +	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; +	} + +	return type; +} + +static int +parse(char *expr[], int numexpr) +{ +	struct val *valhead, *valp, v = { .str = NULL, .num = 0 }; +	int *ophead, *opp, 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, +	}; + +	valp = valhead = enreallocarray(3, NULL, numexpr, sizeof(*valp)); +	opp = ophead = enreallocarray(3, NULL, numexpr, sizeof(*opp)); +	for (; *expr; expr++) { +		switch ((type = lex(*expr, &v))) { +		case VAL: +			/* treatment of *expr is not known until +			 * doop(); treat as a string for now */ +			valp->str = *expr; +			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, argv0 ? (argc--, argv++) : (void *)0; + +	ret = !parse(argv, argc); + +	if (fshut(stdout, "<stdout>")) +		ret = 3; + +	return ret; +} diff --git a/util/sbase/false.1 b/util/sbase/false.1 new file mode 100644 index 00000000..e6adf570 --- /dev/null +++ b/util/sbase/false.1 @@ -0,0 +1,13 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/false.c b/util/sbase/false.c new file mode 100644 index 00000000..fce3fd98 --- /dev/null +++ b/util/sbase/false.c @@ -0,0 +1,6 @@ +/* See LICENSE file for copyright and license details. */ +int +main(void) +{ +	return 1; +} diff --git a/util/sbase/find.1 b/util/sbase/find.1 new file mode 100644 index 00000000..00f26306 --- /dev/null +++ b/util/sbase/find.1 @@ -0,0 +1,151 @@ +.Dd July 30, 2025 +.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 +.Xr getpwuid 3 +returns NULL. +.It Fl nogroup +True if the file belongs to a group for which +.Xr getgrgid 3 +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 current pathname followed by a newline ('\en') character. +.It Fl print0 +True. +Print the current pathname followed by a NUL ('\e0') character. +.It Fl newer Ar file +True if the modification time of the current file is newer than that of +the provided file. +.It Fl depth +True. +Causes find to evaluate files within in a directory before the directory +itself. +.El +.Ss Operators +In order of decreasing precedence +.Bl -tag -width Ds +.It Ar \&( expression \&) +True if expression is true. +.It Ar \&! expression +True if expression if false. +.It Ar expression [ Fl a ] Ar expression +True if both expressions are true. +Second expression is not evaluated if first expression is false. +.Fl a +is implied if there is no operator between primaries. +.It Ar expression Fl o Ar expression +True if either expression is true. +Second expression is not evaluated if first expression is true. +.El +.Pp +If no expression is supplied, -print is used. +If an expression is supplied but none of -print, -exec, or -ok is +supplied, then -a -print is appended to the expressions. diff --git a/util/sbase/find.c b/util/sbase/find.c new file mode 100644 index 00000000..5bc1a1f1 --- /dev/null +++ b/util/sbase/find.c @@ -0,0 +1,1103 @@ +/* See LICENSE file for copyright and license details. */ +#include <dirent.h> +#include <errno.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; +}; + +/* Utility */ +static int spawn(char *argv[]); +static int do_stat(char *path, struct stat *sb, struct findhist *hist); + +/* 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_print0 (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 }, +	{ "-print0" , pri_print0 , 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; + +/* + * Utility + */ +static int +spawn(char *argv[]) +{ +	pid_t pid; +	int status; + +	/* flush stdout so that -print output always appears before +	 * any output from the command and does not get cut-off in +	 * the middle of a line. */ +	fflush(stdout); + +	switch((pid = fork())) { +	case -1: +		eprintf("fork:"); +	case 0: +		execvp(*argv, argv); +		weprintf("exec %s failed:", *argv); +		_exit(1); +	} + +	/* FIXME: proper course of action for waitpid() on EINTR? */ +	waitpid(pid, &status, 0); +	return status; +} + +static int +do_stat(char *path, struct stat *sb, struct findhist *hist) +{ +	if (gflags.l || (gflags.h && !hist)) { +		if (stat(path, sb) == 0) { +			return 0; +		} else if (errno != ENOENT && errno != ENOTDIR) { +			return -1; +		} +	} + +	return lstat(path, sb); +} + +/* + * Primaries + */ +static int +pri_name(struct arg *arg) +{ +	int ret; +	char *path; + +	path = estrdup(arg->path); +	ret = !fnmatch((char *)arg->extra.p, basename(path), 0); +	free(path); + +	return ret; +} + +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; +	return n->cmp((start.tv_sec - arg->st->st_atime) / 86400, n->n); +} + +static int +pri_ctime(struct arg *arg) +{ +	struct narg *n = arg->extra.p; +	return n->cmp((start.tv_sec - arg->st->st_ctime) / 86400, n->n); +} + +static int +pri_mtime(struct arg *arg) +{ +	struct narg *n = arg->extra.p; +	return n->cmp((start.tv_sec - arg->st->st_mtime) / 86400, n->n); +} + +static int +pri_exec(struct arg *arg) +{ +	int status; +	size_t len; +	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; + +			status = spawn(e->argv); +			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; + +		status = spawn(e->argv); +		return !status; +	} +} + +static int +pri_ok(struct arg *arg) +{ +	int status, reply; +	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; + +	status = spawn(o->argv); +	return !!status; +} + +static int +pri_print(struct arg *arg) +{ +	if (puts(arg->path) == EOF) +		eprintf("puts failed:"); +	return 1; +} + +static int +pri_print0(struct arg *arg) +{ +	if (fwrite(arg->path, strlen(arg->path) + 1, 1, stdout) != 1) +		eprintf("fwrite 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) +{ +	mode_t mask; +	struct permarg *p = extra->p = emalloc(sizeof(*p)); + +	if (**argv == '-') +		(*argv)++; +	else +		p->exact = 1; + +	mask = umask(0); +	umask(mask); + +	p->mode = parsemode(*argv, 0, mask); + +	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 (do_stat(*argv, &st, NULL)) +		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; +	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) { +			status = spawn(e->argv); +			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 *tok, *rpn, *out, **top, *infix, **stack; +	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 */ +	infix = ereallocarray(NULL, 2 * argc + 1, sizeof(*infix)); +	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 || op->type == LPAR)) { /* need another implicit -a */ +				*tok++ = and; +				ntok++; +			} +			tok->type = op->type; +			tok->u.oinfo = op; +		} else { +			/* token is neither primary nor operator, must be */ +			if ((*arg)[0] == '-') /* an unsupported option */ +				eprintf("unknown operand: %s\n", *arg); +			else /* or a 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)); +	stack = ereallocarray(NULL, argc + gflags.print, sizeof(*stack)); +	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; + +	free(infix); +	free(stack); +} + +/* 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 namelen, pathcap = 0, len; +	struct arg arg = { path, &st, { NULL } }; +	char *p, *pathbuf = NULL; + +	len = strlen(path) + 2; /* \0 and '/' */ + +	if (do_stat(path, &st, hist) < 0) { +		weprintf("failed to stat %s:", path); +		gflags.ret = 1; +		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); +			gflags.ret = 1; +			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); +		gflags.ret = 1; +		/* should we just ignore this since we hit an error? */ +		if (gflags.depth) +			eval(root, &arg); +		return; +	} + +	while (errno = 0, (de = readdir(dir))) { +		if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) +			continue; +		namelen = strlen(de->d_name); +		if (len + namelen > pathcap) { +			pathcap = len + namelen; +			pathbuf = erealloc(pathbuf, pathcap); +		} +		p = pathbuf + estrlcpy(pathbuf, path, pathcap); +		if (*--p != '/') +			estrlcat(pathbuf, "/", pathcap); +		estrlcat(pathbuf, de->d_name, pathcap); +		find(pathbuf, &cur); +	} +	free(pathbuf); +	if (errno) { +		weprintf("readdir %s:", path); +		gflags.ret = 1; +		closedir(dir); +		return; +	} +	closedir(dir); + +	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.h = 1; +		gflags.l = 0; +		break; +	case 'L': +		gflags.l = 1; +		gflags.h = 0; +		break; +	default: +		usage(); +	} ARGEND + +	paths = argv; + +	for (; *argv && **argv != '-' && strcmp(*argv, "!") && strcmp(*argv, "("); argv++) +		; + +	if (!(npaths = argv - paths)) +		eprintf("must specify a path\n"); + +	parse(argc - npaths, argv); + +	/* calculate number of bytes in environ for -exec {} + ARG_MAX avoidance +	 * libc implementation defined whether null bytes, pointers, and alignment +	 * are counted, so count them */ +	for (argv = environ; *argv; argv++) +		envlen += strlen(*argv) + 1 + sizeof(*argv); + +	if ((argmax = sysconf(_SC_ARG_MAX)) == (size_t)-1) +		argmax = _POSIX_ARG_MAX; + +	if (clock_gettime(CLOCK_REALTIME, &start) < 0) +		weprintf("clock_gettime() failed:"); + +	while (npaths--) +		find(*paths++, NULL); + +	for (t = toks; t->type != END; t++) +		if (t->type == PRIM && t->u.pinfo->freearg) +			t->u.pinfo->freearg(t->extra); +	free(toks); + +	gflags.ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return gflags.ret; +} diff --git a/util/sbase/flock.1 b/util/sbase/flock.1 new file mode 100644 index 00000000..9dfaf394 --- /dev/null +++ b/util/sbase/flock.1 @@ -0,0 +1,35 @@ +.Dd October 8, 2015 +.Dt FLOCK 1 +.Os sbase +.Sh NAME +.Nm flock +.Nd tool to manage locks on files +.Sh SYNOPSIS +.Nm +.Op Fl nosux +.Ar file +.Ar cmd Op arg ... +.Sh DESCRIPTION +.Nm +is used to manage advisory locks on open files. +It is commonly used to prevent long running cron jobs from running in +parallel. +If +.Ar file +does not exist, it will be created. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl n +Set non-blocking mode on the lock. +Fail immediately if the lock cannot be acquired. +.It Fl o +Close the file descriptor before exec to avoid having the exec'ed +program holding on to the lock. +.It Fl s +Acquire a shared lock. +.It Fl u +Release the lock. +.It Fl x +Acquire an exclusive lock. +This is the default. +.El diff --git a/util/sbase/flock.c b/util/sbase/flock.c new file mode 100644 index 00000000..fc2b6ed6 --- /dev/null +++ b/util/sbase/flock.c @@ -0,0 +1,82 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/file.h> +#include <sys/wait.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s [-nosux] file cmd [arg ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	int fd, status, savederrno, flags = LOCK_EX, nonblk = 0, oflag = 0; +	pid_t pid; + +	ARGBEGIN { +	case 'n': +		nonblk = LOCK_NB; +		break; +	case 'o': +		oflag = 1; +		break; +	case 's': +		flags = LOCK_SH; +		break; +	case 'u': +		flags = LOCK_UN; +		break; +	case 'x': +		flags = LOCK_EX; +		break; +	default: +		usage(); +	} ARGEND + +	if (argc < 2) +		usage(); + +	if ((fd = open(*argv, O_RDONLY | O_CREAT, 0644)) < 0) +		eprintf("open %s:", *argv); + +	if (flock(fd, flags | nonblk)) { +		if (nonblk && errno == EWOULDBLOCK) +			return 1; +		eprintf("flock:"); +	} + +	switch ((pid = fork())) { +	case -1: +		eprintf("fork:"); +	case 0: +		if (oflag && close(fd) < 0) +			eprintf("close:"); +		argv++; +		execvp(*argv, argv); +		savederrno = errno; +		weprintf("execvp %s:", *argv); +		_exit(126 + (savederrno == ENOENT)); +	default: +		break; +	} +	if (waitpid(pid, &status, 0) < 0) +		eprintf("waitpid:"); + +	if (close(fd) < 0) +		eprintf("close:"); + +	if (WIFSIGNALED(status)) +		return 128 + WTERMSIG(status); +	if (WIFEXITED(status)) +		return WEXITSTATUS(status); + +	return 0; +} diff --git a/util/sbase/fold.1 b/util/sbase/fold.1 new file mode 100644 index 00000000..3c2a3e80 --- /dev/null +++ b/util/sbase/fold.1 @@ -0,0 +1,39 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +.Pp +The +.Op Fl Ns Ar num +syntax is an extension to that specification. diff --git a/util/sbase/fold.c b/util/sbase/fold.c new file mode 100644 index 00000000..6c7b9e7b --- /dev/null +++ b/util/sbase/fold.c @@ -0,0 +1,130 @@ +/* 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" +#include "utf.h" + +static int    bflag = 0; +static int    sflag = 0; +static size_t width = 80; + +static void +foldline(struct line *l, const char *fname) { +	size_t i, col, last, spacesect, len; +	Rune r; +	int runelen; + +	for (i = 0, last = 0, col = 0, spacesect = 0; i < l->len; i += runelen) { +		if (col >= width && ((l->data[i] != '\r' && l->data[i] != '\b') || bflag)) { +			if (bflag && col > width) +				i -= runelen;	/* never split a character */ +			len = ((sflag && spacesect) ? spacesect : i) - last; +			if (fwrite(l->data + last, 1, len, stdout) != len) +				eprintf("fwrite <stdout>:"); +			if (l->data[i] != '\n') +				putchar('\n'); +			if (sflag && spacesect) +				i = spacesect; +			last = i; +			col = 0; +			spacesect = 0; +		} +		runelen = charntorune(&r, l->data + i, l->len - i); +		if (!runelen || r == Runeerror) +			eprintf("charntorune: %s: invalid utf\n", fname); +		if (sflag && isblankrune(r)) +			spacesect = i + runelen; +		if (!bflag && iscntrl(l->data[i])) { +			switch(l->data[i]) { +			case '\b': +				col -= (col > 0); +				break; +			case '\r': +				col = 0; +				break; +			case '\t': +				col += (8 - (col % 8)); +				if (col >= width) +					i--; +				break; +			} +		} else { +			col += bflag ? runelen : 1; +		} +	} +	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, fname); +	} +	if (ferror(fp)) +		eprintf("getline %s:", fname); +} + +static void +usage(void) +{ +	eprintf("usage: %s [-bs] [-w num | -num] [FILE ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	FILE *fp; +	int ret = 0; + +	ARGBEGIN { +	case 'b': +		bflag = 1; +		break; +	case 's': +		sflag = 1; +		break; +	case 'w': +		width = estrtonum(EARGF(usage()), 1, MIN(LLONG_MAX, SIZE_MAX)); +		break; +	ARGNUM: +		if (!(width = ARGNUMF())) +			eprintf("illegal width value, too small\n"); +		break; +	default: +		usage(); +	} ARGEND + +	if (!argc) { +		fold(stdin, "<stdin>"); +	} else { +		for (; *argv; argc--, argv++) { +			if (!strcmp(*argv, "-")) { +				*argv = "<stdin>"; +				fp = stdin; +			} else if (!(fp = fopen(*argv, "r"))) { +				weprintf("fopen %s:", *argv); +				ret = 1; +				continue; +			} +			fold(fp, *argv); +			if (fp != stdin && fshut(fp, *argv)) +				ret = 1; +		} +	} + +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/fs.h b/util/sbase/fs.h new file mode 100644 index 00000000..fd647bb7 --- /dev/null +++ b/util/sbase/fs.h @@ -0,0 +1,47 @@ +/* See LICENSE file for copyright and license details. */ +#include <limits.h> +#include <sys/stat.h> +#include <sys/types.h> + +struct history { +	struct history *prev; +	dev_t dev; +	ino_t ino; +}; + +struct recursor { +	void (*fn)(int, const char *, struct stat *, void *, struct recursor *); +	char path[PATH_MAX]; +	size_t pathlen; +	struct history *hist; +	int depth; +	int maxdepth; +	int follow; +	int flags; +}; + +enum { +	SAMEDEV  = 1 << 0, +	DIRFIRST = 1 << 1, +	SILENT   = 1 << 2, +	CONFIRM  = 1 << 3, +	IGNORE   = 1 << 4, +}; + +extern int cp_aflag; +extern int cp_fflag; +extern int cp_iflag; +extern int cp_pflag; +extern int cp_rflag; +extern int cp_vflag; +extern int cp_follow; +extern int cp_status; + +extern int rm_status; + +extern int recurse_status; + +void recurse(int, const char *, void *, struct recursor *); + +int cp(const char *, const char *, int); +void rm(int, const char *, struct stat *st, void *, struct recursor *); diff --git a/util/sbase/getconf.1 b/util/sbase/getconf.1 new file mode 100644 index 00000000..e75abbb0 --- /dev/null +++ b/util/sbase/getconf.1 @@ -0,0 +1,57 @@ +.Dd October 8, 2015 +.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, +.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 invalid. +.El +.Sh STANDARDS +POSIX.1-2013. +Except for the +.Op Fl v +flag. diff --git a/util/sbase/getconf.c b/util/sbase/getconf.c new file mode 100644 index 00000000..ca3e186f --- /dev/null +++ b/util/sbase/getconf.c @@ -0,0 +1,108 @@ +/* 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; +}; + +#include "getconf.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; +	default: +		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 fshut(stdout, "<stdout>"); +		} +		/* 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 fshut(stdout, "<stdout>"); +		} +		/* 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 fshut(stdout, "<stdout>"); +		} +	} 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 fshut(stdout, "<stdout>"); +		} +	} else { +		usage(); +	} + +	eprintf("invalid variable: %s\n", argv[0]); + +	return 0; +} diff --git a/util/sbase/grep.1 b/util/sbase/grep.1 new file mode 100644 index 00000000..9de0294a --- /dev/null +++ b/util/sbase/grep.1 @@ -0,0 +1,94 @@ +.Dd October 8, 2015 +.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 or +.Xr re_format 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 re_format 7 , +.Xr regex 7 +.Sh STANDARDS +POSIX.1-2013. +.Pp +The +.Op Fl Hhw +flags are an extension to that specification. diff --git a/util/sbase/grep.c b/util/sbase/grep.c new file mode 100644 index 00000000..1c978070 --- /dev/null +++ b/util/sbase/grep.c @@ -0,0 +1,290 @@ +/* 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, result = 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'; +		match = 0; +		SLIST_FOREACH(pnode, &phead, entry) { +			if (Fflag) { +				if (xflag) { +					if (!(iflag ? strcasecmp : strcmp)(buf, pnode->pattern)) { +						match = 1; +						break; +					} +				} else { +					if ((iflag ? strcasestr : strstr)(buf, pnode->pattern)) { +						match = 1; +						break; +					} +				} +			} else { +				if (regexec(&pnode->preg, buf, 0, NULL, 0) == 0) { +					match = 1; +					break; +				} +			} +		} +		if (match != vflag) { +			result = Match; +			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; +			} +		} +	} +	if (mode == 'c') +		printf("%ld\n", c); +end: +	if (ferror(fp)) { +		weprintf("%s: read error:", str); +		result = Error; +	} +	return result; +} + +static void +usage(void) +{ +	enprintf(Error, "usage: %s [-EFHchilnqsvwx] [-e pattern] [-f file] " +	         "[pattern] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	struct pattern *pnode; +	int m, flags = REG_NOSUB, match = NoMatch; +	FILE *fp; +	char *arg; + +	SLIST_INIT(&phead); + +	ARGBEGIN { +	case 'E': +		Eflag = 1; +		Fflag = 0; +		flags |= REG_EXTENDED; +		break; +	case 'F': +		Fflag = 1; +		Eflag = 0; +		flags &= ~REG_EXTENDED; +		break; +	case 'H': +		Hflag = 1; +		hflag = 0; +		break; +	case 'e': +		arg = EARGF(usage()); +		if (!(fp = fmemopen(arg, strlen(arg) + 1, "r"))) +			eprintf("fmemopen:"); +		addpatternfile(fp); +		efshut(fp, arg); +		eflag = 1; +		break; +	case 'f': +		arg = EARGF(usage()); +		fp = fopen(arg, "r"); +		if (!fp) +			enprintf(Error, "fopen %s:", arg); +		addpatternfile(fp); +		efshut(fp, arg); +		fflag = 1; +		break; +	case 'h': +		hflag = 1; +		Hflag = 0; +		break; +	case 'c': +	case 'l': +	case 'n': +	case 'q': +		mode = ARGC(); +		break; +	case 'i': +		flags |= REG_ICASE; +		iflag = 1; +		break; +	case 's': +		sflag = 1; +		break; +	case 'v': +		vflag = 1; +		break; +	case 'w': +		wflag = 1; +		break; +	case 'x': +		xflag = 1; +		break; +	default: +		usage(); +	} ARGEND + +	if (argc == 0 && !eflag && !fflag) +		usage(); /* no pattern */ + +	/* just add literal pattern to list */ +	if (!eflag && !fflag) { +		if (!(fp = fmemopen(argv[0], strlen(argv[0]) + 1, "r"))) +			eprintf("fmemopen:"); +		addpatternfile(fp); +		efshut(fp, argv[0]); +		argc--; +		argv++; +	} + +	if (!Fflag) +		/* Compile regex for all search patterns */ +		SLIST_FOREACH(pnode, &phead, entry) +			enregcomp(Error, &pnode->preg, pnode->pattern, flags); +	many = (argc > 1); +	if (argc == 0) { +		match = grep(stdin, "<stdin>"); +	} else { +		for (; *argv; argc--, argv++) { +			if (!strcmp(*argv, "-")) { +				*argv = "<stdin>"; +				fp = stdin; +			} else if (!(fp = fopen(*argv, "r"))) { +				if (!sflag) +					weprintf("fopen %s:", *argv); +				match = Error; +				continue; +			} +			m = grep(fp, *argv); +			if (m == Error || (match != Error && m == Match)) +				match = m; +			if (fp != stdin && fshut(fp, *argv)) +				match = Error; +		} +	} + +	if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>")) +		match = Error; + +	return match; +} diff --git a/util/sbase/head.1 b/util/sbase/head.1 new file mode 100644 index 00000000..0bf3127f --- /dev/null +++ b/util/sbase/head.1 @@ -0,0 +1,40 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +.Pp +The +.Op Fl Ns num +syntax is an extension to that specification. diff --git a/util/sbase/head.c b/util/sbase/head.c new file mode 100644 index 00000000..ae550c01 --- /dev/null +++ b/util/sbase/head.c @@ -0,0 +1,77 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +static void +head(FILE *fp, const char *fname, size_t n) +{ +	char *buf = NULL; +	size_t i = 0, size = 0; +	ssize_t len; + +	while (i < n && (len = getline(&buf, &size, fp)) > 0) { +		fwrite(buf, 1, len, stdout); +		i += (len && (buf[len - 1] == '\n')); +	} +	free(buf); +	if (ferror(fp)) +		eprintf("getline %s:", fname); +} + +static void +usage(void) +{ +	eprintf("usage: %s [-num | -n num] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	size_t n = 10; +	FILE *fp; +	int ret = 0, newline = 0, many = 0; + +	ARGBEGIN { +	case 'n': +		n = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, SIZE_MAX)); +		break; +	ARGNUM: +		n = ARGNUMF(); +		break; +	default: +		usage(); +	} ARGEND + +	if (!argc) { +		head(stdin, "<stdin>", n); +	} else { +		many = argc > 1; +		for (newline = 0; *argv; argc--, argv++) { +			if (!strcmp(*argv, "-")) { +				*argv = "<stdin>"; +				fp = stdin; +			} else if (!(fp = fopen(*argv, "r"))) { +				weprintf("fopen %s:", *argv); +				ret = 1; +				continue; +			} +			if (many) { +				if (newline) +					putchar('\n'); +				printf("==> %s <==\n", *argv); +			} +			newline = 1; +			head(fp, *argv, n); +			if (fp != stdin && fshut(fp, *argv)) +				ret = 1; +		} +	} + +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/hostname.1 b/util/sbase/hostname.1 new file mode 100644 index 00000000..601aef9d --- /dev/null +++ b/util/sbase/hostname.1 @@ -0,0 +1,18 @@ +.Dd October 8, 2015 +.Dt HOSTNAME 1 +.Os sbase +.Sh NAME +.Nm hostname +.Nd set or print host name +.Sh SYNOPSIS +.Nm +.Op Ar name +.Sh DESCRIPTION +.Nm +sets the current host name to +.Ar name . +If no +.Ar name +is given, the current host name is written to stdout. +.Sh SEE ALSO +.Xr hostname 7 diff --git a/util/sbase/hostname.c b/util/sbase/hostname.c new file mode 100644 index 00000000..2532ec8d --- /dev/null +++ b/util/sbase/hostname.c @@ -0,0 +1,36 @@ +/* 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]; + +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	if (!argc) { +		if (gethostname(host, sizeof(host)) < 0) +			eprintf("gethostname:"); +		puts(host); +	} else if (argc == 1) { +		if (sethostname(argv[0], strlen(argv[0])) < 0) +			eprintf("sethostname:"); +	} else { +		usage(); +	} + +	return fshut(stdout, "<stdout>"); +} diff --git a/util/sbase/join.1 b/util/sbase/join.1 new file mode 100644 index 00000000..6d1f4be1 --- /dev/null +++ b/util/sbase/join.1 @@ -0,0 +1,105 @@ +.Dd October 8, 2015 +.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 , +then the remaining fields from +.Ar file2 . +.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 , +replace empty fields in the output list with +.Ar string . +.It Fl o Ar list +Format output according to the string +.Ar list . +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 , +then the third field of +.Ar file1 . +.Pp +Only paired lines are formatted with the +.Fl o +option. +Unpairable lines (selected with +.Fl a +or +.Fl v ) +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 +POSIX.1-2013. +.Pp +With the following exception: +.Bl -bullet -offset indent +.It +Unpairable lines ignore formatting specified with +.Fl o . +.El +.Pp +The possibility of specifying multibyte delimiters of arbitrary +length is an extension to the specification. diff --git a/util/sbase/join.c b/util/sbase/join.c new file mode 100644 index 00000000..d3e23439 --- /dev/null +++ b/util/sbase/join.c @@ -0,0 +1,529 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "text.h" +#include "utf.h" +#include "util.h" + +enum { +	INIT = 1, +	GROW = 2, +}; + +enum { +	EXPAND = 0, +	RESET  = 1, +}; + +enum { FIELD_ERROR = -2, }; + +struct field { +	char *s; +	size_t len; +}; + +struct jline { +	struct line text; +	size_t nf; +	size_t maxf; +	struct field *fields; +}; + +struct spec { +	size_t fileno; +	size_t fldno; +}; + +struct outlist { +	size_t ns; +	size_t maxs; +	struct spec **specs; +}; + +struct span { +	size_t nl; +	size_t maxl; +	struct jline **lines; +}; + +static char *sep = NULL; +static char *replace = NULL; +static const char defaultofs = ' '; +static const int jfield = 1;            /* POSIX default join field */ +static int unpairsa = 0, unpairsb = 0; +static int oflag = 0; +static int pairs = 1; +static size_t seplen; +static struct outlist output; + +static void +usage(void) +{ +	eprintf("usage: %s [-1 field] [-2 field] [-o list] [-e string] " +	        "[-a | -v fileno] [-t delim] file1 file2\n", argv0); +} + +static void +prfield(struct field *fp) +{ +	if (fwrite(fp->s, 1, fp->len, stdout) != fp->len) +		eprintf("fwrite:"); +} + +static void +prsep(void) +{ +	if (sep) +		fwrite(sep, 1, seplen, stdout); +	else +		putchar(defaultofs); +} + +static void +swaplines(struct jline *la, struct jline *lb) +{ +	struct jline tmp; + +	tmp = *la; +	*la = *lb; +	*lb = tmp; +} + +static void +prjoin(struct jline *la, struct jline *lb, size_t jfa, size_t jfb) +{ +	struct spec *sp; +	struct field *joinfield; +	size_t i; + +	if (jfa >= la->nf || jfb >= lb->nf) +		return; + +	joinfield = &la->fields[jfa]; + +	if (oflag) { +		for (i = 0; i < output.ns; i++) { +			sp = output.specs[i]; + +			if (sp->fileno == 1) { +				if (sp->fldno < la->nf) +					prfield(&la->fields[sp->fldno]); +				else if (replace) +					fputs(replace, stdout); +			} else if (sp->fileno == 2) { +				if (sp->fldno < lb->nf) +					prfield(&lb->fields[sp->fldno]); +				else if (replace) +					fputs(replace, stdout); +			} else if (sp->fileno == 0) { +				prfield(joinfield); +			} + +			if (i < output.ns - 1) +				prsep(); +		} +	} else { +		prfield(joinfield); +		prsep(); + +		for (i = 0; i < la->nf; i++) { +			if (i != jfa) { +				prfield(&la->fields[i]); +				prsep(); +			} +		} +		for (i = 0; i < lb->nf; i++) { +			if (i != jfb) { +				prfield(&lb->fields[i]); +				if (i < lb->nf - 1) +					prsep(); +			} +		} +	} +	putchar('\n'); +} + +static void +prline(struct jline *lp) +{ +	if (fwrite(lp->text.data, 1, lp->text.len, stdout) != lp->text.len) +		eprintf("fwrite:"); +	putchar('\n'); +} + +static int +jlinecmp(struct jline *la, struct jline *lb, size_t jfa, size_t jfb) +{ +	int status; + +	/* return FIELD_ERROR if both lines are short */ +	if (jfa >= la->nf) { +		status = (jfb >= lb->nf) ? FIELD_ERROR : -1; +	} else if (jfb >= lb->nf) { +		status = 1; +	} else { +		status = memcmp(la->fields[jfa].s, lb->fields[jfb].s, +		                MAX(la->fields[jfa].len, lb->fields[jfb].len)); +		LIMIT(status, -1, 1); +	} + +	return status; +} + +static void +addfield(struct jline *lp, char *sp, size_t len) +{ +	if (lp->nf >= lp->maxf) { +		lp->fields = ereallocarray(lp->fields, (GROW * lp->maxf), +		        sizeof(struct field)); +		lp->maxf *= GROW; +	} +	lp->fields[lp->nf].s = sp; +	lp->fields[lp->nf].len = len; +	lp->nf++; +} + +static void +prspanjoin(struct span *spa, struct span *spb, size_t jfa, size_t jfb) +{ +	size_t i, j; + +	for (i = 0; i < (spa->nl - 1); i++) +		for (j = 0; j < (spb->nl - 1); j++) +			prjoin(spa->lines[i], spb->lines[j], jfa, jfb); +} + +static struct jline * +makeline(char *s, size_t len) +{ +	struct jline *lp; +	char *tmp; +	size_t i, end; + +	if (s[len - 1] == '\n') +		s[--len] = '\0'; + +	lp = ereallocarray(NULL, INIT, sizeof(struct jline)); +	lp->text.data = s; +	lp->text.len = len; +	lp->fields = ereallocarray(NULL, INIT, sizeof(struct field)); +	lp->nf = 0; +	lp->maxf = INIT; + +	for (i = 0; i < lp->text.len && isblank(lp->text.data[i]); i++) +		; +	while (i < lp->text.len) { +		if (sep) { +			if ((lp->text.len - i) < seplen || +			    !(tmp = memmem(lp->text.data + i, +			                   lp->text.len - i, sep, seplen))) { +				goto eol; +			} +			end = tmp - lp->text.data; +			addfield(lp, lp->text.data + i, end - i); +			i = end + seplen; +		} else { +			for (end = i; !(isblank(lp->text.data[end])); end++) { +				if (end + 1 == lp->text.len) +					goto eol; +			} +			addfield(lp, lp->text.data + i, end - i); +			for (i = end; isblank(lp->text.data[i]); i++) +				; +		} +	} +eol: +	addfield(lp, lp->text.data + i, lp->text.len - i); + +	return lp; +} + +static int +addtospan(struct span *sp, FILE *fp, int reset) +{ +	char *newl = NULL; +	ssize_t len; +	size_t size = 0; + +	if ((len = getline(&newl, &size, fp)) < 0) { +		if (ferror(fp)) +			eprintf("getline:"); +		else +			return 0; +	} + +	if (reset) +		sp->nl = 0; + +	if (sp->nl >= sp->maxl) { +		sp->lines = ereallocarray(sp->lines, (GROW * sp->maxl), +		        sizeof(struct jline *)); +		sp->maxl *= GROW; +	} + +	sp->lines[sp->nl] = makeline(newl, len); +	sp->nl++; +	return 1; +} + +static void +initspan(struct span *sp) +{ +	sp->nl = 0; +	sp->maxl = INIT; +	sp->lines = ereallocarray(NULL, INIT, sizeof(struct jline *)); +} + +static void +freespan(struct span *sp) +{ +	size_t i; + +	for (i = 0; i < sp->nl; i++) { +		free(sp->lines[i]->fields); +		free(sp->lines[i]->text.data); +	} +	free(sp->lines); +} + +static void +initolist(struct outlist *olp) +{ +	olp->ns = 0; +	olp->maxs = 1; +	olp->specs = ereallocarray(NULL, INIT, sizeof(struct spec *)); +} + +static void +addspec(struct outlist *olp, struct spec *sp) +{ +	if (olp->ns >= olp->maxs) { +		olp->specs = ereallocarray(olp->specs, (GROW * olp->maxs), +		        sizeof(struct spec *)); +		olp->maxs *= GROW; +	} +	olp->specs[olp->ns] = sp; +	olp->ns++; +} + +static struct spec * +makespec(char *s) +{ +	struct spec *sp; +	int fileno; +	size_t fldno; + +	if (!strcmp(s, "0")) {   /* join field must be 0 and nothing else */ +		fileno = 0; +		fldno = 0; +	} else if ((s[0] == '1' || s[0] == '2') && s[1] == '.') { +		fileno = s[0] - '0'; +		fldno = estrtonum(&s[2], 1, MIN(LLONG_MAX, SIZE_MAX)) - 1; +	} else { +		eprintf("%s: invalid format\n", s); +	} + +	sp = ereallocarray(NULL, INIT, sizeof(struct spec)); +	sp->fileno = fileno; +	sp->fldno = fldno; +	return sp; +} + +static void +makeolist(struct outlist *olp, char *s) +{ +	char *item, *sp; +	sp = s; + +	while (sp) { +		item = sp; +		sp = strpbrk(sp, ", \t"); +		if (sp) +			*sp++ = '\0'; +		addspec(olp, makespec(item)); +	} +} + +static void +freespecs(struct outlist *olp) +{ +	size_t i; + +	for (i = 0; i < olp->ns; i++) +		free(olp->specs[i]); +} + +static void +join(FILE *fa, FILE *fb, size_t jfa, size_t jfb) +{ +	struct span spa, spb; +	int cmp, eofa, eofb; + +	initspan(&spa); +	initspan(&spb); +	cmp = eofa = eofb = 0; + +	addtospan(&spa, fa, RESET); +	addtospan(&spb, fb, RESET); + +	while (spa.nl && spb.nl) { +		if ((cmp = jlinecmp(spa.lines[0], spb.lines[0], jfa, jfb)) < 0) { +			if (unpairsa) +				prline(spa.lines[0]); +			if (!addtospan(&spa, fa, RESET)) { +				if (unpairsb) {    /* a is EOF'd; print the rest of b */ +					do +						prline(spb.lines[0]); +					while (addtospan(&spb, fb, RESET)); +				} +				eofa = eofb = 1; +			} else { +				continue; +			} +		} else if (cmp > 0) { +			if (unpairsb) +				prline(spb.lines[0]); +			if (!addtospan(&spb, fb, RESET)) { +				if (unpairsa) {    /* b is EOF'd; print the rest of a */ +					do +						prline(spa.lines[0]); +					while (addtospan(&spa, fa, RESET)); +				} +				eofa = eofb = 1; +			} else { +				continue; +			} +		} else if (cmp == 0) { +			/* read all consecutive matching lines from a */ +			do { +				if (!addtospan(&spa, fa, EXPAND)) { +					eofa = 1; +					spa.nl++; +					break; +				} +			} while (jlinecmp(spa.lines[spa.nl-1], spb.lines[0], jfa, jfb) == 0); + +			/* read all consecutive matching lines from b */ +			do { +				if (!addtospan(&spb, fb, EXPAND)) { +					eofb = 1; +					spb.nl++; +					break; +				} +			} while (jlinecmp(spa.lines[0], spb.lines[spb.nl-1], jfa, jfb) == 0); + +			if (pairs) +				prspanjoin(&spa, &spb, jfa, jfb); + +		} else {      /* FIELD_ERROR: both lines lacked join fields */ +			if (unpairsa) +				prline(spa.lines[0]); +			if (unpairsb) +				prline(spb.lines[0]); +			eofa = addtospan(&spa, fa, RESET) ? 0 : 1; +			eofb = addtospan(&spb, fb, RESET) ? 0 : 1; +			if (!eofa && !eofb) +				continue; +		} + +		if (eofa) { +			spa.nl = 0; +		} else { +			swaplines(spa.lines[0], spa.lines[spa.nl - 1]);   /* ugly */ +			spa.nl = 1; +		} + +		if (eofb) { +			spb.nl = 0; +		} else { +			swaplines(spb.lines[0], spb.lines[spb.nl - 1]);   /* ugly */ +			spb.nl = 1; +		} +	} +	freespan(&spa); +	freespan(&spb); +} + + +int +main(int argc, char *argv[]) +{ +	size_t jf[2] = { jfield, jfield, }; +	FILE *fp[2]; +	int ret = 0, n; +	char *fno; + +	ARGBEGIN { +	case '1': +		jf[0] = estrtonum(EARGF(usage()), 1, MIN(LLONG_MAX, SIZE_MAX)); +		break; +	case '2': +		jf[1] = estrtonum(EARGF(usage()), 1, MIN(LLONG_MAX, SIZE_MAX)); +		break; +	case 'a': +		fno = EARGF(usage()); +		if (strcmp(fno, "1") == 0) +			unpairsa = 1; +		else if (strcmp(fno, "2") == 0) +			unpairsb = 1; +		else +			usage(); +		break; +	case 'e': +		replace = EARGF(usage()); +		break; +	case 'o': +		oflag = 1; +		initolist(&output); +		makeolist(&output, EARGF(usage())); +		break; +	case 't': +		sep = EARGF(usage()); +		break; +	case 'v': +		pairs = 0; +		fno = EARGF(usage()); +		if (strcmp(fno, "1") == 0) +			unpairsa = 1; +		else if (strcmp(fno, "2") == 0) +			unpairsb = 1; +		else +			usage(); +		break; +	default: +		usage(); +	} ARGEND + +	if (sep) +		seplen = unescape(sep); + +	if (argc != 2) +		usage(); + +	for (n = 0; n < 2; n++) { +		if (!strcmp(argv[n], "-")) { +			argv[n] = "<stdin>"; +			fp[n] = stdin; +		} else if (!(fp[n] = fopen(argv[n], "r"))) { +			eprintf("fopen %s:", argv[n]); +		} +	} + +	jf[0]--; +	jf[1]--; + +	join(fp[0], fp[1], jf[0], jf[1]); + +	if (oflag) +		freespecs(&output); + +	if (fshut(fp[0], argv[0]) | (fp[0] != fp[1] && fshut(fp[1], argv[1])) | +	    fshut(stdout, "<stdout>")) +		ret = 2; + +	return ret; +} diff --git a/util/sbase/kill.1 b/util/sbase/kill.1 new file mode 100644 index 00000000..37ec4dac --- /dev/null +++ b/util/sbase/kill.1 @@ -0,0 +1,39 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +.Pp +The +.Fl Ar signame +and +.Fl Ar num +syntax is marked by POSIX.1-2013 as an X/OPEN System Interfaces option. diff --git a/util/sbase/kill.c b/util/sbase/kill.c new file mode 100644 index 00000000..b09f55cb --- /dev/null +++ b/util/sbase/kill.c @@ -0,0 +1,131 @@ +/* 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(TRAP), 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, argv0 ? (argc--, argv++) : (void *)0; + +	if (!argc) +		usage(); + +	if ((*argv)[0] == '-') { +		switch ((*argv)[1]) { +		case 'l': +			if ((*argv)[2]) +				goto longopt; +			argc--, argv++; +			if (!argc) { +				for (i = 0; i < LEN(sigs); i++) +					puts(sigs[i].name); +			} else if (argc == 1) { +				sig = estrtonum(*argv, 0, INT_MAX); +				if (sig > 128) +					sig = WTERMSIG(sig); +				puts(sig2name(sig)); +			} else { +				usage(); +			} +			return fshut(stdout, "<stdout>"); +		case 's': +			if ((*argv)[2]) +				goto longopt; +			argc--, argv++; +			if (!argc) +				usage(); +			sig = name2sig(*argv); +			argc--, argv++; +			break; +		case '-': +			if ((*argv)[2]) +				goto longopt; +			argc--, argv++; +			break; +		default: +		longopt: +			/* XSI-extensions -argnum and -argname*/ +			if (isdigit((*argv)[1])) { +				sig = estrtonum((*argv) + 1, 0, INT_MAX); +				sig2name(sig); +			} else { +				sig = name2sig((*argv) + 1); +			} +			argc--, argv++; +		} +	} + +	if (argc && !strcmp(*argv, "--")) +		argc--, argv++; + +	if (!argc) +		usage(); + +	for (; *argv; argc--, argv++) { +		pid = estrtonum(*argv, INT_MIN, INT_MAX); +		if (kill(pid, sig) < 0) { +			weprintf("kill %d:", pid); +			ret = 1; +		} +	} + +	return ret; +} diff --git a/util/sbase/libutf/Makefile b/util/sbase/libutf/Makefile new file mode 100644 index 00000000..aac2d2e6 --- /dev/null +++ b/util/sbase/libutf/Makefile @@ -0,0 +1,6 @@ +AWK = awk +UNICODE = http://unicode.org/Public/UCD/latest/ucd/UnicodeData.txt + +default: +	@echo Downloading and parsing $(UNICODE) +	@curl -\# $(UNICODE) | $(AWK) -f mkrunetype.awk diff --git a/util/sbase/libutf/fgetrune.c b/util/sbase/libutf/fgetrune.c new file mode 100644 index 00000000..8cd78c64 --- /dev/null +++ b/util/sbase/libutf/fgetrune.c @@ -0,0 +1,36 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../utf.h" + +int +fgetrune(Rune *r, FILE *fp) +{ +	char buf[UTFmax]; +	int  i = 0, c; + +	while (i < UTFmax && (c = fgetc(fp)) != EOF) { +		buf[i++] = c; +		if (charntorune(r, buf, i) > 0) +			break; +	} +	if (ferror(fp)) +		return -1; + +	return i; +} + +int +efgetrune(Rune *r, FILE *fp, const char *file) +{ +	int ret; + +	if ((ret = fgetrune(r, fp)) < 0) { +		fprintf(stderr, "fgetrune %s: %s\n", file, strerror(errno)); +		exit(1); +	} +	return ret; +} diff --git a/util/sbase/libutf/fputrune.c b/util/sbase/libutf/fputrune.c new file mode 100644 index 00000000..6a393b5a --- /dev/null +++ b/util/sbase/libutf/fputrune.c @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../utf.h" + +int +fputrune(const Rune *r, FILE *fp) +{ +	char buf[UTFmax]; + +	return fwrite(buf, runetochar(buf, r), 1, fp); +} + +int +efputrune(const Rune *r, FILE *fp, const char *file) +{ +	int ret; + +	if ((ret = fputrune(r, fp)) < 0) { +		fprintf(stderr, "fputrune %s: %s\n", file, strerror(errno)); +		exit(1); +	} +	return ret; +} diff --git a/util/sbase/libutf/isalnumrune.c b/util/sbase/libutf/isalnumrune.c new file mode 100644 index 00000000..e4720d00 --- /dev/null +++ b/util/sbase/libutf/isalnumrune.c @@ -0,0 +1,9 @@ +/* Automatically generated by mkrunetype.awk */ +#include "../utf.h" +#include "runetype.h" + +int +isalnumrune(Rune r) +{ +	return isalpharune(r) || isdigitrune(r); +} diff --git a/util/sbase/libutf/isalpharune.c b/util/sbase/libutf/isalpharune.c new file mode 100644 index 00000000..9d1faffe --- /dev/null +++ b/util/sbase/libutf/isalpharune.c @@ -0,0 +1,830 @@ +/* Automatically generated by mkrunetype.awk */ +#include <stdlib.h> + +#include "../utf.h" +#include "runetype.h" + +static const 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 }, +	{ 0x0887, 0x0889 }, +	{ 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, 0x0E86 }, +	{ 0x0E8A, 0x0E8C }, +	{ 0x0EA3, 0x0EA7 }, +	{ 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 }, +	{ 0x176C, 0x176E }, +	{ 0x18A8, 0x18AA }, +	{ 0x1CEC, 0x1CEE }, +	{ 0x1CF3, 0x1CF5 }, +	{ 0x1F57, 0x1F5F }, +	{ 0x1FB4, 0x1FB6 }, +	{ 0x1FBC, 0x1FBE }, +	{ 0x1FC4, 0x1FC6 }, +	{ 0x1FF4, 0x1FF6 }, +	{ 0x2113, 0x2115 }, +	{ 0x2124, 0x212A }, +	{ 0x212D, 0x212F }, +	{ 0x2D25, 0x2D27 }, +	{ 0x2DA6, 0x2DA8 }, +	{ 0x2DAE, 0x2DB0 }, +	{ 0x2DB6, 0x2DB8 }, +	{ 0x2DBE, 0x2DC0 }, +	{ 0x2DC6, 0x2DC8 }, +	{ 0x2DCE, 0x2DD0 }, +	{ 0x2DD6, 0x2DD8 }, +	{ 0x309F, 0x30A1 }, +	{ 0x30FA, 0x30FC }, +	{ 0x312F, 0x3131 }, +	{ 0xA7D1, 0xA7D5 }, +	{ 0xA801, 0xA803 }, +	{ 0xA805, 0xA807 }, +	{ 0xA80A, 0xA80C }, +	{ 0xA8FB, 0xA8FD }, +	{ 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 }, +	{ 0x1057A, 0x1057C }, +	{ 0x1058A, 0x1058C }, +	{ 0x10592, 0x10594 }, +	{ 0x10595, 0x10597 }, +	{ 0x105A1, 0x105A3 }, +	{ 0x105B1, 0x105B3 }, +	{ 0x105B9, 0x105BB }, +	{ 0x10785, 0x10787 }, +	{ 0x107B0, 0x107B2 }, +	{ 0x10808, 0x1080A }, +	{ 0x10835, 0x10837 }, +	{ 0x108F2, 0x108F4 }, +	{ 0x10A13, 0x10A15 }, +	{ 0x10A17, 0x10A19 }, +	{ 0x10AC7, 0x10AC9 }, +	{ 0x111DA, 0x111DC }, +	{ 0x11211, 0x11213 }, +	{ 0x11286, 0x1128A }, +	{ 0x1128D, 0x1128F }, +	{ 0x1129D, 0x1129F }, +	{ 0x11328, 0x1132A }, +	{ 0x11330, 0x11332 }, +	{ 0x11333, 0x11335 }, +	{ 0x114C5, 0x114C7 }, +	{ 0x11913, 0x11915 }, +	{ 0x11916, 0x11918 }, +	{ 0x1193F, 0x11941 }, +	{ 0x119E1, 0x119E3 }, +	{ 0x11C08, 0x11C0A }, +	{ 0x11D06, 0x11D08 }, +	{ 0x11D09, 0x11D0B }, +	{ 0x11D65, 0x11D67 }, +	{ 0x11D68, 0x11D6A }, +	{ 0x11F02, 0x11F04 }, +	{ 0x11F10, 0x11F12 }, +	{ 0x16FE1, 0x16FE3 }, +	{ 0x1AFF3, 0x1AFF5 }, +	{ 0x1AFFB, 0x1AFFD }, +	{ 0x1AFFE, 0x1B000 }, +	{ 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 }, +	{ 0x1E7E6, 0x1E7E8 }, +	{ 0x1E7EB, 0x1E7ED }, +	{ 0x1E7EE, 0x1E7F0 }, +	{ 0x1E7FE, 0x1E800 }, +	{ 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 const 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 }, +	{ 0x0560, 0x0588 }, +	{ 0x05D0, 0x05EA }, +	{ 0x05EF, 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 }, +	{ 0x0860, 0x086A }, +	{ 0x0870, 0x0887 }, +	{ 0x0889, 0x088E }, +	{ 0x08A0, 0x08C9 }, +	{ 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, 0x0C5A }, +	{ 0x0C60, 0x0C61 }, +	{ 0x0C85, 0x0C8C }, +	{ 0x0C8E, 0x0C90 }, +	{ 0x0C92, 0x0CA8 }, +	{ 0x0CAA, 0x0CB3 }, +	{ 0x0CB5, 0x0CB9 }, +	{ 0x0CDD, 0x0CDE }, +	{ 0x0CE0, 0x0CE1 }, +	{ 0x0CF1, 0x0CF2 }, +	{ 0x0D04, 0x0D0C }, +	{ 0x0D0E, 0x0D10 }, +	{ 0x0D12, 0x0D3A }, +	{ 0x0D54, 0x0D56 }, +	{ 0x0D5F, 0x0D61 }, +	{ 0x0D7A, 0x0D7F }, +	{ 0x0D85, 0x0D96 }, +	{ 0x0D9A, 0x0DB1 }, +	{ 0x0DB3, 0x0DBB }, +	{ 0x0DC0, 0x0DC6 }, +	{ 0x0E01, 0x0E30 }, +	{ 0x0E32, 0x0E33 }, +	{ 0x0E40, 0x0E46 }, +	{ 0x0E81, 0x0E82 }, +	{ 0x0E86, 0x0E8A }, +	{ 0x0E8C, 0x0EA3 }, +	{ 0x0EA7, 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, 0x13F5 }, +	{ 0x13F8, 0x13FD }, +	{ 0x1401, 0x166C }, +	{ 0x166F, 0x167F }, +	{ 0x1681, 0x169A }, +	{ 0x16A0, 0x16EA }, +	{ 0x16F1, 0x16F8 }, +	{ 0x1700, 0x1711 }, +	{ 0x171F, 0x1731 }, +	{ 0x1740, 0x1751 }, +	{ 0x1760, 0x176C }, +	{ 0x176E, 0x1770 }, +	{ 0x1780, 0x17B3 }, +	{ 0x1820, 0x1878 }, +	{ 0x1880, 0x1884 }, +	{ 0x1887, 0x18A8 }, +	{ 0x18B0, 0x18F5 }, +	{ 0x1900, 0x191E }, +	{ 0x1950, 0x196D }, +	{ 0x1970, 0x1974 }, +	{ 0x1980, 0x19AB }, +	{ 0x19B0, 0x19C9 }, +	{ 0x1A00, 0x1A16 }, +	{ 0x1A20, 0x1A54 }, +	{ 0x1B05, 0x1B33 }, +	{ 0x1B45, 0x1B4C }, +	{ 0x1B83, 0x1BA0 }, +	{ 0x1BAE, 0x1BAF }, +	{ 0x1BBA, 0x1BE5 }, +	{ 0x1C00, 0x1C23 }, +	{ 0x1C4D, 0x1C4F }, +	{ 0x1C5A, 0x1C7D }, +	{ 0x1C80, 0x1C88 }, +	{ 0x1C90, 0x1CBA }, +	{ 0x1CBD, 0x1CBF }, +	{ 0x1CE9, 0x1CEC }, +	{ 0x1CEE, 0x1CF3 }, +	{ 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, 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, 0x312F }, +	{ 0x3131, 0x318E }, +	{ 0x31A0, 0x31BF }, +	{ 0x31F0, 0x31FF }, +	{ 0x9FFF, 0xA48C }, +	{ 0xA4D0, 0xA4FD }, +	{ 0xA500, 0xA60C }, +	{ 0xA610, 0xA61F }, +	{ 0xA62A, 0xA62B }, +	{ 0xA640, 0xA66E }, +	{ 0xA67F, 0xA69D }, +	{ 0xA6A0, 0xA6E5 }, +	{ 0xA717, 0xA71F }, +	{ 0xA722, 0xA788 }, +	{ 0xA78B, 0xA7CA }, +	{ 0xA7D0, 0xA7D1 }, +	{ 0xA7D5, 0xA7D9 }, +	{ 0xA7F2, 0xA801 }, +	{ 0xA803, 0xA805 }, +	{ 0xA807, 0xA80A }, +	{ 0xA80C, 0xA822 }, +	{ 0xA840, 0xA873 }, +	{ 0xA882, 0xA8B3 }, +	{ 0xA8F2, 0xA8F7 }, +	{ 0xA8FD, 0xA8FE }, +	{ 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, 0xAB69 }, +	{ 0xAB70, 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 }, +	{ 0x1032D, 0x10340 }, +	{ 0x10342, 0x10349 }, +	{ 0x10350, 0x10375 }, +	{ 0x10380, 0x1039D }, +	{ 0x103A0, 0x103C3 }, +	{ 0x103C8, 0x103CF }, +	{ 0x10400, 0x1049D }, +	{ 0x104B0, 0x104D3 }, +	{ 0x104D8, 0x104FB }, +	{ 0x10500, 0x10527 }, +	{ 0x10530, 0x10563 }, +	{ 0x10570, 0x1057A }, +	{ 0x1057C, 0x1058A }, +	{ 0x1058C, 0x10592 }, +	{ 0x10594, 0x10595 }, +	{ 0x10597, 0x105A1 }, +	{ 0x105A3, 0x105B1 }, +	{ 0x105B3, 0x105B9 }, +	{ 0x105BB, 0x105BC }, +	{ 0x10600, 0x10736 }, +	{ 0x10740, 0x10755 }, +	{ 0x10760, 0x10767 }, +	{ 0x10780, 0x10785 }, +	{ 0x10787, 0x107B0 }, +	{ 0x107B2, 0x107BA }, +	{ 0x10800, 0x10805 }, +	{ 0x1080A, 0x10835 }, +	{ 0x10837, 0x10838 }, +	{ 0x1083F, 0x10855 }, +	{ 0x10860, 0x10876 }, +	{ 0x10880, 0x1089E }, +	{ 0x108E0, 0x108F2 }, +	{ 0x108F4, 0x108F5 }, +	{ 0x10900, 0x10915 }, +	{ 0x10920, 0x10939 }, +	{ 0x10980, 0x109B7 }, +	{ 0x109BE, 0x109BF }, +	{ 0x10A10, 0x10A13 }, +	{ 0x10A15, 0x10A17 }, +	{ 0x10A19, 0x10A35 }, +	{ 0x10A60, 0x10A7C }, +	{ 0x10A80, 0x10A9C }, +	{ 0x10AC0, 0x10AC7 }, +	{ 0x10AC9, 0x10AE4 }, +	{ 0x10B00, 0x10B35 }, +	{ 0x10B40, 0x10B55 }, +	{ 0x10B60, 0x10B72 }, +	{ 0x10B80, 0x10B91 }, +	{ 0x10C00, 0x10C48 }, +	{ 0x10C80, 0x10CB2 }, +	{ 0x10CC0, 0x10CF2 }, +	{ 0x10D00, 0x10D23 }, +	{ 0x10E80, 0x10EA9 }, +	{ 0x10EB0, 0x10EB1 }, +	{ 0x10F00, 0x10F1C }, +	{ 0x10F30, 0x10F45 }, +	{ 0x10F70, 0x10F81 }, +	{ 0x10FB0, 0x10FC4 }, +	{ 0x10FE0, 0x10FF6 }, +	{ 0x11003, 0x11037 }, +	{ 0x11071, 0x11072 }, +	{ 0x11083, 0x110AF }, +	{ 0x110D0, 0x110E8 }, +	{ 0x11103, 0x11126 }, +	{ 0x11150, 0x11172 }, +	{ 0x11183, 0x111B2 }, +	{ 0x111C1, 0x111C4 }, +	{ 0x11200, 0x11211 }, +	{ 0x11213, 0x1122B }, +	{ 0x1123F, 0x11240 }, +	{ 0x11280, 0x11286 }, +	{ 0x1128A, 0x1128D }, +	{ 0x1128F, 0x1129D }, +	{ 0x1129F, 0x112A8 }, +	{ 0x112B0, 0x112DE }, +	{ 0x11305, 0x1130C }, +	{ 0x1130F, 0x11310 }, +	{ 0x11313, 0x11328 }, +	{ 0x1132A, 0x11330 }, +	{ 0x11332, 0x11333 }, +	{ 0x11335, 0x11339 }, +	{ 0x1135D, 0x11361 }, +	{ 0x11400, 0x11434 }, +	{ 0x11447, 0x1144A }, +	{ 0x1145F, 0x11461 }, +	{ 0x11480, 0x114AF }, +	{ 0x114C4, 0x114C5 }, +	{ 0x11580, 0x115AE }, +	{ 0x115D8, 0x115DB }, +	{ 0x11600, 0x1162F }, +	{ 0x11680, 0x116AA }, +	{ 0x11700, 0x1171A }, +	{ 0x11740, 0x11746 }, +	{ 0x11800, 0x1182B }, +	{ 0x118A0, 0x118DF }, +	{ 0x118FF, 0x11906 }, +	{ 0x1190C, 0x11913 }, +	{ 0x11915, 0x11916 }, +	{ 0x11918, 0x1192F }, +	{ 0x119A0, 0x119A7 }, +	{ 0x119AA, 0x119D0 }, +	{ 0x11A0B, 0x11A32 }, +	{ 0x11A5C, 0x11A89 }, +	{ 0x11AB0, 0x11AF8 }, +	{ 0x11C00, 0x11C08 }, +	{ 0x11C0A, 0x11C2E }, +	{ 0x11C72, 0x11C8F }, +	{ 0x11D00, 0x11D06 }, +	{ 0x11D08, 0x11D09 }, +	{ 0x11D0B, 0x11D30 }, +	{ 0x11D60, 0x11D65 }, +	{ 0x11D67, 0x11D68 }, +	{ 0x11D6A, 0x11D89 }, +	{ 0x11EE0, 0x11EF2 }, +	{ 0x11F04, 0x11F10 }, +	{ 0x11F12, 0x11F33 }, +	{ 0x12000, 0x12399 }, +	{ 0x12480, 0x12543 }, +	{ 0x12F90, 0x12FF0 }, +	{ 0x13000, 0x1342F }, +	{ 0x13441, 0x13446 }, +	{ 0x14400, 0x14646 }, +	{ 0x16800, 0x16A38 }, +	{ 0x16A40, 0x16A5E }, +	{ 0x16A70, 0x16ABE }, +	{ 0x16AD0, 0x16AED }, +	{ 0x16B00, 0x16B2F }, +	{ 0x16B40, 0x16B43 }, +	{ 0x16B63, 0x16B77 }, +	{ 0x16B7D, 0x16B8F }, +	{ 0x16E40, 0x16E7F }, +	{ 0x16F00, 0x16F4A }, +	{ 0x16F93, 0x16F9F }, +	{ 0x16FE0, 0x16FE1 }, +	{ 0x18800, 0x18CD5 }, +	{ 0x1AFF0, 0x1AFF3 }, +	{ 0x1AFF5, 0x1AFFB }, +	{ 0x1AFFD, 0x1AFFE }, +	{ 0x1B000, 0x1B122 }, +	{ 0x1B150, 0x1B152 }, +	{ 0x1B164, 0x1B167 }, +	{ 0x1B170, 0x1B2FB }, +	{ 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 }, +	{ 0x1DF00, 0x1DF1E }, +	{ 0x1DF25, 0x1DF2A }, +	{ 0x1E030, 0x1E06D }, +	{ 0x1E100, 0x1E12C }, +	{ 0x1E137, 0x1E13D }, +	{ 0x1E290, 0x1E2AD }, +	{ 0x1E2C0, 0x1E2EB }, +	{ 0x1E4D0, 0x1E4EB }, +	{ 0x1E7E0, 0x1E7E6 }, +	{ 0x1E7E8, 0x1E7EB }, +	{ 0x1E7ED, 0x1E7EE }, +	{ 0x1E7F0, 0x1E7FE }, +	{ 0x1E800, 0x1E8C4 }, +	{ 0x1E900, 0x1E943 }, +	{ 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 const Rune alpha1[] = { +	0x00AA, +	0x00B5, +	0x00BA, +	0x0559, +	0x06FF, +	0x07B1, +	0x07FA, +	0x081A, +	0x0824, +	0x0828, +	0x093D, +	0x0950, +	0x09BD, +	0x09CE, +	0x09FC, +	0x0ABD, +	0x0AD0, +	0x0AF9, +	0x0B3D, +	0x0B71, +	0x0BD0, +	0x0C3D, +	0x0C5D, +	0x0C80, +	0x0CBD, +	0x0D3D, +	0x0D4E, +	0x0EBD, +	0x0F00, +	0x103F, +	0x1061, +	0x108E, +	0x10CD, +	0x17D7, +	0x17DC, +	0x1AA7, +	0x1CFA, +	0x2071, +	0x207F, +	0x2102, +	0x2107, +	0x214E, +	0x2D2D, +	0x2D6F, +	0x2E2F, +	0x3400, +	0x4DBF, +	0x4E00, +	0xA9CF, +	0xAA7A, +	0xAC00, +	0xD7A3, +	0x1083C, +	0x10A00, +	0x10F27, +	0x11075, +	0x11144, +	0x11147, +	0x11176, +	0x1133D, +	0x11350, +	0x11644, +	0x116B8, +	0x11909, +	0x11A00, +	0x11A3A, +	0x11A50, +	0x11A9D, +	0x11C40, +	0x11D46, +	0x11D98, +	0x11FB0, +	0x16F50, +	0x17000, +	0x187F7, +	0x18D00, +	0x18D08, +	0x1B132, +	0x1B155, +	0x1D4A2, +	0x1E14E, +	0x1E94B, +	0x1EE42, +	0x20000, +	0x2A6DF, +	0x2A700, +	0x2B739, +	0x2B740, +	0x2B81D, +	0x2B820, +	0x2CEA1, +	0x2CEB0, +	0x2EBE0, +	0x30000, +	0x3134A, +	0x31350, +	0x323AF, +}; + +int +isalpharune(Rune r) +{ +	const Rune *match; + +	if((match = bsearch(&r, alpha3, nelem(alpha3), sizeof *alpha3, &rune2cmp))) +		return !((r - match[0]) % 2); +	if(bsearch(&r, alpha2, nelem(alpha2), sizeof *alpha2, &rune2cmp)) +		return 1; +	if(bsearch(&r, alpha1, nelem(alpha1), sizeof *alpha1, &rune1cmp)) +		return 1; +	return 0; +} diff --git a/util/sbase/libutf/isblankrune.c b/util/sbase/libutf/isblankrune.c new file mode 100644 index 00000000..7cf91597 --- /dev/null +++ b/util/sbase/libutf/isblankrune.c @@ -0,0 +1,9 @@ +/* Automatically generated by mkrunetype.awk */ +#include "../utf.h" +#include "runetype.h" + +int +isblankrune(Rune r) +{ +	return r == ' ' || r == '\t'; +} diff --git a/util/sbase/libutf/iscntrlrune.c b/util/sbase/libutf/iscntrlrune.c new file mode 100644 index 00000000..603e57cb --- /dev/null +++ b/util/sbase/libutf/iscntrlrune.c @@ -0,0 +1,18 @@ +/* Automatically generated by mkrunetype.awk */ +#include <stdlib.h> + +#include "../utf.h" +#include "runetype.h" + +static const Rune cntrl2[][2] = { +	{ 0x0000, 0x001F }, +	{ 0x007F, 0x009F }, +}; + +int +iscntrlrune(Rune r) +{ +	if(bsearch(&r, cntrl2, nelem(cntrl2), sizeof *cntrl2, &rune2cmp)) +		return 1; +	return 0; +} diff --git a/util/sbase/libutf/isdigitrune.c b/util/sbase/libutf/isdigitrune.c new file mode 100644 index 00000000..c8901bc4 --- /dev/null +++ b/util/sbase/libutf/isdigitrune.c @@ -0,0 +1,80 @@ +/* Automatically generated by mkrunetype.awk */ +#include <stdlib.h> + +#include "../utf.h" +#include "runetype.h" + +static const 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 }, +	{ 0x10D30, 0x10D39 }, +	{ 0x11066, 0x1106F }, +	{ 0x110F0, 0x110F9 }, +	{ 0x11136, 0x1113F }, +	{ 0x111D0, 0x111D9 }, +	{ 0x112F0, 0x112F9 }, +	{ 0x11450, 0x11459 }, +	{ 0x114D0, 0x114D9 }, +	{ 0x11650, 0x11659 }, +	{ 0x116C0, 0x116C9 }, +	{ 0x11730, 0x11739 }, +	{ 0x118E0, 0x118E9 }, +	{ 0x11950, 0x11959 }, +	{ 0x11C50, 0x11C59 }, +	{ 0x11D50, 0x11D59 }, +	{ 0x11DA0, 0x11DA9 }, +	{ 0x11F50, 0x11F59 }, +	{ 0x16A60, 0x16A69 }, +	{ 0x16AC0, 0x16AC9 }, +	{ 0x16B50, 0x16B59 }, +	{ 0x1D7CE, 0x1D7FF }, +	{ 0x1E140, 0x1E149 }, +	{ 0x1E2F0, 0x1E2F9 }, +	{ 0x1E4F0, 0x1E4F9 }, +	{ 0x1E950, 0x1E959 }, +	{ 0x1FBF0, 0x1FBF9 }, +}; + +int +isdigitrune(Rune r) +{ +	if(bsearch(&r, digit2, nelem(digit2), sizeof *digit2, &rune2cmp)) +		return 1; +	return 0; +} diff --git a/util/sbase/libutf/isgraphrune.c b/util/sbase/libutf/isgraphrune.c new file mode 100644 index 00000000..08770f64 --- /dev/null +++ b/util/sbase/libutf/isgraphrune.c @@ -0,0 +1,9 @@ +/* Automatically generated by mkrunetype.awk */ +#include "../utf.h" +#include "runetype.h" + +int +isgraphrune(Rune r) +{ +	return !isspacerune(r) && isprintrune(r); +} diff --git a/util/sbase/libutf/isprintrune.c b/util/sbase/libutf/isprintrune.c new file mode 100644 index 00000000..f6e2fa48 --- /dev/null +++ b/util/sbase/libutf/isprintrune.c @@ -0,0 +1,10 @@ +/* Automatically generated by mkrunetype.awk */ +#include "../utf.h" +#include "runetype.h" + +int +isprintrune(Rune r) +{ +	return !iscntrlrune(r) && (r != 0x2028) && (r != 0x2029) && +	       ((r < 0xFFF9) || (r > 0xFFFB)); +} diff --git a/util/sbase/libutf/ispunctrune.c b/util/sbase/libutf/ispunctrune.c new file mode 100644 index 00000000..d73cb25b --- /dev/null +++ b/util/sbase/libutf/ispunctrune.c @@ -0,0 +1,9 @@ +/* Automatically generated by mkrunetype.awk */ +#include "../utf.h" +#include "runetype.h" + +int +ispunctrune(Rune r) +{ +	return isgraphrune(r) && !isalnumrune(r); +} diff --git a/util/sbase/libutf/isspacerune.c b/util/sbase/libutf/isspacerune.c new file mode 100644 index 00000000..8583f932 --- /dev/null +++ b/util/sbase/libutf/isspacerune.c @@ -0,0 +1,31 @@ +/* Automatically generated by mkrunetype.awk */ +#include <stdlib.h> + +#include "../utf.h" +#include "runetype.h" + +static const Rune space2[][2] = { +	{ 0x0009, 0x000D }, +	{ 0x001C, 0x0020 }, +	{ 0x2000, 0x200A }, +	{ 0x2028, 0x2029 }, +}; + +static const Rune space1[] = { +	0x0085, +	0x00A0, +	0x1680, +	0x202F, +	0x205F, +	0x3000, +}; + +int +isspacerune(Rune r) +{ +	if(bsearch(&r, space2, nelem(space2), sizeof *space2, &rune2cmp)) +		return 1; +	if(bsearch(&r, space1, nelem(space1), sizeof *space1, &rune1cmp)) +		return 1; +	return 0; +} diff --git a/util/sbase/libutf/istitlerune.c b/util/sbase/libutf/istitlerune.c new file mode 100644 index 00000000..36b38d12 --- /dev/null +++ b/util/sbase/libutf/istitlerune.c @@ -0,0 +1,31 @@ +/* Automatically generated by mkrunetype.awk */ +#include <stdlib.h> + +#include "../utf.h" +#include "runetype.h" + +static const Rune title2[][2] = { +	{ 0x1F88, 0x1F8F }, +	{ 0x1F98, 0x1F9F }, +	{ 0x1FA8, 0x1FAF }, +}; + +static const Rune title1[] = { +	0x01C5, +	0x01C8, +	0x01CB, +	0x01F2, +	0x1FBC, +	0x1FCC, +	0x1FFC, +}; + +int +istitlerune(Rune r) +{ +	if(bsearch(&r, title2, nelem(title2), sizeof *title2, &rune2cmp)) +		return 1; +	if(bsearch(&r, title1, nelem(title1), sizeof *title1, &rune1cmp)) +		return 1; +	return 0; +} diff --git a/util/sbase/libutf/isxdigitrune.c b/util/sbase/libutf/isxdigitrune.c new file mode 100644 index 00000000..0797240a --- /dev/null +++ b/util/sbase/libutf/isxdigitrune.c @@ -0,0 +1,9 @@ +/* Automatically generated by mkrunetype.awk */ +#include "../utf.h" +#include "runetype.h" + +int +isxdigitrune(Rune r) +{ +	return (r >= '0' && (r - '0') < 10) || (r >= 'a' && (r - 'a') < 6); +} diff --git a/util/sbase/libutf/lowerrune.c b/util/sbase/libutf/lowerrune.c new file mode 100644 index 00000000..d91a364b --- /dev/null +++ b/util/sbase/libutf/lowerrune.c @@ -0,0 +1,356 @@ +/* Automatically generated by mkrunetype.awk */ +#include <stdlib.h> + +#include "../utf.h" +#include "runetype.h" + +static const 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 }, +	{ 0xA7B5, 0xA7C3 }, +	{ 0xA7C8, 0xA7CA }, +	{ 0xA7D7, 0xA7D9 }, +}; + +static const 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 }, +	{ 0x0284, 0x0286, 0x0284 }, +	{ 0x028A, 0x028B, 0x01B1 }, +	{ 0x028D, 0x0291, 0x028D }, +	{ 0x0295, 0x029C, 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 }, +	{ 0x0587, 0x0588, 0x0587 }, +	{ 0x10D0, 0x10FA, 0x1C90 }, +	{ 0x10FD, 0x10FF, 0x1CBD }, +	{ 0x13F8, 0x13FD, 0x13F0 }, +	{ 0x1C83, 0x1C84, 0x0421 }, +	{ 0x1D00, 0x1D2B, 0x1D00 }, +	{ 0x1D6B, 0x1D77, 0x1D6B }, +	{ 0x1D7A, 0x1D7C, 0x1D7A }, +	{ 0x1D7E, 0x1D8D, 0x1D7E }, +	{ 0x1D8F, 0x1D9A, 0x1D8F }, +	{ 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, 0x2C5F, 0x2C00 }, +	{ 0x2C77, 0x2C7B, 0x2C77 }, +	{ 0x2D00, 0x2D25, 0x10A0 }, +	{ 0xA730, 0xA731, 0xA730 }, +	{ 0xA771, 0xA778, 0xA771 }, +	{ 0xAB30, 0xAB52, 0xAB30 }, +	{ 0xAB54, 0xAB5A, 0xAB54 }, +	{ 0xAB60, 0xAB68, 0xAB60 }, +	{ 0xAB70, 0xABBF, 0x13A0 }, +	{ 0xFB00, 0xFB06, 0xFB00 }, +	{ 0xFB13, 0xFB17, 0xFB13 }, +	{ 0xFF41, 0xFF5A, 0xFF21 }, +	{ 0x10428, 0x1044F, 0x10400 }, +	{ 0x104D8, 0x104FB, 0x104B0 }, +	{ 0x10597, 0x105A1, 0x10570 }, +	{ 0x105A3, 0x105B1, 0x1057C }, +	{ 0x105B3, 0x105B9, 0x1058C }, +	{ 0x105BB, 0x105BC, 0x10594 }, +	{ 0x10CC0, 0x10CF2, 0x10C80 }, +	{ 0x118C0, 0x118DF, 0x118A0 }, +	{ 0x16E60, 0x16E7F, 0x16E40 }, +	{ 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 }, +	{ 0x1DF00, 0x1DF09, 0x1DF00 }, +	{ 0x1DF0B, 0x1DF1E, 0x1DF0B }, +	{ 0x1DF25, 0x1DF2A, 0x1DF25 }, +	{ 0x1E922, 0x1E943, 0x1E900 }, +}; + +static const 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, 0xA7AE }, +	{ 0x026B, 0x2C62 }, +	{ 0x026C, 0xA7AD }, +	{ 0x026F, 0x019C }, +	{ 0x0270, 0x0270 }, +	{ 0x0271, 0x2C6E }, +	{ 0x0272, 0x019D }, +	{ 0x0275, 0x019F }, +	{ 0x027D, 0x2C64 }, +	{ 0x0280, 0x01A6 }, +	{ 0x0281, 0x0281 }, +	{ 0x0282, 0xA7C5 }, +	{ 0x0283, 0x01A9 }, +	{ 0x0287, 0xA7B1 }, +	{ 0x0288, 0x01AE }, +	{ 0x0289, 0x0244 }, +	{ 0x028C, 0x0245 }, +	{ 0x0292, 0x01B7 }, +	{ 0x0293, 0x0293 }, +	{ 0x029D, 0xA7B2 }, +	{ 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 }, +	{ 0x0560, 0x0560 }, +	{ 0x1C80, 0x0412 }, +	{ 0x1C81, 0x0414 }, +	{ 0x1C82, 0x041E }, +	{ 0x1C85, 0x0422 }, +	{ 0x1C86, 0x042A }, +	{ 0x1C87, 0x0462 }, +	{ 0x1C88, 0xA64A }, +	{ 0x1D79, 0xA77D }, +	{ 0x1D7D, 0x2C63 }, +	{ 0x1D8E, 0xA7C6 }, +	{ 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 }, +	{ 0xA794, 0xA7C4 }, +	{ 0xA795, 0xA795 }, +	{ 0xA7AF, 0xA7AF }, +	{ 0xA7D1, 0xA7D0 }, +	{ 0xA7D3, 0xA7D3 }, +	{ 0xA7D5, 0xA7D5 }, +	{ 0xA7F6, 0xA7F5 }, +	{ 0xA7FA, 0xA7FA }, +	{ 0xAB53, 0xA7B3 }, +	{ 0x1D4BB, 0x1D4BB }, +	{ 0x1D7CB, 0x1D7CB }, +}; + +int +islowerrune(Rune r) +{ +	const Rune *match; + +	if((match = bsearch(&r, lower4, nelem(lower4), sizeof *lower4, &rune2cmp))) +		return !((r - match[0]) % 2); +	if(bsearch(&r, lower2, nelem(lower2), sizeof *lower2, &rune2cmp)) +		return 1; +	if(bsearch(&r, lower1, nelem(lower1), sizeof *lower1, &rune1cmp)) +		return 1; +	return 0; +} + +int +toupperrune(Rune r) +{ +	Rune *match; + +	match = bsearch(&r, lower4, nelem(lower4), sizeof *lower4, &rune2cmp); +	if (match) +		return ((r - match[0]) % 2) ? r : r - 1; +	match = bsearch(&r, lower2, nelem(lower2), sizeof *lower2, &rune2cmp); +	if (match) +		return match[2] + (r - match[0]); +	match = bsearch(&r, lower1, nelem(lower1), sizeof *lower1, &rune1cmp); +	if (match) +		return match[1]; +	return r; +} diff --git a/util/sbase/libutf/mkrunetype.awk b/util/sbase/libutf/mkrunetype.awk new file mode 100644 index 00000000..e01ea2cc --- /dev/null +++ b/util/sbase/libutf/mkrunetype.awk @@ -0,0 +1,240 @@ +# See LICENSE file for copyright and license details. + +BEGIN { +	FS = ";" +	# set up hexadecimal lookup table +	for(i = 0; i < 16; i++) +		hex[sprintf("%X",i)] = i; +	HEADER = "/* Automatically generated by mkrunetype.awk */\n#include <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 const 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 const 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 const 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 const 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 const Rune "name"1[][2] = {" > file; +			for(j = 0; j < rune1c; j++) { +				print "\t{ 0x"rune1v[j]", 0x"case1v[j]" }," > file; +			} +		} else { +			print "static const 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 "\tconst Rune *match;\n" > file; +	if(rune4c > 0) { +		print "\tif((match = bsearch(&r, "name"4, nelem("name"4), sizeof *"name"4, &rune2cmp)))" > file; +		print "\t\treturn !((r - match[0]) % 2);" > file; +	} +	if(rune3c > 0) { +		print "\tif((match = bsearch(&r, "name"3, nelem("name"3), sizeof *"name"3, &rune2cmp)))" > file; +		print "\t\treturn !((r - match[0]) % 2);" > file; +	} +	if(rune2c > 0) { +		print "\tif(bsearch(&r, "name"2, nelem("name"2), sizeof *"name"2, &rune2cmp))\n\t\treturn 1;" > file; +	} +	if(rune1c > 0) { +		print "\tif(bsearch(&r, "name"1, nelem("name"1), sizeof *"name"1, &rune1cmp))\n\t\treturn 1;" > file; +	} +	print "\treturn 0;\n}" > file; + +	# generate case conversion function +	if(length(casev) > 0) { +		print "\nint\nto"casename"rune(Rune r)\n{\n\tRune *match;\n" > file; +		if(rune4c > 0) { +			print "\tmatch = bsearch(&r, "name"4, nelem("name"4), sizeof *"name"4, &rune2cmp);" > file; +			print "\tif (match)" > file; +			print "\t\treturn ((r - match[0]) % 2) ? r : r - 1;" > file; +		} +		if(rune3c > 0) { +			print "\tmatch = bsearch(&r, "name"3, nelem("name"3), sizeof *"name"3, &rune2cmp);" > file; +			print "\tif (match)" > file; +			print "\t\treturn ((r - match[0]) % 2) ? r : r + 1;" > file; +		} +		if(rune2c > 0) { +			print "\tmatch = bsearch(&r, "name"2, nelem("name"2), sizeof *"name"2, &rune2cmp);" > file; +			print "\tif (match)" > file; +			print "\t\treturn match[2] + (r - match[0]);" > file; +		} +		if(rune1c > 0) { +			print "\tmatch = bsearch(&r, "name"1, nelem("name"1), sizeof *"name"1, &rune1cmp);" > file; +			print "\tif (match)" > file; +			print "\t\treturn match[1];" > file; +		} +		print "\treturn r;\n}" > file; +	} +} + +function otheris() { +	print HEADER_OTHER > "isalnumrune.c"; +	print "int\nisalnumrune(Rune r)\n{\n\treturn isalpharune(r) || isdigitrune(r);\n}" > "isalnumrune.c"; +	print HEADER_OTHER > "isblankrune.c"; +	print "int\nisblankrune(Rune r)\n{\n\treturn r == ' ' || r == '\\t';\n}" > "isblankrune.c"; +	print HEADER_OTHER > "isprintrune.c"; +	print "int\nisprintrune(Rune r)\n{\n\treturn !iscntrlrune(r) && (r != 0x2028) && (r != 0x2029) &&" > "isprintrune.c"; +	print "\t       ((r < 0xFFF9) || (r > 0xFFFB));\n}" > "isprintrune.c"; +	print HEADER_OTHER > "isgraphrune.c"; +	print "int\nisgraphrune(Rune r)\n{\n\treturn !isspacerune(r) && isprintrune(r);\n}" > "isgraphrune.c"; +	print HEADER_OTHER > "ispunctrune.c"; +	print "int\nispunctrune(Rune r)\n{\n\treturn isgraphrune(r) && !isalnumrune(r);\n}" > "ispunctrune.c"; +	print HEADER_OTHER > "isxdigitrune.c"; +	print "int\nisxdigitrune(Rune r)\n{\n\treturn (r >= '0' && (r - '0') < 10) || (r >= 'a' && (r - 'a') < 6);\n}" > "isxdigitrune.c"; +} diff --git a/util/sbase/libutf/rune.c b/util/sbase/libutf/rune.c new file mode 100644 index 00000000..1273f451 --- /dev/null +++ b/util/sbase/libutf/rune.c @@ -0,0 +1,148 @@ +/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "../utf.h" + +#define MIN(x,y)  ((x) < (y) ? (x) : (y)) + +#define UTFSEQ(x) ((((x) & 0x80) == 0x00) ? 1 /* 0xxxxxxx */ \ +                 : (((x) & 0xC0) == 0x80) ? 0 /* 10xxxxxx */ \ +                 : (((x) & 0xE0) == 0xC0) ? 2 /* 110xxxxx */ \ +                 : (((x) & 0xF0) == 0xE0) ? 3 /* 1110xxxx */ \ +                 : (((x) & 0xF8) == 0xF0) ? 4 /* 11110xxx */ \ +                 : (((x) & 0xFC) == 0xF8) ? 5 /* 111110xx */ \ +                 : (((x) & 0xFE) == 0xFC) ? 6 /* 1111110x */ \ +                                          : 0 ) + +#define BADRUNE(x) ((x) < 0 || (x) > Runemax \ +                || ((x) & 0xFFFE) == 0xFFFE \ +                || ((x) >= 0xD800 && (x) <= 0xDFFF) \ +                || ((x) >= 0xFDD0 && (x) <= 0xFDEF)) + +int +runetochar(char *s, const Rune *p) +{ +	Rune r = *p; + +	switch(runelen(r)) { +	case 1: /* 0aaaaaaa */ +		s[0] = r; +		return 1; +	case 2: /* 00000aaa aabbbbbb */ +		s[0] = 0xC0 | ((r & 0x0007C0) >>  6); /* 110aaaaa */ +		s[1] = 0x80 |  (r & 0x00003F);        /* 10bbbbbb */ +		return 2; +	case 3: /* aaaabbbb bbcccccc */ +		s[0] = 0xE0 | ((r & 0x00F000) >> 12); /* 1110aaaa */ +		s[1] = 0x80 | ((r & 0x000FC0) >>  6); /* 10bbbbbb */ +		s[2] = 0x80 |  (r & 0x00003F);        /* 10cccccc */ +		return 3; +	case 4: /* 000aaabb bbbbcccc ccdddddd */ +		s[0] = 0xF0 | ((r & 0x1C0000) >> 18); /* 11110aaa */ +		s[1] = 0x80 | ((r & 0x03F000) >> 12); /* 10bbbbbb */ +		s[2] = 0x80 | ((r & 0x000FC0) >>  6); /* 10cccccc */ +		s[3] = 0x80 |  (r & 0x00003F);        /* 10dddddd */ +		return 4; +	default: +		return 0; /* error */ +	} +} + +int +chartorune(Rune *p, const char *s) +{ +	return charntorune(p, s, UTFmax); +} + +int +charntorune(Rune *p, const char *s, size_t len) +{ +	unsigned int i, n; +	Rune r; + +	if(len == 0) /* can't even look at s[0] */ +		return 0; + +	switch((n = UTFSEQ(s[0]))) { +	case 1: r = s[0];        break; /* 0xxxxxxx */ +	case 2: r = s[0] & 0x1F; break; /* 110xxxxx */ +	case 3: r = s[0] & 0x0F; break; /* 1110xxxx */ +	case 4: r = s[0] & 0x07; break; /* 11110xxx */ +	case 5: r = s[0] & 0x03; break; /* 111110xx */ +	case 6: r = s[0] & 0x01; break; /* 1111110x */ +	default: /* invalid sequence */ +		*p = Runeerror; +		return 1; +	} +	/* add values from continuation bytes */ +	for(i = 1; i < MIN(n, len); i++) +		if((s[i] & 0xC0) == 0x80) { +			/* add bits from continuation byte to rune value +			 * cannot overflow: 6 byte sequences contain 31 bits */ +			r = (r << 6) | (s[i] & 0x3F); /* 10xxxxxx */ +		} +		else { /* expected continuation */ +			*p = Runeerror; +			return i; +		} + +	if(i < n) /* must have reached len limit */ +		return 0; + +	/* reject invalid or overlong sequences */ +	if(BADRUNE(r) || runelen(r) < (int)n) +		r = Runeerror; + +	*p = r; +	return n; +} + +int +runelen(Rune r) +{ +	if(BADRUNE(r)) +		return 0; /* error */ +	else if(r <= 0x7F) +		return 1; +	else if(r <= 0x07FF) +		return 2; +	else if(r <= 0xFFFF) +		return 3; +	else +		return 4; +} + +size_t +runenlen(const Rune *p, size_t len) +{ +	size_t i, n = 0; + +	for(i = 0; i < len; i++) +		n += runelen(p[i]); +	return n; +} + +int +fullrune(const char *s, size_t len) +{ +	Rune r; + +	return charntorune(&r, s, len) > 0; +} diff --git a/util/sbase/libutf/runetype.c b/util/sbase/libutf/runetype.c new file mode 100644 index 00000000..9e8ede8a --- /dev/null +++ b/util/sbase/libutf/runetype.c @@ -0,0 +1,41 @@ +/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com> + *                            (c) 2015 Laslo Hunhold <dev@frign.de> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "../utf.h" + +int +rune1cmp(const void *v1, const void *v2) +{ +	Rune r1 = *(Rune *)v1, r2 = *(Rune *)v2; + +	return r1 - r2; +} + +int +rune2cmp(const void *v1, const void *v2) +{ +	Rune r = *(Rune *)v1, *p = (Rune *)v2; + +	if(r >= p[0] && r <= p[1]) +		return 0; +	else +		return r - p[0]; +} diff --git a/util/sbase/libutf/runetype.h b/util/sbase/libutf/runetype.h new file mode 100644 index 00000000..8d09c347 --- /dev/null +++ b/util/sbase/libutf/runetype.h @@ -0,0 +1,26 @@ +/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com> + *                            (c) 2015 Laslo Hunhold <dev@frign.de> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#define nelem(x)  (sizeof (x) / sizeof *(x)) + +int rune1cmp(const void *, const void *); +int rune2cmp(const void *, const void *); diff --git a/util/sbase/libutf/upperrune.c b/util/sbase/libutf/upperrune.c new file mode 100644 index 00000000..0c874a85 --- /dev/null +++ b/util/sbase/libutf/upperrune.c @@ -0,0 +1,265 @@ +/* Automatically generated by mkrunetype.awk */ +#include <stdlib.h> + +#include "../utf.h" +#include "runetype.h" + +static const 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 }, +	{ 0xA7B4, 0xA7C2 }, +	{ 0xA7C7, 0xA7C9 }, +	{ 0xA7D6, 0xA7D8 }, +}; + +static const 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 }, +	{ 0x13A0, 0x13EF, 0xAB70 }, +	{ 0x13F0, 0x13F5, 0x13F8 }, +	{ 0x1C90, 0x1CBA, 0x10D0 }, +	{ 0x1CBD, 0x1CBF, 0x10FD }, +	{ 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, 0x2C2F, 0x2C30 }, +	{ 0x2C7E, 0x2C7F, 0x023F }, +	{ 0xFF21, 0xFF3A, 0xFF41 }, +	{ 0x10400, 0x10427, 0x10428 }, +	{ 0x104B0, 0x104D3, 0x104D8 }, +	{ 0x10570, 0x1057A, 0x10597 }, +	{ 0x1057C, 0x1058A, 0x105A3 }, +	{ 0x1058C, 0x10592, 0x105B3 }, +	{ 0x10594, 0x10595, 0x105BB }, +	{ 0x10C80, 0x10CB2, 0x10CC0 }, +	{ 0x118A0, 0x118BF, 0x118C0 }, +	{ 0x16E40, 0x16E5F, 0x16E60 }, +	{ 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 }, +	{ 0x1E900, 0x1E921, 0x1E922 }, +}; + +static const 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 }, +	{ 0xA7AE, 0x026A }, +	{ 0xA7B0, 0x029E }, +	{ 0xA7B1, 0x0287 }, +	{ 0xA7B2, 0x029D }, +	{ 0xA7B3, 0xAB53 }, +	{ 0xA7C4, 0xA794 }, +	{ 0xA7C5, 0x0282 }, +	{ 0xA7C6, 0x1D8E }, +	{ 0xA7D0, 0xA7D1 }, +	{ 0xA7F5, 0xA7F6 }, +	{ 0x1D49C, 0x1D49C }, +	{ 0x1D4A2, 0x1D4A2 }, +	{ 0x1D546, 0x1D546 }, +	{ 0x1D7CA, 0x1D7CA }, +}; + +int +isupperrune(Rune r) +{ +	const Rune *match; + +	if((match = bsearch(&r, upper3, nelem(upper3), sizeof *upper3, &rune2cmp))) +		return !((r - match[0]) % 2); +	if(bsearch(&r, upper2, nelem(upper2), sizeof *upper2, &rune2cmp)) +		return 1; +	if(bsearch(&r, upper1, nelem(upper1), sizeof *upper1, &rune1cmp)) +		return 1; +	return 0; +} + +int +tolowerrune(Rune r) +{ +	Rune *match; + +	match = bsearch(&r, upper3, nelem(upper3), sizeof *upper3, &rune2cmp); +	if (match) +		return ((r - match[0]) % 2) ? r : r + 1; +	match = bsearch(&r, upper2, nelem(upper2), sizeof *upper2, &rune2cmp); +	if (match) +		return match[2] + (r - match[0]); +	match = bsearch(&r, upper1, nelem(upper1), sizeof *upper1, &rune1cmp); +	if (match) +		return match[1]; +	return r; +} diff --git a/util/sbase/libutf/utf.c b/util/sbase/libutf/utf.c new file mode 100644 index 00000000..492e020f --- /dev/null +++ b/util/sbase/libutf/utf.c @@ -0,0 +1,142 @@ +/* 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; +} + +size_t +utfmemlen(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))); i++) +		p += n; +	return i; +} + +char * +utfrune(const char *s, Rune r) +{ +	if(r < Runeself) { +		return strchr(s, r); +	} +	else if(r == Runeerror) { +		Rune r0; +		int n; + +		for(; *s != '\0'; s += n) { +			n = chartorune(&r0, s); +			if(r == r0) +				return (char *)s; +		} +	} +	else { +		char buf[UTFmax+1]; +		int n; + +		if(!(n = runetochar(buf, &r))) +			return NULL; +		buf[n] = '\0'; +		return strstr(s, buf); +	} +	return NULL; +} + +char * +utfrrune(const char *s, Rune r) +{ +	const char *p = NULL; +	Rune r0; +	int n; + +	if(r < Runeself) +		return strrchr(s, r); + +	for(; *s != '\0'; s += n) { +		n = chartorune(&r0, s); +		if(r == r0) +			p = s; +	} +	return (char *)p; +} + +char * +utfutf(const char *s, const char *t) +{ +	const char *p, *q; +	Rune r0, r1, r2; +	int n, m; + +	for(chartorune(&r0, t); (s = utfrune(s, r0)); s++) { +		for(p = s, q = t; *q && *p; p += n, q += m) { +			n = chartorune(&r1, p); +			m = chartorune(&r2, q); +			if(r1 != r2) +				break; +		} +		if(!*q) +			return (char *)s; +	} +	return NULL; +} diff --git a/util/sbase/libutf/utftorunestr.c b/util/sbase/libutf/utftorunestr.c new file mode 100644 index 00000000..e182bc15 --- /dev/null +++ b/util/sbase/libutf/utftorunestr.c @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#include "../utf.h" + +size_t +utftorunestr(const char *str, Rune *r) +{ +	size_t i; +	int n; + +	for (i = 0; (n = chartorune(&r[i], str)) && r[i]; i++) +		str += n; + +	return i; +} + +size_t +utfntorunestr(const char *str, size_t len, Rune *r) +{ +	size_t i; +	int n; +	const char *end = str + len; + +	for (i = 0; (n = charntorune(&r[i], str, end - str)); i++) +		str += n; + +	return i; +} diff --git a/util/sbase/libutil/concat.c b/util/sbase/libutil/concat.c new file mode 100644 index 00000000..2e9aa521 --- /dev/null +++ b/util/sbase/libutil/concat.c @@ -0,0 +1,23 @@ +/* See LICENSE file for copyright and license details. */ +#include <unistd.h> + +#include "../util.h" + +int +concat(int f1, const char *s1, int f2, const char *s2) +{ +	char buf[BUFSIZ]; +	ssize_t n; + +	while ((n = read(f1, buf, sizeof(buf))) > 0) { +		if (writeall(f2, buf, n) < 0) { +			weprintf("write %s:", s2); +			return -2; +		} +	} +	if (n < 0) { +		weprintf("read %s:", s1); +		return -1; +	} +	return 0; +} diff --git a/util/sbase/libutil/confirm.c b/util/sbase/libutil/confirm.c new file mode 100644 index 00000000..44396af9 --- /dev/null +++ b/util/sbase/libutil/confirm.c @@ -0,0 +1,22 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdarg.h> +#include <ctype.h> + +#include "../util.h" + +int +confirm(const char *fmt, ...) +{ +	int c, ans; +	va_list ap; + +	va_start(ap, fmt); +	xvprintf(fmt, ap); +	va_end(ap); + +	c = getchar(); +	ans = (c == 'y' || c == 'Y'); +	while (c != '\n' && c != EOF) +		c = getchar(); +	return ans; +} diff --git a/util/sbase/libutil/cp.c b/util/sbase/libutil/cp.c new file mode 100644 index 00000000..2ab32a03 --- /dev/null +++ b/util/sbase/libutil/cp.c @@ -0,0 +1,176 @@ +/* See LICENSE file for copyright and license details. */ +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <utime.h> + +#include "../fs.h" +#include "../util.h" + +int cp_aflag  = 0; +int cp_fflag  = 0; +int cp_iflag  = 0; +int cp_pflag  = 0; +int cp_rflag  = 0; +int cp_vflag  = 0; +int cp_status = 0; +int cp_follow; + +int +cp(const char *s1, const char *s2, int depth) +{ +	DIR *dp; +	int f1, f2, flags = 0; +	struct dirent *d; +	struct stat st; +	struct timespec times[2]; +	ssize_t r; +	char target[PATH_MAX], ns1[PATH_MAX], ns2[PATH_MAX]; + +	if (cp_follow == 'P' || (cp_follow == 'H' && depth)) +		flags |= AT_SYMLINK_NOFOLLOW; + +	if (fstatat(AT_FDCWD, s1, &st, flags) < 0) { +		weprintf("stat %s:", s1); +		cp_status = 1; +		return 0; +	} + +	if (cp_iflag && access(s2, F_OK) == 0) { +		if (!confirm("overwrite '%s'? ", s2)) +			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; +			closedir(dp); +			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 = open(s1, O_RDONLY)) < 0) { +			weprintf("open %s:", s1); +			cp_status = 1; +			return 0; +		} +		if ((f2 = creat(s2, st.st_mode)) < 0 && cp_fflag) { +			if (unlink(s2) < 0 && errno != ENOENT) { +				weprintf("unlink %s:", s2); +				cp_status = 1; +				close(f1); +				return 0; +			} +			f2 = creat(s2, st.st_mode); +		} +		if (f2 < 0) { +			weprintf("creat %s:", s2); +			cp_status = 1; +			close(f1); +			return 0; +		} +		if (concat(f1, s1, f2, s2) < 0) { +			cp_status = 1; +			close(f1); +			close(f2); +			return 0; +		} + +		close(f1); +		close(f2); +	} + +	if (cp_aflag || cp_pflag) { +		/* atime and mtime */ +		times[0] = st.st_atim; +		times[1] = st.st_mtim; +		if (utimensat(AT_FDCWD, s2, times, AT_SYMLINK_NOFOLLOW) < 0) { +			weprintf("utimensat %s:", s2); +			cp_status = 1; +		} + +		/* owner and mode */ +		if (!S_ISLNK(st.st_mode)) { +			if (chown(s2, st.st_uid, st.st_gid) < 0) { +				weprintf("chown %s:", s2); +				cp_status = 1; +				st.st_mode &= ~(S_ISUID | S_ISGID); +			} +			if (chmod(s2, st.st_mode) < 0) { +				weprintf("chmod %s:", s2); +				cp_status = 1; +			} +		} else { +			if (lchown(s2, st.st_uid, st.st_gid) < 0) { +				weprintf("lchown %s:", s2); +				cp_status = 1; +				return 0; +			} +		} +	} + +	return 0; +} diff --git a/util/sbase/libutil/crypt.c b/util/sbase/libutil/crypt.c new file mode 100644 index 00000000..e285614b --- /dev/null +++ b/util/sbase/libutil/crypt.c @@ -0,0 +1,184 @@ +/* See LICENSE file for copyright and license details. */ +#include <fcntl.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.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) +{ +	int fd; +	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 ((fd = open(file, O_RDONLY)) < 0) { +			weprintf("open %s:", file); +			(*noread)++; +			continue; +		} +		if (cryptsum(ops, fd, file, md)) { +			(*noread)++; +			continue; +		} +		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)++; +		} +		close(fd); +	} +	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) +{ +	int fd; +	int ret = 0; + +	if (argc == 0) { +		if (cryptsum(ops, 0, "<stdin>", md)) +			ret = 1; +		else +			mdprint(md, "<stdin>", sz); +	} else { +		for (; *argv; argc--, argv++) { +			if ((*argv)[0] == '-' && !(*argv)[1]) { +				*argv = "<stdin>"; +				fd = 0; +			} else if ((fd = open(*argv, O_RDONLY)) < 0) { +				weprintf("open %s:", *argv); +				ret = 1; +				continue; +			} +			if (cryptsum(ops, fd, *argv, md)) +				ret = 1; +			else +				mdprint(md, *argv, sz); +			if (fd != 0) +				close(fd); +		} +	} + +	return ret; +} + +int +cryptsum(struct crypt_ops *ops, int fd, const char *f, uint8_t *md) +{ +	uint8_t buf[BUFSIZ]; +	ssize_t n; + +	ops->init(ops->s); +	while ((n = read(fd, buf, sizeof(buf))) > 0) +		ops->update(ops->s, buf, n); +	if (n < 0) { +		weprintf("%s: read error:", f); +		return 1; +	} +	ops->sum(ops->s, md); +	return 0; +} + +void +mdprint(const uint8_t *md, const char *f, size_t len) +{ +	size_t i; + +	for (i = 0; i < len; i++) +		printf("%02x", md[i]); +	printf("  %s\n", f); +} diff --git a/util/sbase/libutil/ealloc.c b/util/sbase/libutil/ealloc.c new file mode 100644 index 00000000..320865da --- /dev/null +++ b/util/sbase/libutil/ealloc.c @@ -0,0 +1,88 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdlib.h> +#include <string.h> + +#include "../util.h" + +void * +ecalloc(size_t nmemb, size_t size) +{ +	return encalloc(1, nmemb, size); +} + +void * +emalloc(size_t size) +{ +	return enmalloc(1, size); +} + +void * +erealloc(void *p, size_t size) +{ +	return enrealloc(1, p, size); +} + +char * +estrdup(const char *s) +{ +	return enstrdup(1, s); +} + +char * +estrndup(const char *s, size_t n) +{ +	return enstrndup(1, s, n); +} + +void * +encalloc(int status, size_t nmemb, size_t size) +{ +	void *p; + +	p = calloc(nmemb, size); +	if (!p) +		enprintf(status, "calloc: out of memory\n"); +	return p; +} + +void * +enmalloc(int status, size_t size) +{ +	void *p; + +	p = malloc(size); +	if (!p) +		enprintf(status, "malloc: out of memory\n"); +	return p; +} + +void * +enrealloc(int status, void *p, size_t size) +{ +	p = realloc(p, size); +	if (!p) +		enprintf(status, "realloc: out of memory\n"); +	return p; +} + +char * +enstrdup(int status, const char *s) +{ +	char *p; + +	p = strdup(s); +	if (!p) +		enprintf(status, "strdup: out of memory\n"); +	return p; +} + +char * +enstrndup(int status, const char *s, size_t n) +{ +	char *p; + +	p = strndup(s, n); +	if (!p) +		enprintf(status, "strndup: out of memory\n"); +	return p; +} diff --git a/util/sbase/libutil/enmasse.c b/util/sbase/libutil/enmasse.c new file mode 100644 index 00000000..a2e225ab --- /dev/null +++ b/util/sbase/libutil/enmasse.c @@ -0,0 +1,38 @@ +/* See LICENSE file for copyright and license details. */ +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "../util.h" + +void +enmasse(int argc, char *argv[], int (*fn)(const char *, const char *, int)) +{ +	struct stat st; +	char buf[PATH_MAX], *dir; +	int i, len; +	size_t dlen; + +	if (argc == 2 && !(stat(argv[1], &st) == 0 && S_ISDIR(st.st_mode))) { +		fnck(argv[0], argv[1], fn, 0); +		return; +	} else { +		dir = (argc == 1) ? "." : argv[--argc]; +	} + +	for (i = 0; i < argc; i++) { +		dlen = strlen(dir); +		if (dlen > 0 && dir[dlen - 1] == '/') +			len = snprintf(buf, sizeof(buf), "%s%s", dir, basename(argv[i])); +		else +			len = snprintf(buf, sizeof(buf), "%s/%s", dir, basename(argv[i])); +		if (len < 0 || len >= sizeof(buf)) { +			eprintf("%s/%s: filename too long\n", dir, +			        basename(argv[i])); +		} +		fnck(argv[i], buf, fn, 0); +	} +} diff --git a/util/sbase/libutil/eprintf.c b/util/sbase/libutil/eprintf.c new file mode 100644 index 00000000..7197fbb9 --- /dev/null +++ b/util/sbase/libutil/eprintf.c @@ -0,0 +1,57 @@ +/* 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; + +void +eprintf(const char *fmt, ...) +{ +	va_list ap; + +	va_start(ap, fmt); +	xvprintf(fmt, ap); +	va_end(ap); + +	exit(1); +} + +void +enprintf(int status, const char *fmt, ...) +{ +	va_list ap; + +	va_start(ap, fmt); +	xvprintf(fmt, ap); +	va_end(ap); + +	exit(status); +} + +void +weprintf(const char *fmt, ...) +{ +	va_list ap; + +	va_start(ap, fmt); +	xvprintf(fmt, ap); +	va_end(ap); +} + +void +xvprintf(const char *fmt, va_list ap) +{ +	if (argv0 && strncmp(fmt, "usage", strlen("usage"))) +		fprintf(stderr, "%s: ", argv0); + +	vfprintf(stderr, fmt, ap); + +	if (fmt[0] && fmt[strlen(fmt)-1] == ':') { +		fputc(' ', stderr); +		perror(NULL); +	} +} diff --git a/util/sbase/libutil/eregcomp.c b/util/sbase/libutil/eregcomp.c new file mode 100644 index 00000000..02c8698c --- /dev/null +++ b/util/sbase/libutil/eregcomp.c @@ -0,0 +1,27 @@ +#include <sys/types.h> + +#include <regex.h> +#include <stdio.h> + +#include "../util.h" + +int +enregcomp(int status, regex_t *preg, const char *regex, int cflags) +{ +	char errbuf[BUFSIZ] = ""; +	int r; + +	if ((r = regcomp(preg, regex, cflags)) == 0) +		return r; + +	regerror(r, preg, errbuf, sizeof(errbuf)); +	enprintf(status, "invalid regex: %s\n", errbuf); + +	return r; +} + +int +eregcomp(regex_t *preg, const char *regex, int cflags) +{ +	return enregcomp(1, preg, regex, cflags); +} diff --git a/util/sbase/libutil/estrtod.c b/util/sbase/libutil/estrtod.c new file mode 100644 index 00000000..24e4fdce --- /dev/null +++ b/util/sbase/libutil/estrtod.c @@ -0,0 +1,18 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../util.h" + +double +estrtod(const char *s) +{ +	char *end; +	double d; + +	d = strtod(s, &end); +	if (end == s || *end != '\0') +		eprintf("%s: not a real number\n", s); +	return d; +} diff --git a/util/sbase/libutil/fnck.c b/util/sbase/libutil/fnck.c new file mode 100644 index 00000000..4f8875ba --- /dev/null +++ b/util/sbase/libutil/fnck.c @@ -0,0 +1,22 @@ +/* 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) { +		weprintf("%s -> %s: same file\n", a, b); +		return; +	} + +	if (fn(a, b, depth) < 0) +		eprintf("%s -> %s:", a, b); +} diff --git a/util/sbase/libutil/fshut.c b/util/sbase/libutil/fshut.c new file mode 100644 index 00000000..e596f074 --- /dev/null +++ b/util/sbase/libutil/fshut.c @@ -0,0 +1,43 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> + +#include "../util.h" + +int +fshut(FILE *fp, const char *fname) +{ +	int ret = 0; + +	/* fflush() is undefined for input streams by ISO C, +	 * but not POSIX 2008 if you ignore ISO C overrides. +	 * Leave it unchecked and rely on the following +	 * functions to detect errors. +	 */ +	fflush(fp); + +	if (ferror(fp) && !ret) { +		weprintf("ferror %s:", fname); +		ret = 1; +	} + +	if (fclose(fp) && !ret) { +		weprintf("fclose %s:", fname); +		ret = 1; +	} + +	return ret; +} + +void +enfshut(int status, FILE *fp, const char *fname) +{ +	if (fshut(fp, fname)) +		exit(status); +} + +void +efshut(FILE *fp, const char *fname) +{ +	enfshut(1, fp, fname); +} diff --git a/util/sbase/libutil/getlines.c b/util/sbase/libutil/getlines.c new file mode 100644 index 00000000..cef9a612 --- /dev/null +++ b/util/sbase/libutil/getlines.c @@ -0,0 +1,32 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../text.h" +#include "../util.h" + +void +getlines(FILE *fp, struct linebuf *b) +{ +	char *line = NULL; +	size_t size = 0, linelen = 0; +	ssize_t len; + +	while ((len = getline(&line, &size, fp)) > 0) { +		if (++b->nlines > b->capacity) { +			b->capacity += 512; +			b->lines = ereallocarray(b->lines, b->capacity, sizeof(*b->lines)); +		} +		linelen = len; +		b->lines[b->nlines - 1].data = memcpy(emalloc(linelen + 1), line, linelen + 1); +		b->lines[b->nlines - 1].len = linelen; +	} +	free(line); +	if (b->lines && b->nlines && linelen && b->lines[b->nlines - 1].data[linelen - 1] != '\n') { +		b->lines[b->nlines - 1].data = erealloc(b->lines[b->nlines - 1].data, linelen + 2); +		b->lines[b->nlines - 1].data[linelen] = '\n'; +		b->lines[b->nlines - 1].data[linelen + 1] = '\0'; +		b->lines[b->nlines - 1].len++; +	} +} diff --git a/util/sbase/libutil/human.c b/util/sbase/libutil/human.c new file mode 100644 index 00000000..7e39ba5f --- /dev/null +++ b/util/sbase/libutil/human.c @@ -0,0 +1,25 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <string.h> +#include <stdint.h> + +#include "../util.h" + +char * +humansize(off_t n) +{ +	static char buf[16]; +	const char postfixes[] = "BKMGTPE"; +	double size; +	int i; + +	for (size = n, i = 0; size >= 1024 && i < strlen(postfixes); i++) +		size /= 1024; + +	if (!i) +		snprintf(buf, sizeof(buf), "%ju", (uintmax_t)n); +	else +		snprintf(buf, sizeof(buf), "%.1f%c", size, postfixes[i]); + +	return buf; +} diff --git a/util/sbase/libutil/linecmp.c b/util/sbase/libutil/linecmp.c new file mode 100644 index 00000000..08fc0e3a --- /dev/null +++ b/util/sbase/libutil/linecmp.c @@ -0,0 +1,17 @@ +/* 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)))) +		res = a->len - b->len; + +	return res; +} diff --git a/util/sbase/libutil/md5.c b/util/sbase/libutil/md5.c new file mode 100644 index 00000000..c7483ac6 --- /dev/null +++ b/util/sbase/libutil/md5.c @@ -0,0 +1,148 @@ +/* public domain md5 implementation based on rfc1321 and libtomcrypt */ +#include <stdint.h> +#include <string.h> + +#include "../md5.h" + +static uint32_t rol(uint32_t n, int k) { return (n << k) | (n >> (32-k)); } +#define F(x,y,z) (z ^ (x & (y ^ z))) +#define G(x,y,z) (y ^ (z & (y ^ x))) +#define H(x,y,z) (x ^ y ^ z) +#define I(x,y,z) (y ^ (x | ~z)) +#define FF(a,b,c,d,w,s,t) a += F(b,c,d) + w + t; a = rol(a,s) + b +#define GG(a,b,c,d,w,s,t) a += G(b,c,d) + w + t; a = rol(a,s) + b +#define HH(a,b,c,d,w,s,t) a += H(b,c,d) + w + t; a = rol(a,s) + b +#define II(a,b,c,d,w,s,t) a += I(b,c,d) + w + t; a = rol(a,s) + b + +static const uint32_t tab[64] = { +	0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, +	0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, +	0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, +	0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, +	0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, +	0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, +	0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, +	0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 +}; + +static void +processblock(struct md5 *s, const uint8_t *buf) +{ +	uint32_t i, W[16], a, b, c, d; + +	for (i = 0; i < 16; i++) { +		W[i] = buf[4*i]; +		W[i] |= (uint32_t)buf[4*i+1]<<8; +		W[i] |= (uint32_t)buf[4*i+2]<<16; +		W[i] |= (uint32_t)buf[4*i+3]<<24; +	} + +	a = s->h[0]; +	b = s->h[1]; +	c = s->h[2]; +	d = s->h[3]; + +	i = 0; +	while (i < 16) { +		FF(a,b,c,d, W[i],  7, tab[i]); i++; +		FF(d,a,b,c, W[i], 12, tab[i]); i++; +		FF(c,d,a,b, W[i], 17, tab[i]); i++; +		FF(b,c,d,a, W[i], 22, tab[i]); i++; +	} +	while (i < 32) { +		GG(a,b,c,d, W[(5*i+1)%16],  5, tab[i]); i++; +		GG(d,a,b,c, W[(5*i+1)%16],  9, tab[i]); i++; +		GG(c,d,a,b, W[(5*i+1)%16], 14, tab[i]); i++; +		GG(b,c,d,a, W[(5*i+1)%16], 20, tab[i]); i++; +	} +	while (i < 48) { +		HH(a,b,c,d, W[(3*i+5)%16],  4, tab[i]); i++; +		HH(d,a,b,c, W[(3*i+5)%16], 11, tab[i]); i++; +		HH(c,d,a,b, W[(3*i+5)%16], 16, tab[i]); i++; +		HH(b,c,d,a, W[(3*i+5)%16], 23, tab[i]); i++; +	} +	while (i < 64) { +		II(a,b,c,d, W[7*i%16],  6, tab[i]); i++; +		II(d,a,b,c, W[7*i%16], 10, tab[i]); i++; +		II(c,d,a,b, W[7*i%16], 15, tab[i]); i++; +		II(b,c,d,a, W[7*i%16], 21, tab[i]); i++; +	} + +	s->h[0] += a; +	s->h[1] += b; +	s->h[2] += c; +	s->h[3] += d; +} + +static void +pad(struct md5 *s) +{ +	unsigned r = s->len % 64; + +	s->buf[r++] = 0x80; +	if (r > 56) { +		memset(s->buf + r, 0, 64 - r); +		r = 0; +		processblock(s, s->buf); +	} +	memset(s->buf + r, 0, 56 - r); +	s->len *= 8; +	s->buf[56] = s->len; +	s->buf[57] = s->len >> 8; +	s->buf[58] = s->len >> 16; +	s->buf[59] = s->len >> 24; +	s->buf[60] = s->len >> 32; +	s->buf[61] = s->len >> 40; +	s->buf[62] = s->len >> 48; +	s->buf[63] = s->len >> 56; +	processblock(s, s->buf); +} + +void +md5_init(void *ctx) +{ +	struct md5 *s = ctx; +	s->len = 0; +	s->h[0] = 0x67452301; +	s->h[1] = 0xefcdab89; +	s->h[2] = 0x98badcfe; +	s->h[3] = 0x10325476; +} + +void +md5_sum(void *ctx, uint8_t md[MD5_DIGEST_LENGTH]) +{ +	struct md5 *s = ctx; +	int i; + +	pad(s); +	for (i = 0; i < 4; i++) { +		md[4*i] = s->h[i]; +		md[4*i+1] = s->h[i] >> 8; +		md[4*i+2] = s->h[i] >> 16; +		md[4*i+3] = s->h[i] >> 24; +	} +} + +void +md5_update(void *ctx, const void *m, unsigned long len) +{ +	struct md5 *s = ctx; +	const uint8_t *p = m; +	unsigned r = s->len % 64; + +	s->len += len; +	if (r) { +		if (len < 64 - r) { +			memcpy(s->buf + r, p, len); +			return; +		} +		memcpy(s->buf + r, p, 64 - r); +		len -= 64 - r; +		p += 64 - r; +		processblock(s, s->buf); +	} +	for (; len >= 64; len -= 64, p += 64) +		processblock(s, p); +	memcpy(s->buf, p, len); +} diff --git a/util/sbase/libutil/memmem.c b/util/sbase/libutil/memmem.c new file mode 100644 index 00000000..7dfef34b --- /dev/null +++ b/util/sbase/libutil/memmem.c @@ -0,0 +1,66 @@ +/*	$OpenBSD: memmem.c,v 1.4 2015/08/31 02:53:57 guenther Exp $ */ + +/* + * Copyright (c) 2005 Pascal Gloor <pascal.gloor@spale.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + *    products derived from this software without specific prior written + *    permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <string.h> + +#include "../util.h" + +/* + * Find the first occurrence of the byte string s in byte string l. + */ + +void * +memmem(const void *l, size_t l_len, const void *s, size_t s_len) +{ +	const char *cur, *last; +	const char *cl = l; +	const char *cs = s; + +	/* a zero length needle should just return the haystack */ +	if (s_len == 0) +		return (void *)cl; + +	/* "s" must be smaller or equal to "l" */ +	if (l_len < s_len) +		return NULL; + +	/* special case where s_len == 1 */ +	if (s_len == 1) +		return memchr(l, *cs, l_len); + +	/* the last position where its possible to find "s" in "l" */ +	last = cl + l_len - s_len; + +	for (cur = cl; cur <= last; cur++) +		if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) +			return (void *)cur; + +	return NULL; +} diff --git a/util/sbase/libutil/mkdirp.c b/util/sbase/libutil/mkdirp.c new file mode 100644 index 00000000..c3c678c0 --- /dev/null +++ b/util/sbase/libutil/mkdirp.c @@ -0,0 +1,39 @@ +/* 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, mode_t mode, mode_t pmode) +{ +	char tmp[PATH_MAX], *p; +	struct stat st; + +	if (stat(path, &st) == 0) { +		if (S_ISDIR(st.st_mode)) +			return 0; +		errno = ENOTDIR; +		weprintf("%s:", path); +		return -1; +	} + +	estrlcpy(tmp, path, sizeof(tmp)); +	for (p = tmp + (tmp[0] == '/'); *p; p++) { +		if (*p != '/') +			continue; +		*p = '\0'; +		if (mkdir(tmp, pmode) < 0 && errno != EEXIST) { +			weprintf("mkdir %s:", tmp); +			return -1; +		} +		*p = '/'; +	} +	if (mkdir(tmp, mode) < 0 && errno != EEXIST) { +		weprintf("mkdir %s:", tmp); +		return -1; +	} +	return 0; +} diff --git a/util/sbase/libutil/mode.c b/util/sbase/libutil/mode.c new file mode 100644 index 00000000..2754be79 --- /dev/null +++ b/util/sbase/libutil/mode.c @@ -0,0 +1,152 @@ +/* See LICENSE file for copyright and license details. */ +#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 octal; +	} +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|S_ISVTX; +			continue; +		case 'a': +			who |= S_IRWXU|S_ISUID|S_IRWXG|S_ISGID|S_IRWXO|S_ISVTX; +			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); +		} + +		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 'X': +					if (S_ISDIR(mode) || mode & (S_IXUSR|S_IXGRP|S_IXOTH)) +						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 & ~S_IFMT; +} diff --git a/util/sbase/libutil/parseoffset.c b/util/sbase/libutil/parseoffset.c new file mode 100644 index 00000000..362a7829 --- /dev/null +++ b/util/sbase/libutil/parseoffset.c @@ -0,0 +1,61 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> + +#include "../util.h" + +off_t +parseoffset(const char *str) +{ +	off_t res, scale = 1; +	char *end; + +	/* strictly check what strtol() usually would let pass */ +	if (!str || !*str || isspace(*str) || *str == '+' || *str == '-') { +		weprintf("parseoffset %s: invalid value\n", str); +		return -1; +	} + +	errno = 0; +	res = strtol(str, &end, 0); +	if (errno) { +		weprintf("parseoffset %s: invalid value\n", str); +		return -1; +	} +	if (res < 0) { +		weprintf("parseoffset %s: negative value\n", str); +		return -1; +	} + +	/* suffix */ +	if (*end) { +		switch (toupper((int)*end)) { +		case 'B': +			scale = 512L; +			break; +		case 'K': +			scale = 1024L; +			break; +		case 'M': +			scale = 1024L * 1024L; +			break; +		case 'G': +			scale = 1024L * 1024L * 1024L; +			break; +		default: +			weprintf("parseoffset %s: invalid suffix '%s'\n", str, end); +			return -1; +		} +	} + +	/* prevent overflow */ +	if (res > (SSIZE_MAX / scale)) { +		weprintf("parseoffset %s: out of range\n", str); +		return -1; +	} + +	return res * scale; +} diff --git a/util/sbase/libutil/putword.c b/util/sbase/libutil/putword.c new file mode 100644 index 00000000..80a9860a --- /dev/null +++ b/util/sbase/libutil/putword.c @@ -0,0 +1,16 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> + +#include "../util.h" + +void +putword(FILE *fp, const char *s) +{ +	static int first = 1; + +	if (!first) +		fputc(' ', fp); + +	fputs(s, fp); +	first = 0; +} diff --git a/util/sbase/libutil/reallocarray.c b/util/sbase/libutil/reallocarray.c new file mode 100644 index 00000000..31ff6c31 --- /dev/null +++ b/util/sbase/libutil/reallocarray.c @@ -0,0 +1,56 @@ +/* + * 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) +{ +	return enreallocarray(1, optr, nmemb, size); +} + +void * +enreallocarray(int status, void *optr, size_t nmemb, size_t size) +{ +	void *p; + +	if (!(p = reallocarray(optr, nmemb, size))) +		enprintf(status, "reallocarray: out of memory\n"); + +	return p; +} diff --git a/util/sbase/libutil/recurse.c b/util/sbase/libutil/recurse.c new file mode 100644 index 00000000..e66efaf5 --- /dev/null +++ b/util/sbase/libutil/recurse.c @@ -0,0 +1,108 @@ +/* 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 "../fs.h" +#include "../util.h" + +int recurse_status = 0; + +void +recurse(int dirfd, const char *name, void *data, struct recursor *r) +{ +	struct dirent *d; +	struct history *new, *h; +	struct stat st, dst; +	DIR *dp; +	int flags = 0, fd; +	size_t pathlen = r->pathlen; + +	if (dirfd == AT_FDCWD) +		pathlen = estrlcpy(r->path, name, sizeof(r->path)); + +	if (r->follow == 'P' || (r->follow == 'H' && r->depth)) +		flags |= AT_SYMLINK_NOFOLLOW; + +	if (fstatat(dirfd, name, &st, flags) < 0) { +		if (!(r->flags & SILENT)) { +			weprintf("stat %s:", r->path); +			recurse_status = 1; +		} +		return; +	} +	if (!S_ISDIR(st.st_mode)) { +		r->fn(dirfd, name, &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 (!r->depth && (r->flags & DIRFIRST)) +		r->fn(dirfd, name, &st, data, r); + +	if (!r->maxdepth || r->depth + 1 < r->maxdepth) { +		fd = openat(dirfd, name, O_RDONLY | O_CLOEXEC | O_DIRECTORY); +		if (fd < 0) { +			weprintf("open %s:", r->path); +			recurse_status = 1; +		} +		if (!(dp = fdopendir(fd))) { +			if (!(r->flags & SILENT)) { +				weprintf("fdopendir:"); +				recurse_status = 1; +			} +			return; +		} +		if (r->path[pathlen - 1] != '/') +			r->path[pathlen++] = '/'; +		if (r->follow == 'H') +			flags |= AT_SYMLINK_NOFOLLOW; +		while ((d = readdir(dp))) { +			if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) +				continue; +			r->pathlen = pathlen + estrlcpy(r->path + pathlen, d->d_name, sizeof(r->path) - pathlen); +			if (fstatat(fd, d->d_name, &dst, flags) < 0) { +				if (!(r->flags & SILENT)) { +					weprintf("stat %s:", r->path); +					recurse_status = 1; +				} +			} else if ((r->flags & SAMEDEV) && dst.st_dev != st.st_dev) { +				continue; +			} else { +				r->depth++; +				r->fn(fd, d->d_name, &dst, data, r); +				r->depth--; +			} +		} +		r->path[pathlen - 1] = '\0'; +		r->pathlen = pathlen - 1; +		closedir(dp); +	} + +	if (!r->depth) { +		if (!(r->flags & DIRFIRST)) +			r->fn(dirfd, name, &st, data, r); + +		while (r->hist) { +			h = r->hist; +			r->hist = r->hist->prev; +			free(h); +		} +	} +} diff --git a/util/sbase/libutil/rm.c b/util/sbase/libutil/rm.c new file mode 100644 index 00000000..fb99840d --- /dev/null +++ b/util/sbase/libutil/rm.c @@ -0,0 +1,49 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> + +#include "../fs.h" +#include "../util.h" + +int rm_status = 0; + +void +rm(int dirfd, const char *name, struct stat *st, void *data, struct recursor *r) +{ +	int quiet, ask, write, flags, ignore; + +	ignore = r->flags & IGNORE; +	quiet = r->flags & SILENT; +	ask = r->flags & CONFIRM; +	write = faccessat(dirfd, name, W_OK, 0) == 0; +	flags = 0; + +	if (S_ISDIR(st->st_mode) && r->maxdepth) { +		errno = EISDIR; +		goto err; +	} + +	if (!quiet && (!write && isatty(0) || ask)) { +		if (!confirm("remove file '%s'", r->path)); +			return; +	} + +	if (S_ISDIR(st->st_mode)) { +		flags = AT_REMOVEDIR; +		recurse(dirfd, name, NULL, r); +	} + +	if (unlinkat(dirfd, name, flags) < 0) +		goto err; +	return; + +err: +	if (!ignore) { +		weprintf("cannot remove '%s':", r->path); +		rm_status = 1; +	} +} diff --git a/util/sbase/libutil/sha1.c b/util/sbase/libutil/sha1.c new file mode 100644 index 00000000..3d76a1be --- /dev/null +++ b/util/sbase/libutil/sha1.c @@ -0,0 +1,144 @@ +/* public domain sha1 implementation based on rfc3174 and libtomcrypt */ +#include <stdint.h> +#include <string.h> + +#include "../sha1.h" + +static uint32_t rol(uint32_t n, int k) { return (n << k) | (n >> (32-k)); } +#define F0(b,c,d) (d ^ (b & (c ^ d))) +#define F1(b,c,d) (b ^ c ^ d) +#define F2(b,c,d) ((b & c) | (d & (b | c))) +#define F3(b,c,d) (b ^ c ^ d) +#define G0(a,b,c,d,e,i) e += rol(a,5)+F0(b,c,d)+W[i]+0x5A827999; b = rol(b,30) +#define G1(a,b,c,d,e,i) e += rol(a,5)+F1(b,c,d)+W[i]+0x6ED9EBA1; b = rol(b,30) +#define G2(a,b,c,d,e,i) e += rol(a,5)+F2(b,c,d)+W[i]+0x8F1BBCDC; b = rol(b,30) +#define G3(a,b,c,d,e,i) e += rol(a,5)+F3(b,c,d)+W[i]+0xCA62C1D6; b = rol(b,30) + +static void +processblock(struct sha1 *s, const uint8_t *buf) +{ +	uint32_t W[80], a, b, c, d, e; +	int i; + +	for (i = 0; i < 16; i++) { +		W[i] = (uint32_t)buf[4*i]<<24; +		W[i] |= (uint32_t)buf[4*i+1]<<16; +		W[i] |= (uint32_t)buf[4*i+2]<<8; +		W[i] |= buf[4*i+3]; +	} +	for (; i < 80; i++) +		W[i] = rol(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16], 1); +	a = s->h[0]; +	b = s->h[1]; +	c = s->h[2]; +	d = s->h[3]; +	e = s->h[4]; +	for (i = 0; i < 20; ) { +		G0(a,b,c,d,e,i++); +		G0(e,a,b,c,d,i++); +		G0(d,e,a,b,c,i++); +		G0(c,d,e,a,b,i++); +		G0(b,c,d,e,a,i++); +	} +	while (i < 40) { +		G1(a,b,c,d,e,i++); +		G1(e,a,b,c,d,i++); +		G1(d,e,a,b,c,i++); +		G1(c,d,e,a,b,i++); +		G1(b,c,d,e,a,i++); +	} +	while (i < 60) { +		G2(a,b,c,d,e,i++); +		G2(e,a,b,c,d,i++); +		G2(d,e,a,b,c,i++); +		G2(c,d,e,a,b,i++); +		G2(b,c,d,e,a,i++); +	} +	while (i < 80) { +		G3(a,b,c,d,e,i++); +		G3(e,a,b,c,d,i++); +		G3(d,e,a,b,c,i++); +		G3(c,d,e,a,b,i++); +		G3(b,c,d,e,a,i++); +	} +	s->h[0] += a; +	s->h[1] += b; +	s->h[2] += c; +	s->h[3] += d; +	s->h[4] += e; +} + +static void +pad(struct sha1 *s) +{ +	unsigned r = s->len % 64; + +	s->buf[r++] = 0x80; +	if (r > 56) { +		memset(s->buf + r, 0, 64 - r); +		r = 0; +		processblock(s, s->buf); +	} +	memset(s->buf + r, 0, 56 - r); +	s->len *= 8; +	s->buf[56] = s->len >> 56; +	s->buf[57] = s->len >> 48; +	s->buf[58] = s->len >> 40; +	s->buf[59] = s->len >> 32; +	s->buf[60] = s->len >> 24; +	s->buf[61] = s->len >> 16; +	s->buf[62] = s->len >> 8; +	s->buf[63] = s->len; +	processblock(s, s->buf); +} + +void +sha1_init(void *ctx) +{ +	struct sha1 *s = ctx; + +	s->len = 0; +	s->h[0] = 0x67452301; +	s->h[1] = 0xEFCDAB89; +	s->h[2] = 0x98BADCFE; +	s->h[3] = 0x10325476; +	s->h[4] = 0xC3D2E1F0; +} + +void +sha1_sum(void *ctx, uint8_t md[SHA1_DIGEST_LENGTH]) +{ +	struct sha1 *s = ctx; +	int i; + +	pad(s); +	for (i = 0; i < 5; i++) { +		md[4*i] = s->h[i] >> 24; +		md[4*i+1] = s->h[i] >> 16; +		md[4*i+2] = s->h[i] >> 8; +		md[4*i+3] = s->h[i]; +	} +} + +void +sha1_update(void *ctx, const void *m, unsigned long len) +{ +	struct sha1 *s = ctx; +	const uint8_t *p = m; +	unsigned r = s->len % 64; + +	s->len += len; +	if (r) { +		if (len < 64 - r) { +			memcpy(s->buf + r, p, len); +			return; +		} +		memcpy(s->buf + r, p, 64 - r); +		len -= 64 - r; +		p += 64 - r; +		processblock(s, s->buf); +	} +	for (; len >= 64; len -= 64, p += 64) +		processblock(s, p); +	memcpy(s->buf, p, len); +} diff --git a/util/sbase/libutil/sha224.c b/util/sbase/libutil/sha224.c new file mode 100644 index 00000000..fce520f5 --- /dev/null +++ b/util/sbase/libutil/sha224.c @@ -0,0 +1,26 @@ +/* public domain sha224 implementation based on fips180-3 */ +#include <stdint.h> +#include "../sha224.h" + +extern void sha256_sum_n(void *, uint8_t *, int n); + +void +sha224_init(void *ctx) +{ +	struct sha224 *s = ctx; +	s->len = 0; +	s->h[0] = 0xc1059ed8; +	s->h[1] = 0x367cd507; +	s->h[2] = 0x3070dd17; +	s->h[3] = 0xf70e5939; +	s->h[4] = 0xffc00b31; +	s->h[5] = 0x68581511; +	s->h[6] = 0x64f98fa7; +	s->h[7] = 0xbefa4fa4; +} + +void +sha224_sum(void *ctx, uint8_t md[SHA224_DIGEST_LENGTH]) +{ +	sha256_sum_n(ctx, md, 8); +} diff --git a/util/sbase/libutil/sha256.c b/util/sbase/libutil/sha256.c new file mode 100644 index 00000000..266cfecb --- /dev/null +++ b/util/sbase/libutil/sha256.c @@ -0,0 +1,154 @@ +/* public domain sha256 implementation based on fips180-3 */ +#include <ctype.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../sha256.h" + +static uint32_t ror(uint32_t n, int k) { return (n >> k) | (n << (32-k)); } +#define Ch(x,y,z)  (z ^ (x & (y ^ z))) +#define Maj(x,y,z) ((x & y) | (z & (x | y))) +#define S0(x)      (ror(x,2) ^ ror(x,13) ^ ror(x,22)) +#define S1(x)      (ror(x,6) ^ ror(x,11) ^ ror(x,25)) +#define R0(x)      (ror(x,7) ^ ror(x,18) ^ (x>>3)) +#define R1(x)      (ror(x,17) ^ ror(x,19) ^ (x>>10)) + +static const uint32_t K[64] = { +0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, +0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, +0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, +0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, +0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, +0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, +0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, +0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +static void +processblock(struct sha256 *s, const uint8_t *buf) +{ +	uint32_t W[64], t1, t2, a, b, c, d, e, f, g, h; +	int i; + +	for (i = 0; i < 16; i++) { +		W[i] = (uint32_t)buf[4*i]<<24; +		W[i] |= (uint32_t)buf[4*i+1]<<16; +		W[i] |= (uint32_t)buf[4*i+2]<<8; +		W[i] |= buf[4*i+3]; +	} +	for (; i < 64; i++) +		W[i] = R1(W[i-2]) + W[i-7] + R0(W[i-15]) + W[i-16]; +	a = s->h[0]; +	b = s->h[1]; +	c = s->h[2]; +	d = s->h[3]; +	e = s->h[4]; +	f = s->h[5]; +	g = s->h[6]; +	h = s->h[7]; +	for (i = 0; i < 64; i++) { +		t1 = h + S1(e) + Ch(e,f,g) + K[i] + W[i]; +		t2 = S0(a) + Maj(a,b,c); +		h = g; +		g = f; +		f = e; +		e = d + t1; +		d = c; +		c = b; +		b = a; +		a = t1 + t2; +	} +	s->h[0] += a; +	s->h[1] += b; +	s->h[2] += c; +	s->h[3] += d; +	s->h[4] += e; +	s->h[5] += f; +	s->h[6] += g; +	s->h[7] += h; +} + +static void +pad(struct sha256 *s) +{ +	unsigned r = s->len % 64; + +	s->buf[r++] = 0x80; +	if (r > 56) { +		memset(s->buf + r, 0, 64 - r); +		r = 0; +		processblock(s, s->buf); +	} +	memset(s->buf + r, 0, 56 - r); +	s->len *= 8; +	s->buf[56] = s->len >> 56; +	s->buf[57] = s->len >> 48; +	s->buf[58] = s->len >> 40; +	s->buf[59] = s->len >> 32; +	s->buf[60] = s->len >> 24; +	s->buf[61] = s->len >> 16; +	s->buf[62] = s->len >> 8; +	s->buf[63] = s->len; +	processblock(s, s->buf); +} + +void +sha256_init(void *ctx) +{ +	struct sha256 *s = ctx; +	s->len = 0; +	s->h[0] = 0x6a09e667; +	s->h[1] = 0xbb67ae85; +	s->h[2] = 0x3c6ef372; +	s->h[3] = 0xa54ff53a; +	s->h[4] = 0x510e527f; +	s->h[5] = 0x9b05688c; +	s->h[6] = 0x1f83d9ab; +	s->h[7] = 0x5be0cd19; +} + +void +sha256_sum_n(void *ctx, uint8_t *md, int n) +{ +	struct sha256 *s = ctx; +	int i; + +	pad(s); +	for (i = 0; i < n; i++) { +		md[4*i] = s->h[i] >> 24; +		md[4*i+1] = s->h[i] >> 16; +		md[4*i+2] = s->h[i] >> 8; +		md[4*i+3] = s->h[i]; +	} +} + +void +sha256_sum(void *ctx, uint8_t md[SHA256_DIGEST_LENGTH]) +{ +	sha256_sum_n(ctx, md, 8); +} + +void +sha256_update(void *ctx, const void *m, unsigned long len) +{ +	struct sha256 *s = ctx; +	const uint8_t *p = m; +	unsigned r = s->len % 64; + +	s->len += len; +	if (r) { +		if (len < 64 - r) { +			memcpy(s->buf + r, p, len); +			return; +		} +		memcpy(s->buf + r, p, 64 - r); +		len -= 64 - r; +		p += 64 - r; +		processblock(s, s->buf); +	} +	for (; len >= 64; len -= 64, p += 64) +		processblock(s, p); +	memcpy(s->buf, p, len); +} diff --git a/util/sbase/libutil/sha384.c b/util/sbase/libutil/sha384.c new file mode 100644 index 00000000..0a0e7777 --- /dev/null +++ b/util/sbase/libutil/sha384.c @@ -0,0 +1,26 @@ +/* public domain sha384 implementation based on fips180-3 */ +#include <stdint.h> +#include "../sha384.h" + +extern void sha512_sum_n(void *, uint8_t *, int n); + +void +sha384_init(void *ctx) +{ +	struct sha384 *s = ctx; +	s->len = 0; +	s->h[0] = 0xcbbb9d5dc1059ed8ULL; +	s->h[1] = 0x629a292a367cd507ULL; +	s->h[2] = 0x9159015a3070dd17ULL; +	s->h[3] = 0x152fecd8f70e5939ULL; +	s->h[4] = 0x67332667ffc00b31ULL; +	s->h[5] = 0x8eb44a8768581511ULL; +	s->h[6] = 0xdb0c2e0d64f98fa7ULL; +	s->h[7] = 0x47b5481dbefa4fa4ULL; +} + +void +sha384_sum(void *ctx, uint8_t md[SHA384_DIGEST_LENGTH]) +{ +	sha512_sum_n(ctx, md, 6); +} diff --git a/util/sbase/libutil/sha512-224.c b/util/sbase/libutil/sha512-224.c new file mode 100644 index 00000000..a5636c13 --- /dev/null +++ b/util/sbase/libutil/sha512-224.c @@ -0,0 +1,26 @@ +/* public domain sha512/224 implementation based on fips180-3 */ +#include <stdint.h> +#include "../sha512-224.h" + +extern void sha512_sum_n(void *, uint8_t *, int n); + +void +sha512_224_init(void *ctx) +{ +	struct sha512_224 *s = ctx; +	s->len = 0; +	s->h[0] = 0x8c3d37c819544da2ULL; +	s->h[1] = 0x73e1996689dcd4d6ULL; +	s->h[2] = 0x1dfab7ae32ff9c82ULL; +	s->h[3] = 0x679dd514582f9fcfULL; +	s->h[4] = 0x0f6d2b697bd44da8ULL; +	s->h[5] = 0x77e36f7304c48942ULL; +	s->h[6] = 0x3f9d85a86a1d36c8ULL; +	s->h[7] = 0x1112e6ad91d692a1ULL; +} + +void +sha512_224_sum(void *ctx, uint8_t md[SHA512_224_DIGEST_LENGTH]) +{ +	sha512_sum_n(ctx, md, 4); +} diff --git a/util/sbase/libutil/sha512-256.c b/util/sbase/libutil/sha512-256.c new file mode 100644 index 00000000..d4b84495 --- /dev/null +++ b/util/sbase/libutil/sha512-256.c @@ -0,0 +1,26 @@ +/* public domain sha512/256 implementation based on fips180-3 */ +#include <stdint.h> +#include "../sha512-256.h" + +extern void sha512_sum_n(void *, uint8_t *, int n); + +void +sha512_256_init(void *ctx) +{ +	struct sha512_256 *s = ctx; +	s->len = 0; +	s->h[0] = 0x22312194fc2bf72cULL; +	s->h[1] = 0x9f555fa3c84c64c2ULL; +	s->h[2] = 0x2393b86b6f53b151ULL; +	s->h[3] = 0x963877195940eabdULL; +	s->h[4] = 0x96283ee2a88effe3ULL; +	s->h[5] = 0xbe5e1e2553863992ULL; +	s->h[6] = 0x2b0199fc2c85b8aaULL; +	s->h[7] = 0x0eb72ddc81c52ca2ULL; +} + +void +sha512_256_sum(void *ctx, uint8_t md[SHA512_256_DIGEST_LENGTH]) +{ +	sha512_sum_n(ctx, md, 4); +} diff --git a/util/sbase/libutil/sha512.c b/util/sbase/libutil/sha512.c new file mode 100644 index 00000000..25264c78 --- /dev/null +++ b/util/sbase/libutil/sha512.c @@ -0,0 +1,175 @@ +/* public domain sha256 implementation based on fips180-3 */ + +#include <ctype.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../sha512.h" + +static uint64_t ror(uint64_t n, int k) { return (n >> k) | (n << (64-k)); } +#define Ch(x,y,z)  (z ^ (x & (y ^ z))) +#define Maj(x,y,z) ((x & y) | (z & (x | y))) +#define S0(x)      (ror(x,28) ^ ror(x,34) ^ ror(x,39)) +#define S1(x)      (ror(x,14) ^ ror(x,18) ^ ror(x,41)) +#define R0(x)      (ror(x,1) ^ ror(x,8) ^ (x>>7)) +#define R1(x)      (ror(x,19) ^ ror(x,61) ^ (x>>6)) + +static const uint64_t K[80] = { +0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, +0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, +0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, +0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, +0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, +0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, +0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, +0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, +0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, +0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, +0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, +0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, +0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, +0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, +0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, +0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, +0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, +0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, +0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, +0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL +}; + +static void +processblock(struct sha512 *s, const uint8_t *buf) +{ +	uint64_t W[80], t1, t2, a, b, c, d, e, f, g, h; +	int i; + +	for (i = 0; i < 16; i++) { +		W[i] = (uint64_t)buf[8*i]<<56; +		W[i] |= (uint64_t)buf[8*i+1]<<48; +		W[i] |= (uint64_t)buf[8*i+2]<<40; +		W[i] |= (uint64_t)buf[8*i+3]<<32; +		W[i] |= (uint64_t)buf[8*i+4]<<24; +		W[i] |= (uint64_t)buf[8*i+5]<<16; +		W[i] |= (uint64_t)buf[8*i+6]<<8; +		W[i] |= buf[8*i+7]; +	} +	for (; i < 80; i++) +		W[i] = R1(W[i-2]) + W[i-7] + R0(W[i-15]) + W[i-16]; +	a = s->h[0]; +	b = s->h[1]; +	c = s->h[2]; +	d = s->h[3]; +	e = s->h[4]; +	f = s->h[5]; +	g = s->h[6]; +	h = s->h[7]; +	for (i = 0; i < 80; i++) { +		t1 = h + S1(e) + Ch(e,f,g) + K[i] + W[i]; +		t2 = S0(a) + Maj(a,b,c); +		h = g; +		g = f; +		f = e; +		e = d + t1; +		d = c; +		c = b; +		b = a; +		a = t1 + t2; +	} +	s->h[0] += a; +	s->h[1] += b; +	s->h[2] += c; +	s->h[3] += d; +	s->h[4] += e; +	s->h[5] += f; +	s->h[6] += g; +	s->h[7] += h; +} + +static void +pad(struct sha512 *s) +{ +	unsigned r = s->len % 128; + +	s->buf[r++] = 0x80; +	if (r > 112) { +		memset(s->buf + r, 0, 128 - r); +		r = 0; +		processblock(s, s->buf); +	} +	memset(s->buf + r, 0, 120 - r); +	s->len *= 8; +	s->buf[120] = s->len >> 56; +	s->buf[121] = s->len >> 48; +	s->buf[122] = s->len >> 40; +	s->buf[123] = s->len >> 32; +	s->buf[124] = s->len >> 24; +	s->buf[125] = s->len >> 16; +	s->buf[126] = s->len >> 8; +	s->buf[127] = s->len; +	processblock(s, s->buf); +} + +void +sha512_init(void *ctx) +{ +	struct sha512 *s = ctx; +	s->len = 0; +	s->h[0] = 0x6a09e667f3bcc908ULL; +	s->h[1] = 0xbb67ae8584caa73bULL; +	s->h[2] = 0x3c6ef372fe94f82bULL; +	s->h[3] = 0xa54ff53a5f1d36f1ULL; +	s->h[4] = 0x510e527fade682d1ULL; +	s->h[5] = 0x9b05688c2b3e6c1fULL; +	s->h[6] = 0x1f83d9abfb41bd6bULL; +	s->h[7] = 0x5be0cd19137e2179ULL; +} + +void +sha512_sum_n(void *ctx, uint8_t *md, int n) +{ +	struct sha512 *s = ctx; +	int i; + +	pad(s); +	for (i = 0; i < n; i++) { +		md[8*i] = s->h[i] >> 56; +		md[8*i+1] = s->h[i] >> 48; +		md[8*i+2] = s->h[i] >> 40; +		md[8*i+3] = s->h[i] >> 32; +		md[8*i+4] = s->h[i] >> 24; +		md[8*i+5] = s->h[i] >> 16; +		md[8*i+6] = s->h[i] >> 8; +		md[8*i+7] = s->h[i]; +	} +} + +void +sha512_sum(void *ctx, uint8_t md[SHA512_DIGEST_LENGTH]) +{ +	sha512_sum_n(ctx, md, 8); +} + +void +sha512_update(void *ctx, const void *m, unsigned long len) +{ +	struct sha512 *s = ctx; +	const uint8_t *p = m; +	unsigned r = s->len % 128; + +	s->len += len; +	if (r) { +		if (len < 128 - r) { +			memcpy(s->buf + r, p, len); +			return; +		} +		memcpy(s->buf + r, p, 128 - r); +		len -= 128 - r; +		p += 128 - r; +		processblock(s, s->buf); +	} +	for (; len >= 128; len -= 128, p += 128) +		processblock(s, p); +	memcpy(s->buf, p, len); +} diff --git a/util/sbase/libutil/strcasestr.c b/util/sbase/libutil/strcasestr.c new file mode 100644 index 00000000..26eb6bbd --- /dev/null +++ b/util/sbase/libutil/strcasestr.c @@ -0,0 +1,38 @@ +/* + * Copyright 2005-2014 Rich Felker, et al. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include <string.h> +#include <strings.h> + +#include "../util.h" + +char * +strcasestr(const char *h, const char *n) +{ +	size_t l = strlen(n); + +	for (; *h; h++) +		if (!strncasecmp(h, n, l)) +			return (char *)h; + +	return 0; +} diff --git a/util/sbase/libutil/strlcat.c b/util/sbase/libutil/strlcat.c new file mode 100644 index 00000000..bf263b87 --- /dev/null +++ b/util/sbase/libutil/strlcat.c @@ -0,0 +1,63 @@ +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <string.h> +#include <sys/types.h> + +#include "../util.h" + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t +strlcat(char *dst, const char *src, size_t siz) +{ +	char *d = dst; +	const char *s = src; +	size_t n = siz; +	size_t dlen; +	/* Find the end of dst and adjust bytes left but don't go past end */ +	while (n-- != 0 && *d != '\0') +		d++; +	dlen = d - dst; +	n = siz - dlen; +	if (n == 0) +		return(dlen + strlen(s)); +	while (*s != '\0') { +		if (n != 1) { +			*d++ = *s; +			n--; +		} +		s++; +	} +	*d = '\0'; +	return(dlen + (s - src)); /* count does not include NUL */ +} + +size_t +estrlcat(char *dst, const char *src, size_t siz) +{ +	size_t ret; + +	if ((ret = strlcat(dst, src, siz)) >= siz) +		eprintf("strlcat: input string too long\n"); + +	return ret; +} diff --git a/util/sbase/libutil/strlcpy.c b/util/sbase/libutil/strlcpy.c new file mode 100644 index 00000000..44b618a0 --- /dev/null +++ b/util/sbase/libutil/strlcpy.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <string.h> +#include <sys/types.h> + +#include "../util.h" + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ +	char *d = dst; +	const char *s = src; +	size_t n = siz; +	/* Copy as many bytes as will fit */ +	if (n != 0) { +		while (--n != 0) { +			if ((*d++ = *s++) == '\0') +				break; +		} +	} +	/* Not enough room in dst, add NUL and traverse rest of src */ +	if (n == 0) { +		if (siz != 0) +			*d = '\0'; /* NUL-terminate dst */ +		while (*s++) +			; +	} +	return(s - src - 1); /* count does not include NUL */ +} + +size_t +estrlcpy(char *dst, const char *src, size_t siz) +{ +	size_t ret; + +	if ((ret = strlcpy(dst, src, siz)) >= siz) +		eprintf("strlcpy: input string too long\n"); + +	return ret; +} diff --git a/util/sbase/libutil/strnsubst.c b/util/sbase/libutil/strnsubst.c new file mode 100644 index 00000000..2da54aba --- /dev/null +++ b/util/sbase/libutil/strnsubst.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2002 J. Mallett.  All rights reserved. + * You may do whatever you want with this file as long as + * the above copyright and this notice remain intact, along + * with the following statement: + * 	For the man who taught me vi, and who got too old, too young. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../util.h" + +/* + * Replaces str with a string consisting of str with match replaced with + * replstr as many times as can be done before the constructed string is + * maxsize bytes large.  It does not free the string pointed to by str, it + * is up to the calling program to be sure that the original contents of + * str as well as the new contents are handled in an appropriate manner. + * If replstr is NULL, then that internally is changed to a nil-string, so + * that we can still pretend to do somewhat meaningful substitution. + * No value is returned. + */ +void +strnsubst(char **str, const char *match, const char *replstr, size_t maxsize) +{ +	char *s1, *s2, *this; +	size_t matchlen, s2len; +	int n; + +	if ((s1 = *str) == NULL) +		return; +	s2 = emalloc(maxsize); + +	if (replstr == NULL) +		replstr = ""; + +	if (match == NULL || *match == '\0' || strlen(s1) >= maxsize) { +		strlcpy(s2, s1, maxsize); +		goto done; +	} + +	*s2 = '\0'; +	s2len = 0; +	matchlen = strlen(match); +	for (;;) { +		if ((this = strstr(s1, match)) == NULL) +			break; +		n = snprintf(s2 + s2len, maxsize - s2len, "%.*s%s", +		    (int)(this - s1), s1, replstr); +		if (n == -1 || n + s2len + strlen(this + matchlen) >= maxsize) +			break;			/* out of room */ +		s2len += n; +		s1 = this + matchlen; +	} +	strlcpy(s2 + s2len, s1, maxsize - s2len); +done: +	*str = s2; +	return; +} diff --git a/util/sbase/libutil/strsep.c b/util/sbase/libutil/strsep.c new file mode 100644 index 00000000..d9f06444 --- /dev/null +++ b/util/sbase/libutil/strsep.c @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2014 Rich Felker, et al. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include <string.h> + +#include "../util.h" + +char * +strsep(char **str, const char *sep) +{ +	char *s = *str, *end; +	if (!s) return NULL; +	end = s + strcspn(s, sep); +	if (*end) *end++ = 0; +	else end = 0; +	*str = end; +	return s; +} diff --git a/util/sbase/libutil/strtonum.c b/util/sbase/libutil/strtonum.c new file mode 100644 index 00000000..c0ac401f --- /dev/null +++ b/util/sbase/libutil/strtonum.c @@ -0,0 +1,85 @@ +/*	$OpenBSD: strtonum.c,v 1.7 2013/04/17 18:40:58 tedu Exp $	*/ + +/* + * Copyright (c) 2004 Ted Unangst and Todd Miller + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <errno.h> +#include <limits.h> +#include <stdlib.h> + +#include "../util.h" + +#define	INVALID		1 +#define	TOOSMALL	2 +#define	TOOLARGE	3 + +long long +strtonum(const char *numstr, long long minval, long long maxval, +         const char **errstrp) +{ +	long long ll = 0; +	int error = 0; +	char *ep; +	struct errval { +		const char *errstr; +		int err; +	} ev[4] = { +		{ NULL,		0 }, +		{ "invalid",	EINVAL }, +		{ "too small",	ERANGE }, +		{ "too large",	ERANGE }, +	}; + +	ev[0].err = errno; +	errno = 0; +	if (minval > maxval) { +		error = INVALID; +	} else { +		ll = strtoll(numstr, &ep, 10); +		if (numstr == ep || *ep != '\0') +			error = INVALID; +		else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) +			error = TOOSMALL; +		else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) +			error = TOOLARGE; +	} +	if (errstrp != NULL) +		*errstrp = ev[error].errstr; +	errno = ev[error].err; +	if (error) +		ll = 0; + +	return (ll); +} + +long long +enstrtonum(int status, const char *numstr, long long minval, long long maxval) +{ +	const char *errstr; +	long long ll; + +	ll = strtonum(numstr, minval, maxval, &errstr); +	if (errstr) +		enprintf(status, "strtonum %s: %s\n", numstr, errstr); +	return ll; +} + +long long +estrtonum(const char *numstr, long long minval, long long maxval) +{ +	return enstrtonum(1, numstr, minval, maxval); +} diff --git a/util/sbase/libutil/unescape.c b/util/sbase/libutil/unescape.c new file mode 100644 index 00000000..b8f75ca9 --- /dev/null +++ b/util/sbase/libutil/unescape.c @@ -0,0 +1,58 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <string.h> + +#include "../util.h" + +#define is_odigit(c)  ('0' <= c && c <= '7') + +size_t +unescape(char *s) +{ +	static const char escapes[256] = { +		['"'] = '"', +		['\''] = '\'', +		['\\'] = '\\', +		['a'] = '\a', +		['b'] = '\b', +		['E'] = 033, +		['e'] = 033, +		['f'] = '\f', +		['n'] = '\n', +		['r'] = '\r', +		['t'] = '\t', +		['v'] = '\v' +	}; +	size_t m, q; +	char *r, *w; + +	for (r = w = s; *r;) { +		if (*r != '\\') { +			*w++ = *r++; +			continue; +		} +		r++; +		if (!*r) { +			eprintf("null escape sequence\n"); +		} else if (escapes[(unsigned char)*r]) { +			*w++ = escapes[(unsigned char)*r++]; +		} else if (is_odigit(*r)) { +			for (q = 0, m = 3; m && is_odigit(*r); m--, r++) +				q = q * 8 + (*r - '0'); +			*w++ = MIN(q, 255); +		} else if (*r == 'x' && isxdigit(r[1])) { +			r++; +			for (q = 0, m = 2; m && isxdigit(*r); m--, r++) +				if (isdigit(*r)) +					q = q * 16 + (*r - '0'); +				else +					q = q * 16 + (tolower(*r) - 'a' + 10); +			*w++ = q; +		} else { +			eprintf("invalid escape sequence '\\%c'\n", *r); +		} +	} +	*w = '\0'; + +	return w - s; +} diff --git a/util/sbase/libutil/writeall.c b/util/sbase/libutil/writeall.c new file mode 100644 index 00000000..4725ced8 --- /dev/null +++ b/util/sbase/libutil/writeall.c @@ -0,0 +1,21 @@ +/* See LICENSE file for copyright and license details. */ +#include <unistd.h> + +#include "../util.h" + +ssize_t +writeall(int fd, const void *buf, size_t len) +{ +	const char *p = buf; +	ssize_t n; + +	while (len) { +		n = write(fd, p, len); +		if (n <= 0) +			return n; +		p += n; +		len -= n; +	} + +	return p - (const char *)buf; +} diff --git a/util/sbase/link.1 b/util/sbase/link.1 new file mode 100644 index 00000000..915b9d80 --- /dev/null +++ b/util/sbase/link.1 @@ -0,0 +1,16 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/link.c b/util/sbase/link.c new file mode 100644 index 00000000..7cee4d0f --- /dev/null +++ b/util/sbase/link.c @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s target name\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	if (argc != 2) +		usage(); + +	if (link(argv[0], argv[1]) < 0) +		eprintf("link:"); + +	return 0; +} diff --git a/util/sbase/ln.1 b/util/sbase/ln.1 new file mode 100644 index 00000000..057b5a09 --- /dev/null +++ b/util/sbase/ln.1 @@ -0,0 +1,61 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/ln.c b/util/sbase/ln.c new file mode 100644 index 00000000..f62068a1 --- /dev/null +++ b/util/sbase/ln.c @@ -0,0 +1,103 @@ +/* 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) { +				if (!fflag) { +					weprintf("%s and %s/%s are the same file\n", +							*argv, targetdir, target); +					ret = 1; +				} +				continue; +			} +		} + +		if (fflag && unlinkat(dirfd, target, 0) < 0 && errno != ENOENT) { +			weprintf("unlinkat %s %s:", targetdir, target); +			ret = 1; +			continue; +		} +		if ((sflag ? symlinkat(*argv, dirfd, target) : +		             linkat(AT_FDCWD, *argv, dirfd, target, flags)) < 0) { +			weprintf("%s %s <- %s/%s:", sflag ? "symlinkat" : "linkat", +			         *argv, targetdir, target); +			ret = 1; +		} +	} + +	return ret; +} diff --git a/util/sbase/logger.1 b/util/sbase/logger.1 new file mode 100644 index 00000000..4624a163 --- /dev/null +++ b/util/sbase/logger.1 @@ -0,0 +1,52 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +.Pp +The +.Op Fl ipst +flags are an extensions to that specification. diff --git a/util/sbase/logger.c b/util/sbase/logger.c new file mode 100644 index 00000000..603da04f --- /dev/null +++ b/util/sbase/logger.c @@ -0,0 +1,91 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#define SYSLOG_NAMES +#include <syslog.h> +#include <unistd.h> + +#include "util.h" + +static int +decodetable(CODE *table, char *name) +{ +	CODE *c; + +	for (c = table; c->c_name; c++) +		if (!strcasecmp(name, c->c_name)) +			return c->c_val; +	eprintf("invalid priority name: %s\n", name); + +	return -1; /* not reached */ +} + +static int +decodepri(char *pri) +{ +	char *lev, *fac = pri; + +	if (!(lev = strchr(pri, '.'))) +		eprintf("invalid priority name: %s\n", pri); +	*lev++ = '\0'; +	if (!*lev) +		eprintf("invalid priority name: %s\n", pri); + +	return (decodetable(facilitynames, fac) & LOG_FACMASK) | +	       (decodetable(prioritynames, lev) & LOG_PRIMASK); +} + +static void +usage(void) +{ +	eprintf("usage: %s [-is] [-p priority] [-t tag] [message ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	size_t sz; +	int logflags = 0, priority = LOG_NOTICE, i; +	char *buf = NULL, *tag = NULL; + +	ARGBEGIN { +	case 'i': +		logflags |= LOG_PID; +		break; +	case 'p': +		priority = decodepri(EARGF(usage())); +		break; +	case 's': +		logflags |= LOG_PERROR; +		break; +	case 't': +		tag = EARGF(usage()); +		break; +	default: +		usage(); +	} ARGEND + +	openlog(tag ? tag : getlogin(), logflags, 0); + +	if (!argc) { +		while (getline(&buf, &sz, stdin) > 0) +			syslog(priority, "%s", buf); +	} else { +		for (i = 0, sz = 0; i < argc; i++) +			sz += strlen(argv[i]); +		sz += argc; +		buf = ecalloc(1, sz); +		for (i = 0; i < argc; i++) { +			estrlcat(buf, argv[i], sz); +			if (i + 1 < argc) +				estrlcat(buf, " ", sz); +		} +		syslog(priority, "%s", buf); +	} + +	closelog(); + +	return fshut(stdin, "<stdin>"); +} diff --git a/util/sbase/logname.1 b/util/sbase/logname.1 new file mode 100644 index 00000000..1c1f16fd --- /dev/null +++ b/util/sbase/logname.1 @@ -0,0 +1,13 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/logname.c b/util/sbase/logname.c new file mode 100644 index 00000000..8eb8eea5 --- /dev/null +++ b/util/sbase/logname.c @@ -0,0 +1,29 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	char *login; + +	argv0 = *argv, argv0 ? (argc--, argv++) : (void *)0; + +	if (argc) +		usage(); + +	if ((login = getlogin())) +		puts(login); +	else +		eprintf("no login name\n"); + +	return fshut(stdout, "<stdout>"); +} diff --git a/util/sbase/ls.1 b/util/sbase/ls.1 new file mode 100644 index 00000000..26a41e62 --- /dev/null +++ b/util/sbase/ls.1 @@ -0,0 +1,96 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +Except for the +.Op Fl Ckmpsx +flags. +.Pp +The +.Op Fl hU +flags are an extension to that specification. diff --git a/util/sbase/ls.c b/util/sbase/ls.c new file mode 100644 index 00000000..aa95fef2 --- /dev/null +++ b/util/sbase/ls.c @@ -0,0 +1,489 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> +#include <sys/types.h> +#ifndef major +#include <sys/sysmacros.h> +#endif + +#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 int showdirs; + +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 +printname(const char *name) +{ +	const char *c; +	Rune r; +	size_t l; + +	for (c = name; *c; c += l) { +		l = chartorune(&r, c); +		if (!qflag || isprintrune(r)) +			fwrite(c, 1, l, stdout); +		else +			putchar('?'); +	} +} + +static void +output(const struct entry *ent) +{ +	struct group *gr; +	struct passwd *pw; +	struct tm *tm; +	ssize_t len; +	char *fmt, buf[BUFSIZ], pwname[_SC_LOGIN_NAME_MAX], +	     grname[_SC_LOGIN_NAME_MAX], mode[] = "----------"; + +	if (iflag) +		printf("%lu ", (unsigned long)ent->ino); +	if (!lflag) { +		printname(ent->name); +		puts(indicator(ent->mode)); +		return; +	} +	if (S_ISREG(ent->mode)) +		mode[0] = '-'; +	else if (S_ISBLK(ent->mode)) +		mode[0] = 'b'; +	else if (S_ISCHR(ent->mode)) +		mode[0] = 'c'; +	else if (S_ISDIR(ent->mode)) +		mode[0] = 'd'; +	else if (S_ISFIFO(ent->mode)) +		mode[0] = 'p'; +	else if (S_ISLNK(ent->mode)) +		mode[0] = 'l'; +	else if (S_ISSOCK(ent->mode)) +		mode[0] = 's'; +	else +		mode[0] = '?'; + +	if (ent->mode & S_IRUSR) mode[1] = 'r'; +	if (ent->mode & S_IWUSR) mode[2] = 'w'; +	if (ent->mode & S_IXUSR) mode[3] = 'x'; +	if (ent->mode & S_IRGRP) mode[4] = 'r'; +	if (ent->mode & S_IWGRP) mode[5] = 'w'; +	if (ent->mode & S_IXGRP) mode[6] = 'x'; +	if (ent->mode & S_IROTH) mode[7] = 'r'; +	if (ent->mode & S_IWOTH) mode[8] = 'w'; +	if (ent->mode & S_IXOTH) mode[9] = 'x'; + +	if (ent->mode & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S'; +	if (ent->mode & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S'; +	if (ent->mode & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T'; + +	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"; + +	if ((tm = localtime(&ent->t.tv_sec))) +		strftime(buf, sizeof(buf), fmt, tm); +	else +		snprintf(buf, sizeof(buf), "%lld", (long long)(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 ", buf); +	printname(ent->name); +	fputs(indicator(ent->mode), stdout); +	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'); +} + +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 (path[0] || showdirs) { +		fputs(path, stdout); +		printname(dir->name); +		puts(":"); +	} +	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, 1); +		} +	} + +	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 = 0; +		else +			putchar('\n'); + +		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, ds, 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) && +		     !(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) && +			     !(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)); +			} +		} + +		showdirs = ds > 1 || (ds && fs); + +		qsort(fents, fs, sizeof(ent), entcmp); +		qsort(dents, ds, sizeof(ent), entcmp); + +		for (i = 0; i < fs; ++i) +			ls("", &fents[i], 0); +		free(fents); +		if (fs && ds) +			putchar('\n'); +		for (i = 0; i < ds; ++i) +			ls("", &dents[i], 1); +		free(dents); +	} + +	return (fshut(stdout, "<stdout>") | ret); +} diff --git a/util/sbase/md5.h b/util/sbase/md5.h new file mode 100644 index 00000000..0b5005e9 --- /dev/null +++ b/util/sbase/md5.h @@ -0,0 +1,18 @@ +/* public domain md5 implementation based on rfc1321 and libtomcrypt */ + +struct md5 { +	uint64_t len;    /* processed message length */ +	uint32_t h[4];   /* hash state */ +	uint8_t buf[64]; /* message block buffer */ +}; + +enum { MD5_DIGEST_LENGTH = 16 }; + +/* reset state */ +void md5_init(void *ctx); +/* process message */ +void md5_update(void *ctx, const void *m, unsigned long len); +/* get message digest */ +/* state is ruined after sum, keep a copy if multiple sum is needed */ +/* part of the message might be left in s, zero it if secrecy is needed */ +void md5_sum(void *ctx, uint8_t md[MD5_DIGEST_LENGTH]); diff --git a/util/sbase/md5sum.1 b/util/sbase/md5sum.1 new file mode 100644 index 00000000..79a37cfe --- /dev/null +++ b/util/sbase/md5sum.1 @@ -0,0 +1,32 @@ +.Dd October 8, 2015 +.Dt MD5SUM 1 +.Os sbase +.Sh NAME +.Nm md5sum +.Nd compute or check MD5 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes MD5 (128-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of MD5 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/util/sbase/md5sum.c b/util/sbase/md5sum.c new file mode 100644 index 00000000..224b20ed --- /dev/null +++ b/util/sbase/md5sum.c @@ -0,0 +1,46 @@ +/* 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 'b': +	case 't': +		/* ignore */ +		break; +	case 'c': +		cryptfunc = cryptcheck; +		break; +	default: +		usage(); +	} ARGEND + +	ret |= cryptfunc(argc, argv, &md5_ops, md, sizeof(md)); +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/mkdir.1 b/util/sbase/mkdir.1 new file mode 100644 index 00000000..ec842d5b --- /dev/null +++ b/util/sbase/mkdir.1 @@ -0,0 +1,34 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/mkdir.c b/util/sbase/mkdir.c new file mode 100644 index 00000000..3e20b1ae --- /dev/null +++ b/util/sbase/mkdir.c @@ -0,0 +1,49 @@ +/* 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, mask; +	int pflag = 0, ret = 0; + +	mask = umask(0); +	mode = 0777 & ~mask; + +	ARGBEGIN { +	case 'p': +		pflag = 1; +		break; +	case 'm': +		mode = parsemode(EARGF(usage()), 0777, mask); +		break; +	default: +		usage(); +	} ARGEND + +	if (!argc) +		usage(); + +	for (; *argv; argc--, argv++) { +		if (pflag) { +			if (mkdirp(*argv, mode, 0777 & (~mask | 0300)) < 0) +				ret = 1; +		} else if (mkdir(*argv, mode) < 0) { +			weprintf("mkdir %s:", *argv); +			ret = 1; +		} +	} + +	return ret; +} diff --git a/util/sbase/mkfifo.1 b/util/sbase/mkfifo.1 new file mode 100644 index 00000000..58b724aa --- /dev/null +++ b/util/sbase/mkfifo.1 @@ -0,0 +1,28 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/mkfifo.c b/util/sbase/mkfifo.c new file mode 100644 index 00000000..2470a09d --- /dev/null +++ b/util/sbase/mkfifo.c @@ -0,0 +1,39 @@ +/* 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 = 0666; +	int ret = 0; + +	ARGBEGIN { +	case 'm': +		mode = parsemode(EARGF(usage()), mode, umask(0)); +		break; +	default: +		usage(); +	} ARGEND + +	if (!argc) +		usage(); + +	for (; *argv; argc--, argv++) { +		if (mkfifo(*argv, mode) < 0) { +			weprintf("mkfifo %s:", *argv); +			ret = 1; +		} +	} + +	return ret; +} diff --git a/util/sbase/mknod.1 b/util/sbase/mknod.1 new file mode 100644 index 00000000..1206549c --- /dev/null +++ b/util/sbase/mknod.1 @@ -0,0 +1,44 @@ +.Dd February 2, 2015 +.Dt MKNOD 1 +.Os sbase +.Sh NAME +.Nm mknod +.Nd create a special device file +.Sh SYNOPSIS +.Nm +.Op Fl m Ar mode +.Ar name +.Cm b Ns | Ns Cm c Ns | Ns Cm u +.Ar major +.Ar minor +.Nm +.Op Fl m Ar mode +.Ar name +.Cm p +.Sh DESCRIPTION +.Nm +creates a special file named +.Ar name . +.Pp +The following special file types are supported: +.Bl -tag -width Ds +.It Cm b +A block device. +.It Cm c | u +A character device. +.It Cm p +A named pipe. +.El +.Pp +Block and character devices are created with major number +.Ar major , +and minor number +.Ar minor . +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl m +Set the mode of the new file based on the octal value of +.Ar mode . +.El +.Sh SEE ALSO +.Xr mknod 2 diff --git a/util/sbase/mknod.c b/util/sbase/mknod.c new file mode 100644 index 00000000..a519ecb2 --- /dev/null +++ b/util/sbase/mknod.c @@ -0,0 +1,72 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> +#include <sys/types.h> +#ifndef makedev +#include <sys/sysmacros.h> +#endif + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s [-m mode] name b|c|u major minor\n" +	        "       %s [-m mode] name p\n", +	        argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ +	mode_t mode = 0666; +	dev_t dev; + +	ARGBEGIN { +	case 'm': +		mode = parsemode(EARGF(usage()), mode, umask(0)); +		break; +	default: +		usage(); +	} ARGEND; + +	if (argc < 2) +		usage(); + +	if (strlen(argv[1]) != 1) +		goto invalid; +	switch (argv[1][0]) { +	case 'b': +		mode |= S_IFBLK; +		break; +	case 'u': +	case 'c': +		mode |= S_IFCHR; +		break; +	case 'p': +		mode |= S_IFIFO; +		break; +	default: +	invalid: +		eprintf("invalid type '%s'\n", argv[1]); +	} + +	if (S_ISFIFO(mode)) { +		if (argc != 2) +			usage(); +		dev = 0; +	} else { +		if (argc != 4) +			usage(); +		dev = makedev(estrtonum(argv[2], 0, LLONG_MAX), estrtonum(argv[3], 0, LLONG_MAX)); +	} + +	if (mknod(argv[0], mode, dev) == -1) +		eprintf("mknod %s:", argv[0]); +	return 0; +} diff --git a/util/sbase/mktemp.1 b/util/sbase/mktemp.1 new file mode 100644 index 00000000..59e27d8e --- /dev/null +++ b/util/sbase/mktemp.1 @@ -0,0 +1,50 @@ +.Dd October 8, 2015 +.Dt MKTEMP 1 +.Os sbase +.Sh NAME +.Nm mktemp +.Nd create temporary file or directory +.Sh SYNOPSIS +.Nm +.Op Fl dqtu +.Op Fl p Ar directory +.Op Ar template +.Sh DESCRIPTION +.Nm +creates a temporary file by generating a unique filename with +.Ar template , +which has to have at least six 'X's appended to it. +If no +.Ar template +is specified, a default of 'tmp.XXXXXXXXXX' is used and the +tmpdir set to '/tmp' or, if set, the TMPDIR environment variable. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl d +Create a temporary directory instead of a file. +.It Fl p Ar directory +Use the specified +.Ar directory +as a prefix when generating the temporary filename. +The directory will be overridden by the user's +.Ev TMPDIR +environment variable if it is set. +This option implies the +.Fl t +flag (see below). +.It Fl q +Fail silently if an error occurs. +.It Fl t +Generate a path rooted in a temporary directory. +.It Fl u +Unlink file before +.Nm +exits. +This is slightly better than +.Xr mktemp 3 +but still introduces a race condition. +Use of this option is not encouraged. +.El +.Sh SEE ALSO +.Xr mkdtemp 3 , +.Xr mkstemp 3 diff --git a/util/sbase/mktemp.c b/util/sbase/mktemp.c new file mode 100644 index 00000000..a3076ba2 --- /dev/null +++ b/util/sbase/mktemp.c @@ -0,0 +1,92 @@ +/* See LICENSE file for copyright and license details. */ +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s [-dqtu] [-p directory] [template]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	int dflag = 0, pflag = 0, qflag = 0, tflag = 0, uflag = 0, fd; +	char *template = "tmp.XXXXXXXXXX", *tmpdir = "", *pdir, +	     *p, path[PATH_MAX], tmp[PATH_MAX]; +	size_t len; + +	ARGBEGIN { +	case 'd': +		dflag = 1; +		break; +	case 'p': +		pflag = 1; +		pdir = EARGF(usage()); +		break; +	case 'q': +		qflag = 1; +		break; +	case 't': +		tflag = 1; +		break; +	case 'u': +		uflag = 1; +		break; +	default: +		usage(); +	} ARGEND + +	if (argc > 1) +		usage(); +	else if (argc == 1) +		template = argv[0]; + +	if (!argc || pflag || tflag) { +		if ((p = getenv("TMPDIR"))) +			tmpdir = p; +		else if (pflag) +			tmpdir = pdir; +		else +			tmpdir = "/tmp"; +	} + +	len = estrlcpy(path, tmpdir, sizeof(path)); +	if (path[0] && path[len - 1] != '/') +		estrlcat(path, "/", sizeof(path)); + +	estrlcpy(tmp, template, sizeof(tmp)); +	p = dirname(tmp); +	if (!(p[0] == '.' && p[1] == '\0')) { +		if (tflag && !pflag) +			eprintf("template must not contain directory separators in -t mode\n"); +	} +	estrlcat(path, template, sizeof(path)); + +	if (dflag) { +		if (!mkdtemp(path)) { +			if (!qflag) +				eprintf("mkdtemp %s:", path); +			return 1; +		} +	} else { +		if ((fd = mkstemp(path)) < 0) { +			if (!qflag) +				eprintf("mkstemp %s:", path); +			return 1; +		} +		if (close(fd)) +			eprintf("close %s:", path); +	} +	if (uflag) +		unlink(path); +	puts(path); + +	efshut(stdout, "<stdout>"); +	return 0; +} diff --git a/util/sbase/mv.1 b/util/sbase/mv.1 new file mode 100644 index 00000000..7fb95273 --- /dev/null +++ b/util/sbase/mv.1 @@ -0,0 +1,36 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +Except for the unsupported +.Fl i +flag. diff --git a/util/sbase/mv.c b/util/sbase/mv.c new file mode 100644 index 00000000..d24c77f5 --- /dev/null +++ b/util/sbase/mv.c @@ -0,0 +1,68 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.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, .follow = 'P', .flags = SILENT }; + +	if (!rename(s1, s2)) +		return 0; +	if (errno == EXDEV) { +		cp_aflag = cp_rflag = cp_pflag = 1; +		cp_follow = 'P'; +		cp_status = 0; +		rm_status = 0; +		cp(s1, s2, depth); +		if (cp_status == 0) +			recurse(AT_FDCWD, s1, NULL, &r); +		if (cp_status || rm_status) +			mv_status = 1; +	} else { +		weprintf("%s -> %s:", s1, s2); +		mv_status = 1; +	} + +	return 0; +} + +static void +usage(void) +{ +	eprintf("usage: %s [-f] source ... dest\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	struct stat st; + +	ARGBEGIN { +	case 'f': +		break; +	default: +		usage(); +	} ARGEND + +	if (argc < 2) +		usage(); + +	if (argc > 2) { +		if (stat(argv[argc - 1], &st) < 0) +			eprintf("stat %s:", argv[argc - 1]); +		if (!S_ISDIR(st.st_mode)) +			eprintf("%s: not a directory\n", argv[argc - 1]); +	} +	enmasse(argc, argv, mv); + +	return mv_status; +} diff --git a/util/sbase/nice.1 b/util/sbase/nice.1 new file mode 100644 index 00000000..18bbe585 --- /dev/null +++ b/util/sbase/nice.1 @@ -0,0 +1,36 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/nice.c b/util/sbase/nice.c new file mode 100644 index 00000000..d036e26c --- /dev/null +++ b/util/sbase/nice.c @@ -0,0 +1,56 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/resource.h> + +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +#ifndef PRIO_MIN +#define PRIO_MIN -NZERO +#endif + +#ifndef PRIO_MAX +#define PRIO_MAX (NZERO-1) +#endif + +static void +usage(void) +{ +	eprintf("usage: %s [-n inc] cmd [arg ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	int val = 10, r, savederrno; + +	ARGBEGIN { +	case 'n': +		val = estrtonum(EARGF(usage()), PRIO_MIN, PRIO_MAX); +		break; +	default: +		usage(); +		break; +	} ARGEND + +	if (!argc) +		usage(); + +	errno = 0; +	r = getpriority(PRIO_PROCESS, 0); +	if (errno) +		weprintf("getpriority:"); +	else +		val += r; +	LIMIT(val, PRIO_MIN, PRIO_MAX); +	if (setpriority(PRIO_PROCESS, 0, val) < 0) +		weprintf("setpriority:"); + +	execvp(argv[0], argv); +	savederrno = errno; +	weprintf("execvp %s:", argv[0]); + +	_exit(126 + (savederrno == ENOENT)); +} diff --git a/util/sbase/nl.1 b/util/sbase/nl.1 new file mode 100644 index 00000000..26975421 --- /dev/null +++ b/util/sbase/nl.1 @@ -0,0 +1,116 @@ +.Dd May 15, 2020 +.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. +.Pp +.Nm +treats the input text as a collection of logical pages divided into +logical page sections. +Each logical page consists of a header section, a body +section and a footer section. +Sections may be empty. +The start of each section is indicated by a single delimiting line, one of: +.Bl -column "\e:\e:\e: " "header " -offset indent +.It Em "Line" Ta Em "Start of" +.It \e:\e:\e:	header +.It \e:\e:	body +.It \e:	footer +.El +.Pp +If the input text contains no delimiting line then all of the input text +belongs to a single logical page body section. +.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 or +.Xr re_format 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 +POSIX.1-2013. diff --git a/util/sbase/nl.c b/util/sbase/nl.c new file mode 100644 index 00000000..9a289b02 --- /dev/null +++ b/util/sbase/nl.c @@ -0,0 +1,212 @@ +/* See LICENSE file for copyright and license details. */ +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "text.h" +#include "utf.h" +#include "util.h" + +static size_t   startnum = 1; +static size_t   incr = 1; +static size_t   blines = 1; +static size_t   delimlen = 2; +static size_t   seplen = 1; +static int      width = 6; +static int      pflag = 0; +static char     type[] = { 'n', 't', 'n' }; /* footer, body, header */ +static char    *delim = "\\:"; +static char     format[6] = "%*ld"; +static char    *sep = "\t"; +static regex_t  preg[3]; + +static int +getsection(struct line *l, int *section) +{ +	size_t i; +	int sectionchanged = 0, newsection = *section; + +	for (i = 0; (l->len - i) >= delimlen && +	     !memcmp(l->data + i, delim, delimlen); i += delimlen) { +		if (!sectionchanged) { +			sectionchanged = 1; +			newsection = 0; +		} else { +			newsection = (newsection + 1) % 3; +		} +	} + +	if (!(l->len - i) || l->data[i] == '\n') +		*section = newsection; +	else +		sectionchanged = 0; + +	return sectionchanged; +} + +static void +nl(const char *fname, FILE *fp) +{ +	static struct line line; +	static size_t size; +	size_t number = startnum, bl = 1; +	ssize_t len; +	int donumber, oldsection, section = 1; + +	while ((len = getline(&line.data, &size, fp)) > 0) { +		line.len = len; +		donumber = 0; +		oldsection = section; + +		if (getsection(&line, §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; +} diff --git a/util/sbase/nohup.1 b/util/sbase/nohup.1 new file mode 100644 index 00000000..48a8fad6 --- /dev/null +++ b/util/sbase/nohup.1 @@ -0,0 +1,40 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/nohup.c b/util/sbase/nohup.c new file mode 100644 index 00000000..5c1bf448 --- /dev/null +++ b/util/sbase/nohup.c @@ -0,0 +1,48 @@ +/* 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; + +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	if (!argc) +		usage(); + +	if (signal(SIGHUP, SIG_IGN) == SIG_ERR) +		enprintf(127, "signal HUP:"); + +	if (isatty(STDOUT_FILENO)) { +		if ((fd = open("nohup.out", O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR)) < 0) +			enprintf(127, "open nohup.out:"); +		if (dup2(fd, STDOUT_FILENO) < 0) +			enprintf(127, "dup2:"); +		close(fd); +	} +	if (isatty(STDERR_FILENO) && dup2(STDOUT_FILENO, STDERR_FILENO) < 0) +		enprintf(127, "dup2:"); + +	execvp(argv[0], argv); +	savederrno = errno; +	weprintf("execvp %s:", argv[0]); + +	_exit(126 + (savederrno == ENOENT)); +} diff --git a/util/sbase/od.1 b/util/sbase/od.1 new file mode 100644 index 00000000..622093f5 --- /dev/null +++ b/util/sbase/od.1 @@ -0,0 +1,80 @@ +.Dd October 25, 2015 +.Dt OD 1 +.Os sbase +.Sh NAME +.Nm od +.Nd octal dump +.Sh SYNOPSIS +.Nm +.Op Fl bdosvx +.Op Fl A Ar addrformat +.Op Fl E | e +.Op Fl j Ar skip +.Op Fl t Ar outputformat... +.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 b +Equivalent to +.Fl t o1 . +.It Fl d +Equivalent to +.Fl t u2 . +.It Fl j Ar skip +Ignore the first +.Ar skip +bytes of input. +.It Fl o +Equivalent to +.Fl t o2 . +.It Fl s +Equivalent to +.Fl t d2 . +.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. +.It Fl x +Equivalent to +.Fl t x2 . +.El +.Sh STANDARDS +POSIX.1-2013. +Except that the +.Op Fl v +flag is always enabled and the 'd' parameter for the +.Op Fl t +flag is interpreted as 'u'. +.Pp +The +.Op Ee +flags are an extension to that specification. diff --git a/util/sbase/od.c b/util/sbase/od.c new file mode 100644 index 00000000..0b1c5c60 --- /dev/null +++ b/util/sbase/od.c @@ -0,0 +1,332 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.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(const unsigned char *s, unsigned char format, size_t len) +{ +	long long res, basefac; +	size_t i; +	char fmt[] = " %#*ll#"; +	unsigned char c; + +	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': +		c = *s & ~128; /* clear high bit as required by standard */ +		if (c < LEN(namedict) || c == 127) { +			printf(" %3s", (c == 127) ? "del" : namedict[c]); +		} else { +			printf(" %3c", c); +		} +		break; +	case 'c': +		if (strchr("\a\b\t\n\v\f\r\0", *s)) { +			printf(" %3s", escdict[*s]); +		} else if (!isprint(*s)) { +			printf(" %3o", *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(const 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 int +od(int fd, char *fname, int last) +{ +	static unsigned char *line; +	static size_t lineoff; +	static off_t addr; +	unsigned char buf[BUFSIZ]; +	size_t i, size = sizeof(buf); +	ssize_t n; + +	while (skip - addr > 0) { +		n = read(fd, buf, MIN(skip - addr, sizeof(buf))); +		if (n < 0) +			weprintf("read %s:", fname); +		if (n <= 0) +			return n; +		addr += n; +	} +	if (!line) +		line = emalloc(linelen); + +	for (;;) { +		if (max >= 0) +			size = MIN(max - (addr - skip), size); +		if ((n = read(fd, buf, size)) <= 0) +			break; +		for (i = 0; i < n; i++, addr++) { +			line[lineoff++] = buf[i]; +			if (lineoff == linelen) { +				printline(line, lineoff, addr - lineoff + 1); +				lineoff = 0; +			} +		} +	} +	if (n < 0) { +		weprintf("read %s:", fname); +		return n; +	} +	if (lineoff && last) +		printline(line, lineoff, addr - lineoff); +	if (last) +		printline((unsigned char *)"", 0, addr); +	return 0; +} + +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 +addtype(char format, int len) +{ +	struct type *t; + +	t = emalloc(sizeof(*t)); +	t->format = format; +	t->len = len; +	TAILQ_INSERT_TAIL(&head, t, entry); +} + +static void +usage(void) +{ +	eprintf("usage: %s [-bdosvx] [-A addressformat] [-E | -e] [-j skip] " +	        "[-t outputformat] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	int fd; +	struct type *t; +	int ret = 0, len; +	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 'b': +		addtype('o', 1); +		break; +	case 'd': +		addtype('u', 2); +		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 'o': +		addtype('o', 2); +		break; +	case 's': +		addtype('d', 2); +		break; +	case 't': +		s = EARGF(usage()); +		for (; *s; s++) { +			switch (*s) { +			case 'a': +			case 'c': +				addtype(*s, 1); +				break; +			case 'd': +			case 'o': +			case 'u': +			case 'x': +				/* todo: allow multiple digits */ +				if (*(s+1) > '0' && *(s+1) <= '9') { +					len = *(s+1) - '0'; +				} else { +					switch (*(s+1)) { +					case 'C': +						len = sizeof(char); +						break; +					case 'S': +						len = sizeof(short); +						break; +					case 'I': +						len = sizeof(int); +						break; +					case 'L': +						len = sizeof(long); +						break; +					default: +						len = sizeof(int); +					} +				} +				addtype(*s++, len); +				break; +			default: +				usage(); +			} +		} +		break; +	case 'v': +		/* always set - use uniq(1) to handle duplicate lines */ +		break; +	case 'x': +		addtype('x', 2); +		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) { +		if (od(0, "<stdin>", 1) < 0) +			ret = 1; +	} else { +		for (; *argv; argc--, argv++) { +			if (!strcmp(*argv, "-")) { +				*argv = "<stdin>"; +				fd = 0; +			} else if ((fd = open(*argv, O_RDONLY)) < 0) { +				weprintf("open %s:", *argv); +				ret = 1; +				continue; +			} +			if (od(fd, *argv, (!*(argv + 1))) < 0) +				ret = 1; +			if (fd != 0) +				close(fd); +		} +	} + +	ret |= fshut(stdout, "<stdout>") | fshut(stderr, "<stderr>"); + +	return ret; +} diff --git a/util/sbase/paste.1 b/util/sbase/paste.1 new file mode 100644 index 00000000..7b26130f --- /dev/null +++ b/util/sbase/paste.1 @@ -0,0 +1,47 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/paste.c b/util/sbase/paste.c new file mode 100644 index 00000000..4fa9fc5a --- /dev/null +++ b/util/sbase/paste.c @@ -0,0 +1,144 @@ +/* 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; + +		while (efgetrune(&c, dsc[i].fp, dsc[i].name)) { +			for (m = last + 1; m < i; m++) { +				if (delim[m % delimlen] != '\0') +					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 if (d != '\0') +				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_rune = NULL; +	size_t delim_runelen, i, delim_bytelen = 1; +	int seq = 0, ret = 0; +	char *delim = "\t"; + +	ARGBEGIN { +	case 's': +		seq = 1; +		break; +	case 'd': +		delim = EARGF(usage()); +		delim_bytelen = unescape(delim); +		break; +	default: +		usage(); +	} ARGEND + +	if (!argc) +		usage(); + +	/* populate delimiters */ +	delim_rune = ereallocarray(NULL, +		utfmemlen(delim, delim_bytelen) + 1, sizeof(*delim_rune)); +	if (!(delim_runelen = utfntorunestr(delim, delim_bytelen, delim_rune))) { +		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_rune, delim_runelen); +	} else { +		parallel(dsc, argc, delim_rune, delim_runelen); +	} + +	for (i = 0; i < argc; i++) +		if (dsc[i].fp != stdin && fshut(dsc[i].fp, argv[i])) +			ret |= fshut(dsc[i].fp, argv[i]); + +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/pathchk.1 b/util/sbase/pathchk.1 new file mode 100644 index 00000000..e0b69b65 --- /dev/null +++ b/util/sbase/pathchk.1 @@ -0,0 +1,31 @@ +.Dd February 3, 2016 +.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 +POSIX.1-2013. diff --git a/util/sbase/pathchk.c b/util/sbase/pathchk.c new file mode 100644 index 00000000..b6b0c91c --- /dev/null +++ b/util/sbase/pathchk.c @@ -0,0 +1,104 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.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) +		eprintf("empty filename\n"); + +	/* Leading hyphen? */ +	if (extra && ((*filename == '-') || strstr(filename, "/-"))) +		eprintf("%s: leading '-' in component of filename\n", filename); + +	/* 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++); +		p = estrdup(filename); +		*invalid_end = 0; +		eprintf("%s: nonportable character '%s'\n", p, invalid); +	} + +	/* 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. */ +		eprintf("%s:", filename); +	} + +	/* Too long pathname? */ +	maxlen = most ? _POSIX_PATH_MAX : PATH_MAX; +	if (strlen(filename) >= maxlen) +		eprintf("%s: is longer than %zu bytes\n", filename, maxlen); + +	/* 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) +			eprintf("%s: includes component longer than %zu bytes\n", +			         filename, maxlen); +	} + +	return 0; +} + +static void +usage(void) +{ +	eprintf("usage: %s [-pP] filename...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	int ret = 0; + +	ARGBEGIN { +	case 'p': +		most = 1; +		break; +	case 'P': +		extra = 1; +		break; +	default: +		usage(); +	} ARGEND + +	if (!argc) +		usage(); + +	for (; argc--; argv++) +		ret |= pathchk(*argv); + +	return ret; +} diff --git a/util/sbase/printenv.1 b/util/sbase/printenv.1 new file mode 100644 index 00000000..24c410be --- /dev/null +++ b/util/sbase/printenv.1 @@ -0,0 +1,30 @@ +.Dd March 30, 2016 +.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 EXIT STATUS +.Bl -tag -width Ds +.It 0 +Successful completion. +.It 1 +One or more queried variables were not found. +.It > 1 +An error occurred. +.El +.Sh SEE ALSO +.Xr env 1 diff --git a/util/sbase/printenv.c b/util/sbase/printenv.c new file mode 100644 index 00000000..19b5b7d2 --- /dev/null +++ b/util/sbase/printenv.c @@ -0,0 +1,39 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> + +#include "util.h" + +extern char **environ; + +static void +usage(void) +{ +	eprintf("usage: %s [var ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	char *var; +	int ret = 0; + +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	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>") ? 2 : ret; +} diff --git a/util/sbase/printf.1 b/util/sbase/printf.1 new file mode 100644 index 00000000..67456e47 --- /dev/null +++ b/util/sbase/printf.1 @@ -0,0 +1,33 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +.Pp +The possibility of specifying 4-digit octals is an extension to that +specification. diff --git a/util/sbase/printf.c b/util/sbase/printf.c new file mode 100644 index 00000000..039dac71 --- /dev/null +++ b/util/sbase/printf.c @@ -0,0 +1,188 @@ +/* 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, blen; +	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('%'); +			continue; +		} + +		switch (format[i]) { +		case 'b': +			if ((tmp = strstr(arg, "\\c"))) { +				*tmp = 0; +				blen = unescape(arg); +				fwrite(arg, sizeof(*arg), blen, stdout); +				return 0; +			} +			blen = unescape(arg); +			fwrite(arg, sizeof(*arg), blen, 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': +			fmt = estrdup(flag ? "%#*.*s" : "%*.*s"); +			if (flag) +				fmt[1] = flag; +			printf(fmt, width, precision, arg); +			free(fmt); +			break; +		case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': +			for (j = 0; isspace(arg[j]); j++); +			if (arg[j] == '\'' || arg[j] == '\"') { +				arg += j + 1; +				unescape(arg); +				rarg = ereallocarray(NULL, utflen(arg) + 1, sizeof(*rarg)); +				utftorunestr(arg, rarg); +				num = rarg[0]; +			} else if (arg[0]) { +				errno = 0; +				if (format[i] == 'd' || format[i] == 'i') +					num = strtol(arg, &tmp, 0); +				else +					num = strtoul(arg, &tmp, 0); + +				if (tmp == arg || *tmp != '\0') { +					ret = 1; +					weprintf("%%%c %s: conversion error\n", +					    format[i], arg); +				} +				if (errno == ERANGE) { +					ret = 1; +					weprintf("%%%c %s: out of range\n", +					    format[i], arg); +				} +			} else { +					num = 0; +			} +			fmt = estrdup(flag ? "%#*.*ll#" : "%*.*ll#"); +			if (flag) +				fmt[1] = flag; +			fmt[flag ? 7 : 6] = format[i]; +			printf(fmt, width, precision, num); +			free(fmt); +			break; +		case 'a': case 'A': case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': +			fmt = estrdup(flag ? "%#*.*#" : "%*.*#"); +			if (flag) +				fmt[1] = flag; +			fmt[flag ? 5 : 4] = format[i]; +			dou = (strlen(arg) > 0) ? estrtod(arg) : 0; +			printf(fmt, width, precision, dou); +			free(fmt); +			break; +		default: +			eprintf("Invalid format specifier '%c'.\n", format[i]); +		} +		if (argi >= argc) +			cooldown = 1; +	} + +	return fshut(stdout, "<stdout>") | ret; +} diff --git a/util/sbase/pwd.1 b/util/sbase/pwd.1 new file mode 100644 index 00000000..71b9d2a8 --- /dev/null +++ b/util/sbase/pwd.1 @@ -0,0 +1,29 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/pwd.c b/util/sbase/pwd.c new file mode 100644 index 00000000..c6a4497f --- /dev/null +++ b/util/sbase/pwd.c @@ -0,0 +1,50 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static const char * +getpwd(const char *cwd) +{ +	const char *pwd; +	struct stat cst, pst; + +	if (!(pwd = getenv("PWD")) || pwd[0] != '/' || stat(pwd, &pst) < 0) +		return cwd; +	if (stat(cwd, &cst) < 0) +		eprintf("stat %s:", cwd); + +	return (pst.st_dev == cst.st_dev && pst.st_ino == cst.st_ino) ? pwd : cwd; +} + +static void +usage(void) +{ +	eprintf("usage: %s [-LP]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	char cwd[PATH_MAX]; +	char mode = 'L'; + +	ARGBEGIN { +	case 'L': +	case 'P': +		mode = ARGC(); +		break; +	default: +		usage(); +	} ARGEND + +	if (!getcwd(cwd, sizeof(cwd))) +		eprintf("getcwd:"); +	puts((mode == 'L') ? getpwd(cwd) : cwd); + +	return fshut(stdout, "<stdout>"); +} diff --git a/util/sbase/queue.h b/util/sbase/queue.h new file mode 100644 index 00000000..f8f09bf1 --- /dev/null +++ b/util/sbase/queue.h @@ -0,0 +1,648 @@ +/*	$OpenBSD: queue.h,v 1.38 2013/07/03 15:05:21 fgsch Exp $	*/ +/*	$NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $	*/ + +/* + * Copyright (c) 1991, 1993 + *	The Regents of the University of California.  All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + *    may be used to endorse or promote products derived from this software + *    without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + *	@(#)queue.h	8.5 (Berkeley) 8/20/94 + */ + +#ifndef	_SYS_QUEUE_H_ +#define	_SYS_QUEUE_H_ + +/* + * This file defines five types of data structures: singly-linked lists,  + * lists, simple queues, tail queues, and circular queues. + * + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction.  Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC)) +#define _Q_INVALIDATE(a) (a) = ((void *)-1) +#else +#define _Q_INVALIDATE(a) +#endif + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type)						\ +struct name {								\ +	struct type *slh_first;	/* first element */			\ +} +  +#define	SLIST_HEAD_INITIALIZER(head)					\ +	{ NULL } +  +#define SLIST_ENTRY(type)						\ +struct {								\ +	struct type *sle_next;	/* next element */			\ +} +  +/* + * Singly-linked List access methods. + */ +#define	SLIST_FIRST(head)	((head)->slh_first) +#define	SLIST_END(head)		NULL +#define	SLIST_EMPTY(head)	(SLIST_FIRST(head) == SLIST_END(head)) +#define	SLIST_NEXT(elm, field)	((elm)->field.sle_next) + +#define	SLIST_FOREACH(var, head, field)					\ +	for((var) = SLIST_FIRST(head);					\ +	    (var) != SLIST_END(head);					\ +	    (var) = SLIST_NEXT(var, field)) + +#define	SLIST_FOREACH_SAFE(var, head, field, tvar)			\ +	for ((var) = SLIST_FIRST(head);				\ +	    (var) && ((tvar) = SLIST_NEXT(var, field), 1);		\ +	    (var) = (tvar)) + +/* + * Singly-linked List functions. + */ +#define	SLIST_INIT(head) {						\ +	SLIST_FIRST(head) = SLIST_END(head);				\ +} + +#define	SLIST_INSERT_AFTER(slistelm, elm, field) do {			\ +	(elm)->field.sle_next = (slistelm)->field.sle_next;		\ +	(slistelm)->field.sle_next = (elm);				\ +} while (0) + +#define	SLIST_INSERT_HEAD(head, elm, field) do {			\ +	(elm)->field.sle_next = (head)->slh_first;			\ +	(head)->slh_first = (elm);					\ +} while (0) + +#define	SLIST_REMOVE_AFTER(elm, field) do {				\ +	(elm)->field.sle_next = (elm)->field.sle_next->field.sle_next;	\ +} while (0) + +#define	SLIST_REMOVE_HEAD(head, field) do {				\ +	(head)->slh_first = (head)->slh_first->field.sle_next;		\ +} while (0) + +#define SLIST_REMOVE(head, elm, type, field) do {			\ +	if ((head)->slh_first == (elm)) {				\ +		SLIST_REMOVE_HEAD((head), field);			\ +	} else {							\ +		struct type *curelm = (head)->slh_first;		\ +									\ +		while (curelm->field.sle_next != (elm))			\ +			curelm = curelm->field.sle_next;		\ +		curelm->field.sle_next =				\ +		    curelm->field.sle_next->field.sle_next;		\ +		_Q_INVALIDATE((elm)->field.sle_next);			\ +	}								\ +} while (0) + +/* + * List definitions. + */ +#define LIST_HEAD(name, type)						\ +struct name {								\ +	struct type *lh_first;	/* first element */			\ +} + +#define LIST_HEAD_INITIALIZER(head)					\ +	{ NULL } + +#define LIST_ENTRY(type)						\ +struct {								\ +	struct type *le_next;	/* next element */			\ +	struct type **le_prev;	/* address of previous next element */	\ +} + +/* + * List access methods + */ +#define	LIST_FIRST(head)		((head)->lh_first) +#define	LIST_END(head)			NULL +#define	LIST_EMPTY(head)		(LIST_FIRST(head) == LIST_END(head)) +#define	LIST_NEXT(elm, field)		((elm)->field.le_next) + +#define LIST_FOREACH(var, head, field)					\ +	for((var) = LIST_FIRST(head);					\ +	    (var)!= LIST_END(head);					\ +	    (var) = LIST_NEXT(var, field)) + +#define	LIST_FOREACH_SAFE(var, head, field, tvar)			\ +	for ((var) = LIST_FIRST(head);				\ +	    (var) && ((tvar) = LIST_NEXT(var, field), 1);		\ +	    (var) = (tvar)) + +/* + * List functions. + */ +#define	LIST_INIT(head) do {						\ +	LIST_FIRST(head) = LIST_END(head);				\ +} while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do {			\ +	if (((elm)->field.le_next = (listelm)->field.le_next) != NULL)	\ +		(listelm)->field.le_next->field.le_prev =		\ +		    &(elm)->field.le_next;				\ +	(listelm)->field.le_next = (elm);				\ +	(elm)->field.le_prev = &(listelm)->field.le_next;		\ +} while (0) + +#define	LIST_INSERT_BEFORE(listelm, elm, field) do {			\ +	(elm)->field.le_prev = (listelm)->field.le_prev;		\ +	(elm)->field.le_next = (listelm);				\ +	*(listelm)->field.le_prev = (elm);				\ +	(listelm)->field.le_prev = &(elm)->field.le_next;		\ +} while (0) + +#define LIST_INSERT_HEAD(head, elm, field) do {				\ +	if (((elm)->field.le_next = (head)->lh_first) != NULL)		\ +		(head)->lh_first->field.le_prev = &(elm)->field.le_next;\ +	(head)->lh_first = (elm);					\ +	(elm)->field.le_prev = &(head)->lh_first;			\ +} while (0) + +#define LIST_REMOVE(elm, field) do {					\ +	if ((elm)->field.le_next != NULL)				\ +		(elm)->field.le_next->field.le_prev =			\ +		    (elm)->field.le_prev;				\ +	*(elm)->field.le_prev = (elm)->field.le_next;			\ +	_Q_INVALIDATE((elm)->field.le_prev);				\ +	_Q_INVALIDATE((elm)->field.le_next);				\ +} while (0) + +#define LIST_REPLACE(elm, elm2, field) do {				\ +	if (((elm2)->field.le_next = (elm)->field.le_next) != NULL)	\ +		(elm2)->field.le_next->field.le_prev =			\ +		    &(elm2)->field.le_next;				\ +	(elm2)->field.le_prev = (elm)->field.le_prev;			\ +	*(elm2)->field.le_prev = (elm2);				\ +	_Q_INVALIDATE((elm)->field.le_prev);				\ +	_Q_INVALIDATE((elm)->field.le_next);				\ +} while (0) + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type)					\ +struct name {								\ +	struct type *sqh_first;	/* first element */			\ +	struct type **sqh_last;	/* addr of last next element */		\ +} + +#define SIMPLEQ_HEAD_INITIALIZER(head)					\ +	{ NULL, &(head).sqh_first } + +#define SIMPLEQ_ENTRY(type)						\ +struct {								\ +	struct type *sqe_next;	/* next element */			\ +} + +/* + * Simple queue access methods. + */ +#define	SIMPLEQ_FIRST(head)	    ((head)->sqh_first) +#define	SIMPLEQ_END(head)	    NULL +#define	SIMPLEQ_EMPTY(head)	    (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) +#define	SIMPLEQ_NEXT(elm, field)    ((elm)->field.sqe_next) + +#define SIMPLEQ_FOREACH(var, head, field)				\ +	for((var) = SIMPLEQ_FIRST(head);				\ +	    (var) != SIMPLEQ_END(head);					\ +	    (var) = SIMPLEQ_NEXT(var, field)) + +#define	SIMPLEQ_FOREACH_SAFE(var, head, field, tvar)			\ +	for ((var) = SIMPLEQ_FIRST(head);				\ +	    (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1);		\ +	    (var) = (tvar)) + +/* + * Simple queue functions. + */ +#define	SIMPLEQ_INIT(head) do {						\ +	(head)->sqh_first = NULL;					\ +	(head)->sqh_last = &(head)->sqh_first;				\ +} while (0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) do {			\ +	if (((elm)->field.sqe_next = (head)->sqh_first) == NULL)	\ +		(head)->sqh_last = &(elm)->field.sqe_next;		\ +	(head)->sqh_first = (elm);					\ +} while (0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) do {			\ +	(elm)->field.sqe_next = NULL;					\ +	*(head)->sqh_last = (elm);					\ +	(head)->sqh_last = &(elm)->field.sqe_next;			\ +} while (0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do {		\ +	if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ +		(head)->sqh_last = &(elm)->field.sqe_next;		\ +	(listelm)->field.sqe_next = (elm);				\ +} while (0) + +#define SIMPLEQ_REMOVE_HEAD(head, field) do {			\ +	if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ +		(head)->sqh_last = &(head)->sqh_first;			\ +} while (0) + +#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do {			\ +	if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \ +	    == NULL)							\ +		(head)->sqh_last = &(elm)->field.sqe_next;		\ +} while (0) + +/* + * XOR Simple queue definitions. + */ +#define XSIMPLEQ_HEAD(name, type)					\ +struct name {								\ +	struct type *sqx_first;	/* first element */			\ +	struct type **sqx_last;	/* addr of last next element */		\ +	unsigned long sqx_cookie;					\ +} + +#define XSIMPLEQ_ENTRY(type)						\ +struct {								\ +	struct type *sqx_next;	/* next element */			\ +} + +/* + * XOR Simple queue access methods. + */ +#define XSIMPLEQ_XOR(head, ptr)	    ((__typeof(ptr))((head)->sqx_cookie ^ \ +					(unsigned long)(ptr))) +#define	XSIMPLEQ_FIRST(head)	    XSIMPLEQ_XOR(head, ((head)->sqx_first)) +#define	XSIMPLEQ_END(head)	    NULL +#define	XSIMPLEQ_EMPTY(head)	    (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head)) +#define	XSIMPLEQ_NEXT(head, elm, field)    XSIMPLEQ_XOR(head, ((elm)->field.sqx_next)) + + +#define XSIMPLEQ_FOREACH(var, head, field)				\ +	for ((var) = XSIMPLEQ_FIRST(head);				\ +	    (var) != XSIMPLEQ_END(head);				\ +	    (var) = XSIMPLEQ_NEXT(head, var, field)) + +#define	XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar)			\ +	for ((var) = XSIMPLEQ_FIRST(head);				\ +	    (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1);	\ +	    (var) = (tvar)) + +/* + * XOR Simple queue functions. + */ +#define	XSIMPLEQ_INIT(head) do {					\ +	arc4random_buf(&(head)->sqx_cookie, sizeof((head)->sqx_cookie)); \ +	(head)->sqx_first = XSIMPLEQ_XOR(head, NULL);			\ +	(head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first);	\ +} while (0) + +#define XSIMPLEQ_INSERT_HEAD(head, elm, field) do {			\ +	if (((elm)->field.sqx_next = (head)->sqx_first) ==		\ +	    XSIMPLEQ_XOR(head, NULL))					\ +		(head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ +	(head)->sqx_first = XSIMPLEQ_XOR(head, (elm));			\ +} while (0) + +#define XSIMPLEQ_INSERT_TAIL(head, elm, field) do {			\ +	(elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL);		\ +	*(XSIMPLEQ_XOR(head, (head)->sqx_last)) = XSIMPLEQ_XOR(head, (elm)); \ +	(head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next);	\ +} while (0) + +#define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do {		\ +	if (((elm)->field.sqx_next = (listelm)->field.sqx_next) ==	\ +	    XSIMPLEQ_XOR(head, NULL))					\ +		(head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ +	(listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm));		\ +} while (0) + +#define XSIMPLEQ_REMOVE_HEAD(head, field) do {				\ +	if (((head)->sqx_first = XSIMPLEQ_XOR(head,			\ +	    (head)->sqx_first)->field.sqx_next) == XSIMPLEQ_XOR(head, NULL)) \ +		(head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ +} while (0) + +#define XSIMPLEQ_REMOVE_AFTER(head, elm, field) do {			\ +	if (((elm)->field.sqx_next = XSIMPLEQ_XOR(head,			\ +	    (elm)->field.sqx_next)->field.sqx_next)			\ +	    == XSIMPLEQ_XOR(head, NULL))				\ +		(head)->sqx_last = 					\ +		    XSIMPLEQ_XOR(head, &(elm)->field.sqx_next);		\ +} while (0) + +		     +/* + * Tail queue definitions. + */ +#define TAILQ_HEAD(name, type)						\ +struct name {								\ +	struct type *tqh_first;	/* first element */			\ +	struct type **tqh_last;	/* addr of last next element */		\ +} + +#define TAILQ_HEAD_INITIALIZER(head)					\ +	{ NULL, &(head).tqh_first } + +#define TAILQ_ENTRY(type)						\ +struct {								\ +	struct type *tqe_next;	/* next element */			\ +	struct type **tqe_prev;	/* address of previous next element */	\ +} + +/*  + * tail queue access methods  + */ +#define	TAILQ_FIRST(head)		((head)->tqh_first) +#define	TAILQ_END(head)			NULL +#define	TAILQ_NEXT(elm, field)		((elm)->field.tqe_next) +#define TAILQ_LAST(head, headname)					\ +	(*(((struct headname *)((head)->tqh_last))->tqh_last)) +/* XXX */ +#define TAILQ_PREV(elm, headname, field)				\ +	(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define	TAILQ_EMPTY(head)						\ +	(TAILQ_FIRST(head) == TAILQ_END(head)) + +#define TAILQ_FOREACH(var, head, field)					\ +	for((var) = TAILQ_FIRST(head);					\ +	    (var) != TAILQ_END(head);					\ +	    (var) = TAILQ_NEXT(var, field)) + +#define	TAILQ_FOREACH_SAFE(var, head, field, tvar)			\ +	for ((var) = TAILQ_FIRST(head);					\ +	    (var) != TAILQ_END(head) &&					\ +	    ((tvar) = TAILQ_NEXT(var, field), 1);			\ +	    (var) = (tvar)) + + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field)		\ +	for((var) = TAILQ_LAST(head, headname);				\ +	    (var) != TAILQ_END(head);					\ +	    (var) = TAILQ_PREV(var, headname, field)) + +#define	TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar)	\ +	for ((var) = TAILQ_LAST(head, headname);			\ +	    (var) != TAILQ_END(head) &&					\ +	    ((tvar) = TAILQ_PREV(var, headname, field), 1);		\ +	    (var) = (tvar)) + +/* + * Tail queue functions. + */ +#define	TAILQ_INIT(head) do {						\ +	(head)->tqh_first = NULL;					\ +	(head)->tqh_last = &(head)->tqh_first;				\ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do {			\ +	if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)	\ +		(head)->tqh_first->field.tqe_prev =			\ +		    &(elm)->field.tqe_next;				\ +	else								\ +		(head)->tqh_last = &(elm)->field.tqe_next;		\ +	(head)->tqh_first = (elm);					\ +	(elm)->field.tqe_prev = &(head)->tqh_first;			\ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do {			\ +	(elm)->field.tqe_next = NULL;					\ +	(elm)->field.tqe_prev = (head)->tqh_last;			\ +	*(head)->tqh_last = (elm);					\ +	(head)->tqh_last = &(elm)->field.tqe_next;			\ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do {		\ +	if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ +		(elm)->field.tqe_next->field.tqe_prev =			\ +		    &(elm)->field.tqe_next;				\ +	else								\ +		(head)->tqh_last = &(elm)->field.tqe_next;		\ +	(listelm)->field.tqe_next = (elm);				\ +	(elm)->field.tqe_prev = &(listelm)->field.tqe_next;		\ +} while (0) + +#define	TAILQ_INSERT_BEFORE(listelm, elm, field) do {			\ +	(elm)->field.tqe_prev = (listelm)->field.tqe_prev;		\ +	(elm)->field.tqe_next = (listelm);				\ +	*(listelm)->field.tqe_prev = (elm);				\ +	(listelm)->field.tqe_prev = &(elm)->field.tqe_next;		\ +} while (0) + +#define TAILQ_REMOVE(head, elm, field) do {				\ +	if (((elm)->field.tqe_next) != NULL)				\ +		(elm)->field.tqe_next->field.tqe_prev =			\ +		    (elm)->field.tqe_prev;				\ +	else								\ +		(head)->tqh_last = (elm)->field.tqe_prev;		\ +	*(elm)->field.tqe_prev = (elm)->field.tqe_next;			\ +	_Q_INVALIDATE((elm)->field.tqe_prev);				\ +	_Q_INVALIDATE((elm)->field.tqe_next);				\ +} while (0) + +#define TAILQ_REPLACE(head, elm, elm2, field) do {			\ +	if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL)	\ +		(elm2)->field.tqe_next->field.tqe_prev =		\ +		    &(elm2)->field.tqe_next;				\ +	else								\ +		(head)->tqh_last = &(elm2)->field.tqe_next;		\ +	(elm2)->field.tqe_prev = (elm)->field.tqe_prev;			\ +	*(elm2)->field.tqe_prev = (elm2);				\ +	_Q_INVALIDATE((elm)->field.tqe_prev);				\ +	_Q_INVALIDATE((elm)->field.tqe_next);				\ +} while (0) + +/* + * Circular queue definitions. + */ +#define CIRCLEQ_HEAD(name, type)					\ +struct name {								\ +	struct type *cqh_first;		/* first element */		\ +	struct type *cqh_last;		/* last element */		\ +} + +#define CIRCLEQ_HEAD_INITIALIZER(head)					\ +	{ CIRCLEQ_END(&head), CIRCLEQ_END(&head) } + +#define CIRCLEQ_ENTRY(type)						\ +struct {								\ +	struct type *cqe_next;		/* next element */		\ +	struct type *cqe_prev;		/* previous element */		\ +} + +/* + * Circular queue access methods  + */ +#define	CIRCLEQ_FIRST(head)		((head)->cqh_first) +#define	CIRCLEQ_LAST(head)		((head)->cqh_last) +#define	CIRCLEQ_END(head)		((void *)(head)) +#define	CIRCLEQ_NEXT(elm, field)	((elm)->field.cqe_next) +#define	CIRCLEQ_PREV(elm, field)	((elm)->field.cqe_prev) +#define	CIRCLEQ_EMPTY(head)						\ +	(CIRCLEQ_FIRST(head) == CIRCLEQ_END(head)) + +#define CIRCLEQ_FOREACH(var, head, field)				\ +	for((var) = CIRCLEQ_FIRST(head);				\ +	    (var) != CIRCLEQ_END(head);					\ +	    (var) = CIRCLEQ_NEXT(var, field)) + +#define	CIRCLEQ_FOREACH_SAFE(var, head, field, tvar)			\ +	for ((var) = CIRCLEQ_FIRST(head);				\ +	    (var) != CIRCLEQ_END(head) &&				\ +	    ((tvar) = CIRCLEQ_NEXT(var, field), 1);			\ +	    (var) = (tvar)) + +#define CIRCLEQ_FOREACH_REVERSE(var, head, field)			\ +	for((var) = CIRCLEQ_LAST(head);					\ +	    (var) != CIRCLEQ_END(head);					\ +	    (var) = CIRCLEQ_PREV(var, field)) + +#define	CIRCLEQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar)	\ +	for ((var) = CIRCLEQ_LAST(head, headname);			\ +	    (var) != CIRCLEQ_END(head) && 				\ +	    ((tvar) = CIRCLEQ_PREV(var, headname, field), 1);		\ +	    (var) = (tvar)) + +/* + * Circular queue functions. + */ +#define	CIRCLEQ_INIT(head) do {						\ +	(head)->cqh_first = CIRCLEQ_END(head);				\ +	(head)->cqh_last = CIRCLEQ_END(head);				\ +} while (0) + +#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do {		\ +	(elm)->field.cqe_next = (listelm)->field.cqe_next;		\ +	(elm)->field.cqe_prev = (listelm);				\ +	if ((listelm)->field.cqe_next == CIRCLEQ_END(head))		\ +		(head)->cqh_last = (elm);				\ +	else								\ +		(listelm)->field.cqe_next->field.cqe_prev = (elm);	\ +	(listelm)->field.cqe_next = (elm);				\ +} while (0) + +#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do {		\ +	(elm)->field.cqe_next = (listelm);				\ +	(elm)->field.cqe_prev = (listelm)->field.cqe_prev;		\ +	if ((listelm)->field.cqe_prev == CIRCLEQ_END(head))		\ +		(head)->cqh_first = (elm);				\ +	else								\ +		(listelm)->field.cqe_prev->field.cqe_next = (elm);	\ +	(listelm)->field.cqe_prev = (elm);				\ +} while (0) + +#define CIRCLEQ_INSERT_HEAD(head, elm, field) do {			\ +	(elm)->field.cqe_next = (head)->cqh_first;			\ +	(elm)->field.cqe_prev = CIRCLEQ_END(head);			\ +	if ((head)->cqh_last == CIRCLEQ_END(head))			\ +		(head)->cqh_last = (elm);				\ +	else								\ +		(head)->cqh_first->field.cqe_prev = (elm);		\ +	(head)->cqh_first = (elm);					\ +} while (0) + +#define CIRCLEQ_INSERT_TAIL(head, elm, field) do {			\ +	(elm)->field.cqe_next = CIRCLEQ_END(head);			\ +	(elm)->field.cqe_prev = (head)->cqh_last;			\ +	if ((head)->cqh_first == CIRCLEQ_END(head))			\ +		(head)->cqh_first = (elm);				\ +	else								\ +		(head)->cqh_last->field.cqe_next = (elm);		\ +	(head)->cqh_last = (elm);					\ +} while (0) + +#define	CIRCLEQ_REMOVE(head, elm, field) do {				\ +	if ((elm)->field.cqe_next == CIRCLEQ_END(head))			\ +		(head)->cqh_last = (elm)->field.cqe_prev;		\ +	else								\ +		(elm)->field.cqe_next->field.cqe_prev =			\ +		    (elm)->field.cqe_prev;				\ +	if ((elm)->field.cqe_prev == CIRCLEQ_END(head))			\ +		(head)->cqh_first = (elm)->field.cqe_next;		\ +	else								\ +		(elm)->field.cqe_prev->field.cqe_next =			\ +		    (elm)->field.cqe_next;				\ +	_Q_INVALIDATE((elm)->field.cqe_prev);				\ +	_Q_INVALIDATE((elm)->field.cqe_next);				\ +} while (0) + +#define CIRCLEQ_REPLACE(head, elm, elm2, field) do {			\ +	if (((elm2)->field.cqe_next = (elm)->field.cqe_next) ==		\ +	    CIRCLEQ_END(head))						\ +		(head)->cqh_last = (elm2);				\ +	else								\ +		(elm2)->field.cqe_next->field.cqe_prev = (elm2);	\ +	if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) ==		\ +	    CIRCLEQ_END(head))						\ +		(head)->cqh_first = (elm2);				\ +	else								\ +		(elm2)->field.cqe_prev->field.cqe_next = (elm2);	\ +	_Q_INVALIDATE((elm)->field.cqe_prev);				\ +	_Q_INVALIDATE((elm)->field.cqe_next);				\ +} while (0) + +#endif	/* !_SYS_QUEUE_H_ */ diff --git a/util/sbase/readlink.1 b/util/sbase/readlink.1 new file mode 100644 index 00000000..ac19710d --- /dev/null +++ b/util/sbase/readlink.1 @@ -0,0 +1,32 @@ +.Dd November 16, 2015 +.Dt READLINK 1 +.Os sbase +.Sh NAME +.Nm readlink +.Nd print symbolic link target or canonical file name +.Sh SYNOPSIS +.Nm +.Op Fl f +.Op Fl n +.Ar path +.Sh DESCRIPTION +.Nm +writes the target of +.Ar path , +if it is a symbolic link, to stdout. +If not, +.Nm +exits with a non-zero return value. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl f +Canonicalize +.Ar path , +which needn't be a symlink, +by recursively following every symlink in its path components. +.It Fl n +Do not print the terminating newline. +.El +.Sh SEE ALSO +.Xr readlink 2 , +.Xr realpath 3 diff --git a/util/sbase/readlink.c b/util/sbase/readlink.c new file mode 100644 index 00000000..d059584f --- /dev/null +++ b/util/sbase/readlink.c @@ -0,0 +1,54 @@ +/* See LICENSE file for copyright and license details. */ +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s [-fn] path\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	char buf[PATH_MAX]; +	ssize_t n; +	int nflag = 0, fflag = 0; + +	ARGBEGIN { +	case 'f': +		fflag = ARGC(); +		break; +	case 'n': +		nflag = 1; +		break; +	default: +		usage(); +	} ARGEND + +	if (argc != 1) +		usage(); + +	if (strlen(argv[0]) >= PATH_MAX) +		eprintf("path too long\n"); + +	if (fflag) { +		if (!realpath(argv[0], buf)) +			eprintf("realpath %s:", argv[0]); +	} else { +		if ((n = readlink(argv[0], buf, PATH_MAX - 1)) < 0) +			eprintf("readlink %s:", argv[0]); +		buf[n] = '\0'; +	} + +	fputs(buf, stdout); +	if (!nflag) +		putchar('\n'); + +	return fshut(stdout, "<stdout>"); +} diff --git a/util/sbase/renice.1 b/util/sbase/renice.1 new file mode 100644 index 00000000..c3ec7f8a --- /dev/null +++ b/util/sbase/renice.1 @@ -0,0 +1,38 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/renice.c b/util/sbase/renice.c new file mode 100644 index 00000000..358c5604 --- /dev/null +++ b/util/sbase/renice.c @@ -0,0 +1,93 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/resource.h> + +#include <errno.h> +#include <pwd.h> +#include <stdlib.h> + +#include "util.h" + +#ifndef PRIO_MIN +#define PRIO_MIN -NZERO +#endif + +#ifndef PRIO_MAX +#define PRIO_MAX (NZERO-1) +#endif + +static int +renice(int which, int who, long adj) +{ +	errno = 0; +	adj += getpriority(which, who); +	if (errno) { +		weprintf("getpriority %d:", who); +		return 0; +	} + +	adj = MAX(PRIO_MIN, MIN(adj, PRIO_MAX)); +	if (setpriority(which, who, (int)adj) < 0) { +		weprintf("setpriority %d:", who); +		return 0; +	} + +	return 1; +} + +static void +usage(void) +{ +	eprintf("usage: %s -n num [-g | -p | -u] id ...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	const char *adj = NULL; +	long val; +	int which = PRIO_PROCESS, ret = 0; +	struct passwd *pw; +	int who; + +	ARGBEGIN { +	case 'n': +		adj = EARGF(usage()); +		break; +	case 'g': +		which = PRIO_PGRP; +		break; +	case 'p': +		which = PRIO_PROCESS; +		break; +	case 'u': +		which = PRIO_USER; +		break; +	default: +		usage(); +	} ARGEND + +	if (!argc || !adj) +		usage(); + +	val = estrtonum(adj, PRIO_MIN, PRIO_MAX); +	for (; *argv; argc--, argv++) { +		if (which == PRIO_USER) { +			errno = 0; +			if (!(pw = getpwnam(*argv))) { +				if (errno) +					weprintf("getpwnam %s:", *argv); +				else +					weprintf("getpwnam %s: no user found\n", *argv); +				ret = 1; +				continue; +			} +			who = pw->pw_uid; +		} else { +			who = estrtonum(*argv, 1, INT_MAX); +		} +		if (!renice(which, who, val)) +			ret = 1; +	} + +	return ret; +} diff --git a/util/sbase/rev.1 b/util/sbase/rev.1 new file mode 100644 index 00000000..e56b920b --- /dev/null +++ b/util/sbase/rev.1 @@ -0,0 +1,22 @@ +.Dd March 26, 2016 +.Dt REV 1 +.Os sbase +.Sh NAME +.Nm rev +.Nd reverse each line +.Sh SYNOPSIS +.Nm +.Op Ar file ... +.Sh DESCRIPTION +.Nm +reads each +.Ar file +in sequence and writes it to stdout, but with all characters in each +line in reverse order. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh SEE ALSO +.Xr tac 1 diff --git a/util/sbase/rev.c b/util/sbase/rev.c new file mode 100644 index 00000000..9ac1da65 --- /dev/null +++ b/util/sbase/rev.c @@ -0,0 +1,74 @@ +/* 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 +usage(void) +{ +	eprintf("usage: %s [file ...]\n", argv0); +} + +static void +rev(FILE *fp) +{ +	static char *line = NULL; +	static size_t size = 0; +	size_t i; +	ssize_t n; +	int lf; + +	while ((n = getline(&line, &size, fp)) > 0) { +		lf = n && line[n - 1] == '\n'; +		i = n -= lf; +		for (n = 0; i--;) { +			if (UTF8_POINT(line[i])) { +				n++; +			} else { +				fwrite(line + i, 1, n + 1, stdout); +				n = 0; +			} +		} +		if (n) +			fwrite(line, 1, n, stdout); +		if (lf) +			fputc('\n', stdout); +	} +} + +int +main(int argc, char *argv[]) +{ +	FILE *fp; +	int ret = 0; + +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	if (!argc) { +		rev(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; +			} +			rev(fp); +			if (fp != stdin && fshut(fp, *argv)) +				ret = 1; +		} +	} + +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/rm.1 b/util/sbase/rm.1 new file mode 100644 index 00000000..604407da --- /dev/null +++ b/util/sbase/rm.1 @@ -0,0 +1,37 @@ +.Dd April 24, 2025 +.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 +POSIX.1-2013. diff --git a/util/sbase/rm.c b/util/sbase/rm.c new file mode 100644 index 00000000..3adfc8cd --- /dev/null +++ b/util/sbase/rm.c @@ -0,0 +1,87 @@ +/* See LICENSE file for copyright and license details. */ +#include <fcntl.h> +#include  <string.h> + +#include "fs.h" +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s [-f] [-iRr] file ...\n", argv0); +} + +static int +forbidden(char *path, struct stat *root) +{ +	char *s, *t; +	size_t n; +	struct stat st; +	static int w1, w2; + +	n = strlen(path); +	for (t = path + n; t > path && t[-1] == '/'; --t) +		; +	for (s = t; s > path && s[-1] != '/'; --s) +		; +	n = t - s; +	if (n == 1 && *s == '.' || n == 2 && s[0] == '.' && s[1] == '.') { +		if (!w1) +			weprintf("\".\" and \"..\" may not be removed\n"); +		w1 = 1; +		return 1; +	} + +	if (stat(path, &st) < 0) +		return 0; +	if (st.st_dev == root->st_dev && st.st_ino == root->st_ino) { +		if (!w2) +			weprintf("\"/\" may not be removed\n"); +		w2 = 1; +		return 1; +	} + +	return 0; +} + +int +main(int argc, char *argv[]) +{ +	char *s; +	struct stat st; +	struct recursor r = { .fn = rm, .maxdepth = 1, .follow = 'P' }; + +	ARGBEGIN { +	case 'f': +		r.flags |= SILENT | IGNORE; +		break; +	case 'i': +		r.flags |= CONFIRM; +		break; +	case 'R': +	case 'r': +		r.maxdepth = 0; +		break; +	default: +		usage(); +	} ARGEND + +	if (!argc) { +		if (!(r.flags & IGNORE)) +			usage(); +		else +			return 0; +	} + +	if (stat("/", &st) < 0) +		eprintf("stat root:"); +	for (; *argv; argc--, argv++) { +		if (forbidden(*argv, &st)) { +			rm_status = 1; +			continue; +		} +		recurse(AT_FDCWD, *argv, NULL, &r); +	} + +	return rm_status || recurse_status; +} diff --git a/util/sbase/rmdir.1 b/util/sbase/rmdir.1 new file mode 100644 index 00000000..b1631cf9 --- /dev/null +++ b/util/sbase/rmdir.1 @@ -0,0 +1,29 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/rmdir.c b/util/sbase/rmdir.c new file mode 100644 index 00000000..44224547 --- /dev/null +++ b/util/sbase/rmdir.c @@ -0,0 +1,49 @@ +/* See LICENSE file for copyright and license details. */ +#include <libgen.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s [-p] dir ...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	int pflag = 0, ret = 0; +	char *d; + +	ARGBEGIN { +	case 'p': +		pflag = 1; +		break; +	default: +		usage(); +	} ARGEND + +	if (!argc) +		usage(); + +	for (; *argv; argc--, argv++) { +		if (rmdir(*argv) < 0) { +			weprintf("rmdir %s:", *argv); +			ret = 1; +		} else if (pflag) { +			d = dirname(*argv); +			for (; strcmp(d, "/") && strcmp(d, ".") ;) { +				if (rmdir(d) < 0) { +					weprintf("rmdir %s:", d); +					ret = 1; +					break; +				} +				d = dirname(d); +			} +		} +	} + +	return ret; +} diff --git a/util/sbase/scripts/getconf.sh b/util/sbase/scripts/getconf.sh new file mode 100755 index 00000000..70902a30 --- /dev/null +++ b/util/sbase/scripts/getconf.sh @@ -0,0 +1,218 @@ +#!/bin/sh + +ifdef() { +	printf 'static const struct var %s[] = {\n' "$1" +	awk '{printf("#ifdef %s\n\t{\"%s\",\t%s},\n#endif\n", $2, $1, $2)}' +	echo '};' +} + +ifdef confstr_l << EOF +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 +EOF + +ifdef limits_l << EOF +_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 +EOF + +ifdef sysconf_l << EOF +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 +EOF + +ifdef pathconf_l << EOF +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 +EOF diff --git a/util/sbase/scripts/install b/util/sbase/scripts/install new file mode 100755 index 00000000..ce78c1da --- /dev/null +++ b/util/sbase/scripts/install @@ -0,0 +1,21 @@ +#!/bin/sh + +set -e + +while read type src dst perm +do +	case $type in +	d) +		mkdir -p $src +		;; +	c) +		cp -f $src $dst +		;; +	*) +		echo install: wrong entry type >&2 +		exit 1 +		;; +	esac + +	chmod $perm $dst +done < $1 diff --git a/util/sbase/scripts/mkbox b/util/sbase/scripts/mkbox new file mode 100755 index 00000000..99e40441 --- /dev/null +++ b/util/sbase/scripts/mkbox @@ -0,0 +1,103 @@ +#!/bin/sh + +trap "rm -rf build" INT QUIT TERM + +rm -rf build +mkdir -p build + +cp *.h build + +cat > build/sbase-box.c <<EOF +#include <unistd.h> + +#include <libgen.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" +#include "sbase-box.h" + +struct cmd { +	char *name; +	int (*fn)(int, char **); +} cmds[] = { +	{"install", xinstall_main}, +	{"[", test_main}, +$(grep -l ^main *.c | +while read f +do +	sed -n ' +	/^main/ { +		s/main/'${f%.c}'_main/ +		s/-/_/g +		w build/'$f' +		s/\(^.*\)(.*)/	{"'${f%.c}'", \1},/p +		d +	} +	w 'build/$f $f +done) +	{NULL}, +}; + +static void +install(char *path) +{ +	int r; +	struct cmd *bp; +	char fname[FILENAME_MAX]; + +	if (path == NULL) { +		fputs("sbase-box [-i path] [command]\n", stderr); +		exit(1); +	} + +	for (bp = cmds; bp->name; ++bp) { +		r = snprintf(fname, sizeof(fname), "%s/%s", path, bp->name); +		if (r < 0 || r >= sizeof(fname)) { +			fprintf(stderr, +			        "sbase-box: destination path truncated for '%s'\n", +			        bp->name); +			exit(1); +		} +		remove(fname); +		if (symlink("sbase-box", fname) < 0) { +			fprintf(stderr, +			        "sbase-box: %s: %s\n", +			        bp->name, strerror(errno)); +			exit(1); +		} +	} +} + +int +main(int argc, char *argv[]) +{ +	char *s = basename(argv[0]); +	struct cmd *bp; + +	if (!strcmp(s, "sbase-box") && argc > 1) { +		argc--; argv++; +		if (strcmp(argv[0], "-i") == 0) { +			install(argv[1]); +			exit(0); +		} +		s = basename(argv[0]); +	} + +	for (bp = cmds; bp->name; ++bp) { +		if (strcmp(bp->name, s) == 0) +			return (*bp->fn)(argc, argv); +	} + +	for (bp = cmds; bp->name; ++bp) +		printf("%s ", bp->name); +	putchar('\n'); + +	return 0; +} +EOF + +sed -n 's/.* \(.*_main\).*/int \1(int, char **);/p'\ +	build/sbase-box.c > build/sbase-box.h diff --git a/util/sbase/scripts/mkproto b/util/sbase/scripts/mkproto new file mode 100755 index 00000000..192fe56b --- /dev/null +++ b/util/sbase/scripts/mkproto @@ -0,0 +1,24 @@ +#!/bin/sh + +usage() +{ +	echo mkproto: prefix manprefix proto>&2 +	exit 1 +} + +prefix=${1?$(usage)} +manprefix=${2?$(usage)} +proto=${3?$(usage)} + +trap "rm -f scripts/proto" EXIT INT QUIT TERM + +(set -e +echo d $prefix/bin $prefix/bin 755 +find . ! -name . -prune -type f \( -perm -u+x -o -perm -g+x -o -perm o+x \) | +sed "s@.*@c & $prefix/bin/& 755@" + +echo d $manprefix/man1 $manprefix/man1 755 +find . ! -name . -prune -name '*.1' | +sed "s@.*@c & $manprefix/man1/& 644@") > $proto + +trap "" EXIT INT QUIT TERM diff --git a/util/sbase/scripts/uninstall b/util/sbase/scripts/uninstall new file mode 100755 index 00000000..e9c74f2d --- /dev/null +++ b/util/sbase/scripts/uninstall @@ -0,0 +1,32 @@ +#!/bin/sh + +set -e + +while read type src dst perm +do +	case $type in +	d) +		echo $type $src $dst $perm +		continue +		;; +	c) +		rm -f $dst +		;; +	*) +		echo uninstall: wrong entry type >&2 +		exit 1 +		;; +	esac +done < $1 | +sort -r | +while read type src dst perm +do +	case $type in +	d) +		if test `ls $dst | wc -l` -eq 0 +		then +			rmdir $dst +		fi +		;; +	esac +done diff --git a/util/sbase/sed.1 b/util/sbase/sed.1 new file mode 100644 index 00000000..18981aa7 --- /dev/null +++ b/util/sbase/sed.1 @@ -0,0 +1,173 @@ +.Dd October 8, 2015 +.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 +.Pp +Commands can be separated by ';' or by a new line. +.Pp +Multiple functions for the specified address (or address-range) can be enclosed +in blocks with '{' and '}': +.Pp +[address[,address]] { function ; 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 (in the format +.No / Ns +.Ar regexp Ns /). +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. +.Pp +The character '!' may be appended after the addresses, +in which case the function is executed only if the addresses +.Em don't +match. +.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 label if 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 #comment +The comment extends until the next newline. +.It Ar = +Write current input line number to output. +.El diff --git a/util/sbase/sed.c b/util/sbase/sed.c new file mode 100644 index 00000000..08471943 --- /dev/null +++ b/util/sbase/sed.c @@ -0,0 +1,1738 @@ +/* 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  , /* line 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 int      ret;    /* exit status */ + +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: sed [-nrE] script [file ...]\n" +	        "       sed [-nrE] -e script [-e script] ... [-f scriptfile] ... [file ...]\n" +	        "       sed [-nrE] [-e script] ... -f scriptfile [-f scriptfile] ... [file ...]\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) { +		f = fopen(s, "r"); +		if (!f) +			eprintf("fopen %s:", s); +	} else { +		if (!*s) /* empty string script */ +			return; +		f = fmemopen(s, strlen(s), "r"); +		if (!f) +			eprintf("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 == INSIDE_OPENING                       ) { state  = BRACKETS_INSIDE ;           } +		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>"); +	/* given no files, default to stdin */ +	file = first && !*files ? stdin : NULL; +	first = 0; + +	while (!file && *files) { +		if (!strcmp(*files, "-")) { +			file = stdin; +		} else if (!(file = fopen(*files, "r"))) { +			/* warn this file didn't open, but move on to next */ +			weprintf("fopen %s:", *files); +			ret = 1; +		} +		files++; +	} + +	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 script = 0; + +	ARGBEGIN { +	case 'n': +		gflags.n = 1; +		break; +	case 'r': +	case 'E': +		gflags.E = 1; +		break; +	case 'e': +		arg = EARGF(usage()); +		compile(arg, 0); +		script = 1; +		break; +	case 'f': +		arg = EARGF(usage()); +		compile(arg, 1); +		script = 1; +		break; +	default : usage(); +	} ARGEND + +	/* no script to run */ +	if (!script && !argc) +		usage(); + +	/* no script yet, next argument is script */ +	if (!script) +		compile(*argv++, 0); + +	/* shrink/grow memory to fit and add our last instruction */ +	resize((void **)&prog, &pcap, sizeof(*prog), pc - prog + 1, NULL); +	pc = prog + pcap - 1; +	pc->fninfo = &(Fninfo){ cmd_last, NULL, NULL, 0 }; + +	files = argv; +	run(); + +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/seq.1 b/util/sbase/seq.1 new file mode 100644 index 00000000..1b9def89 --- /dev/null +++ b/util/sbase/seq.1 @@ -0,0 +1,40 @@ +.Dd October 8, 2015 +.Dt SEQ 1 +.Os sbase +.Sh NAME +.Nm seq +.Nd print a sequence of numbers +.Sh SYNOPSIS +.Nm +.Op Fl w +.Op Fl f Ar fmt +.Op Fl s Ar sep +.Op Ar startnum Op Ar step +.Ar endnum +.Sh DESCRIPTION +.Nm +writes a sequence of numbers from +.Ar startnum +(default: 1) to +.Ar endnum +in +.Ar step +intervals (default: 1) +to stdout. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl f Ar fmt +Use +.Ar fmt +as the output line format according to +.Xr printf 3 . +.It Fl s Ar sep +Print +.Ar sep +between output lines. +The default is "\en". +.It Fl w +Print out lines in equal width. +.El +.Sh SEE ALSO +.Xr printf 3 diff --git a/util/sbase/seq.c b/util/sbase/seq.c new file mode 100644 index 00000000..70763d1e --- /dev/null +++ b/util/sbase/seq.c @@ -0,0 +1,147 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +static int +digitsleft(const char *d) +{ +	int shift; +	char *exp; + +	if (*d == '+') +		d++; +	exp = strpbrk(d, "eE"); +	shift = exp ? estrtonum(exp + 1, INT_MIN, INT_MAX) : 0; + +	return MAX(0, strspn(d, "-0123456789") + shift); +} + +static int +digitsright(const char *d) +{ +	int shift, after; +	char *exp; + +	exp = strpbrk(d, "eE"); +	shift = exp ? estrtonum(&exp[1], INT_MIN, INT_MAX) : 0; +	after = (d = strchr(d, '.')) ? strspn(&d[1], "0123456789") : 0; + +	return MAX(0, after - shift); +} + +static int +validfmt(const char *fmt) +{ +	int occur = 0; + +literal: +	while (*fmt) +		if (*fmt++ == '%') +			goto format; +	return occur == 1; + +format: +	if (*fmt == '%') { +		fmt++; +		goto literal; +	} +	fmt += strspn(fmt, "-+#0 '"); +	fmt += strspn(fmt, "0123456789"); +	if (*fmt == '.') { +		fmt++; +		fmt += strspn(fmt, "0123456789"); +	} +	if (*fmt == 'L') +		fmt++; + +	switch (*fmt) { +	case 'f': case 'F': +	case 'g': case 'G': +	case 'e': case 'E': +	case 'a': case 'A': +		occur++; +		goto literal; +	default: +		return 0; +	} +} + +static void +usage(void) +{ +	eprintf("usage: %s [-f fmt] [-s sep] [-w] " +	        "[startnum [step]] endnum\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	double start, step, end, out, dir; +	int wflag = 0, left, right; +	char *tmp, ftmp[BUFSIZ], *fmt = ftmp; +	const char *starts = "1", *steps = "1", *ends = "1", *sep = "\n"; + +	ARGBEGIN { +	case 'f': +		if (!validfmt(tmp=EARGF(usage()))) +			eprintf("%s: invalid format\n", tmp); +		fmt = tmp; +		break; +	case 's': +		sep = EARGF(usage()); +		break; +	case 'w': +		wflag = 1; +		break; +	default: +		usage(); +	} ARGEND + +	switch (argc) { +	case 3: +		steps = argv[1]; +		argv[1] = argv[2]; +		/* fallthrough */ +	case 2: +		starts = argv[0]; +		argv++; +		/* fallthrough */ +	case 1: +		ends = argv[0]; +		break; +	default: +		usage(); +	} +	start = estrtod(starts); +	step  = estrtod(steps); +	end   = estrtod(ends); + +	dir = (step > 0) ? 1.0 : -1.0; +	if (step == 0 || start * dir > end * dir) +		return 1; + +	if (fmt == ftmp) { +		right = MAX(digitsright(starts), +		            MAX(digitsright(ends), +		                digitsright(steps))); + +		if (wflag) { +			left = MAX(digitsleft(starts), digitsleft(ends)); + +			snprintf(ftmp, sizeof ftmp, "%%0%d.%df", +					right + left + (right != 0), right); +		} else +			snprintf(ftmp, sizeof ftmp, "%%.%df", right); +	} +	for (out = start; out * dir <= end * dir; out += step) { +		if (out != start) +			fputs(sep, stdout); +		printf(fmt, out); +	} +	putchar('\n'); + +	return fshut(stdout, "<stdout>"); +} diff --git a/util/sbase/setsid.1 b/util/sbase/setsid.1 new file mode 100644 index 00000000..5a7b2412 --- /dev/null +++ b/util/sbase/setsid.1 @@ -0,0 +1,18 @@ +.Dd July 14, 2020 +.Dt SETSID 1 +.Os sbase +.Sh NAME +.Nm setsid +.Nd run a command in a new session +.Sh SYNOPSIS +.Nm +.Op Fl f +.Ar cmd +.Op Ar arg ... +.Sh DESCRIPTION +.Nm +runs +.Ar cmd +in a new session. +.Sh SEE ALSO +.Xr setsid 2 diff --git a/util/sbase/setsid.c b/util/sbase/setsid.c new file mode 100644 index 00000000..9a154d13 --- /dev/null +++ b/util/sbase/setsid.c @@ -0,0 +1,48 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <unistd.h> + +#include "util.h" + +static int fflag = 0; + +static void +usage(void) +{ +	eprintf("usage: %s [-f] cmd [arg ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	int savederrno; + +	ARGBEGIN { +	case 'f': +		fflag = 1; +		break; +	default: +		usage(); +	} ARGEND + +	if (!argc) +		usage(); + +	if (fflag || getpgrp() == getpid()) { +		switch (fork()) { +		case -1: +			eprintf("fork:"); +		case 0: +			break; +		default: +			return 0; +		} +	} +	if (setsid() < 0) +		eprintf("setsid:"); +	execvp(argv[0], argv); +	savederrno = errno; +	weprintf("execvp %s:", argv[0]); + +	_exit(126 + (savederrno == ENOENT)); +} diff --git a/util/sbase/sha1.h b/util/sbase/sha1.h new file mode 100644 index 00000000..86317770 --- /dev/null +++ b/util/sbase/sha1.h @@ -0,0 +1,18 @@ +/* public domain sha1 implementation based on rfc3174 and libtomcrypt */ + +struct sha1 { +	uint64_t len;    /* processed message length */ +	uint32_t h[5];   /* hash state */ +	uint8_t buf[64]; /* message block buffer */ +}; + +enum { SHA1_DIGEST_LENGTH = 20 }; + +/* reset state */ +void sha1_init(void *ctx); +/* process message */ +void sha1_update(void *ctx, const void *m, unsigned long len); +/* get message digest */ +/* state is ruined after sum, keep a copy if multiple sum is needed */ +/* part of the message might be left in s, zero it if secrecy is needed */ +void sha1_sum(void *ctx, uint8_t md[SHA1_DIGEST_LENGTH]); diff --git a/util/sbase/sha1sum.1 b/util/sbase/sha1sum.1 new file mode 100644 index 00000000..62187135 --- /dev/null +++ b/util/sbase/sha1sum.1 @@ -0,0 +1,32 @@ +.Dd October 8, 2015 +.Dt SHA1SUM 1 +.Os sbase +.Sh NAME +.Nm sha1sum +.Nd compute or check SHA-1 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes SHA-1 (160-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of SHA-1 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/util/sbase/sha1sum.c b/util/sbase/sha1sum.c new file mode 100644 index 00000000..cc8dcae9 --- /dev/null +++ b/util/sbase/sha1sum.c @@ -0,0 +1,45 @@ +/* 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 'b': +	case 't': +		/* ignore */ +		break; +	case 'c': +		cryptfunc = cryptcheck; +		break; +	default: +		usage(); +	} ARGEND + +	ret |= cryptfunc(argc, argv, &sha1_ops, md, sizeof(md)); +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/sha224.h b/util/sbase/sha224.h new file mode 100644 index 00000000..d7f40532 --- /dev/null +++ b/util/sbase/sha224.h @@ -0,0 +1,16 @@ +/* public domain sha224 implementation based on fips180-3 */ + +#include "sha256.h" + +#define sha224  sha256  /*struct*/ + +enum { SHA224_DIGEST_LENGTH = 28 }; + +/* reset state */ +void sha224_init(void *ctx); +/* process message */ +#define sha224_update  sha256_update +/* get message digest */ +/* state is ruined after sum, keep a copy if multiple sum is needed */ +/* part of the message might be left in s, zero it if secrecy is needed */ +void sha224_sum(void *ctx, uint8_t md[SHA224_DIGEST_LENGTH]); diff --git a/util/sbase/sha224sum.1 b/util/sbase/sha224sum.1 new file mode 100644 index 00000000..42141a5f --- /dev/null +++ b/util/sbase/sha224sum.1 @@ -0,0 +1,32 @@ +.Dd February 24, 2016 +.Dt SHA224SUM 1 +.Os sbase +.Sh NAME +.Nm sha224sum +.Nd compute or check SHA-224 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes SHA-224 (224-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of SHA-224 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/util/sbase/sha224sum.c b/util/sbase/sha224sum.c new file mode 100644 index 00000000..e9a10cf9 --- /dev/null +++ b/util/sbase/sha224sum.c @@ -0,0 +1,45 @@ +/* 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 'b': +	case 't': +		/* ignore */ +		break; +	case 'c': +		cryptfunc = cryptcheck; +		break; +	default: +		usage(); +	} ARGEND + +	ret |= cryptfunc(argc, argv, &sha224_ops, md, sizeof(md)); +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/sha256.h b/util/sbase/sha256.h new file mode 100644 index 00000000..5968b8e1 --- /dev/null +++ b/util/sbase/sha256.h @@ -0,0 +1,18 @@ +/* public domain sha256 implementation based on fips180-3 */ + +struct sha256 { +	uint64_t len;    /* processed message length */ +	uint32_t h[8];   /* hash state */ +	uint8_t buf[64]; /* message block buffer */ +}; + +enum { SHA256_DIGEST_LENGTH = 32 }; + +/* reset state */ +void sha256_init(void *ctx); +/* process message */ +void sha256_update(void *ctx, const void *m, unsigned long len); +/* get message digest */ +/* state is ruined after sum, keep a copy if multiple sum is needed */ +/* part of the message might be left in s, zero it if secrecy is needed */ +void sha256_sum(void *ctx, uint8_t md[SHA256_DIGEST_LENGTH]); diff --git a/util/sbase/sha256sum.1 b/util/sbase/sha256sum.1 new file mode 100644 index 00000000..1a9aeee9 --- /dev/null +++ b/util/sbase/sha256sum.1 @@ -0,0 +1,32 @@ +.Dd October 8, 2015 +.Dt SHA256SUM 1 +.Os sbase +.Sh NAME +.Nm sha256sum +.Nd compute or check SHA-256 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes SHA-256 (256-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of SHA-256 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/util/sbase/sha256sum.c b/util/sbase/sha256sum.c new file mode 100644 index 00000000..686c70f0 --- /dev/null +++ b/util/sbase/sha256sum.c @@ -0,0 +1,45 @@ +/* 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 'b': +	case 't': +		/* ignore */ +		break; +	case 'c': +		cryptfunc = cryptcheck; +		break; +	default: +		usage(); +	} ARGEND + +	ret |= cryptfunc(argc, argv, &sha256_ops, md, sizeof(md)); +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/sha384.h b/util/sbase/sha384.h new file mode 100644 index 00000000..2ab9bc49 --- /dev/null +++ b/util/sbase/sha384.h @@ -0,0 +1,16 @@ +/* public domain sha512 implementation based on fips180-3 */ + +#include "sha512.h" + +#define sha384  sha512  /*struct*/ + +enum { SHA384_DIGEST_LENGTH = 48 }; + +/* reset state */ +void sha384_init(void *ctx); +/* process message */ +#define sha384_update  sha512_update +/* get message digest */ +/* state is ruined after sum, keep a copy if multiple sum is needed */ +/* part of the message might be left in s, zero it if secrecy is needed */ +void sha384_sum(void *ctx, uint8_t md[SHA384_DIGEST_LENGTH]); diff --git a/util/sbase/sha384sum.1 b/util/sbase/sha384sum.1 new file mode 100644 index 00000000..a417ca96 --- /dev/null +++ b/util/sbase/sha384sum.1 @@ -0,0 +1,32 @@ +.Dd February 24, 2016 +.Dt SHA384SUM 1 +.Os sbase +.Sh NAME +.Nm sha384sum +.Nd compute or check SHA-384 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes SHA-384 (384-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of SHA-384 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/util/sbase/sha384sum.c b/util/sbase/sha384sum.c new file mode 100644 index 00000000..c76947e6 --- /dev/null +++ b/util/sbase/sha384sum.c @@ -0,0 +1,45 @@ +/* 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 'b': +	case 't': +		/* ignore */ +		break; +	case 'c': +		cryptfunc = cryptcheck; +		break; +	default: +		usage(); +	} ARGEND + +	ret |= cryptfunc(argc, argv, &sha384_ops, md, sizeof(md)); +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/sha512-224.h b/util/sbase/sha512-224.h new file mode 100644 index 00000000..8364fc5f --- /dev/null +++ b/util/sbase/sha512-224.h @@ -0,0 +1,16 @@ +/* public domain sha512/224 implementation based on fips180-3 */ + +#include "sha512.h" + +#define sha512_224  sha512  /*struct*/ + +enum { SHA512_224_DIGEST_LENGTH = 28 }; + +/* reset state */ +void sha512_224_init(void *ctx); +/* process message */ +#define sha512_224_update  sha512_update +/* get message digest */ +/* state is ruined after sum, keep a copy if multiple sum is needed */ +/* part of the message might be left in s, zero it if secrecy is needed */ +void sha512_224_sum(void *ctx, uint8_t md[SHA512_224_DIGEST_LENGTH]); diff --git a/util/sbase/sha512-224sum.1 b/util/sbase/sha512-224sum.1 new file mode 100644 index 00000000..89206950 --- /dev/null +++ b/util/sbase/sha512-224sum.1 @@ -0,0 +1,32 @@ +.Dd February 24, 2016 +.Dt SHA512-224SUM 1 +.Os sbase +.Sh NAME +.Nm sha512-224sum +.Nd compute or check SHA-512/224 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes SHA-512/224 (224-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of SHA-512/224 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/util/sbase/sha512-224sum.c b/util/sbase/sha512-224sum.c new file mode 100644 index 00000000..53f2e625 --- /dev/null +++ b/util/sbase/sha512-224sum.c @@ -0,0 +1,45 @@ +/* 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 'b': +	case 't': +		/* ignore */ +		break; +	case 'c': +		cryptfunc = cryptcheck; +		break; +	default: +		usage(); +	} ARGEND + +	ret |= cryptfunc(argc, argv, &sha512_224_ops, md, sizeof(md)); +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/sha512-256.h b/util/sbase/sha512-256.h new file mode 100644 index 00000000..eb0b731d --- /dev/null +++ b/util/sbase/sha512-256.h @@ -0,0 +1,16 @@ +/* public domain sha512/256 implementation based on fips180-3 */ + +#include "sha512.h" + +#define sha512_256  sha512  /*struct*/ + +enum { SHA512_256_DIGEST_LENGTH = 32 }; + +/* reset state */ +void sha512_256_init(void *ctx); +/* process message */ +#define sha512_256_update  sha512_update +/* get message digest */ +/* state is ruined after sum, keep a copy if multiple sum is needed */ +/* part of the message might be left in s, zero it if secrecy is needed */ +void sha512_256_sum(void *ctx, uint8_t md[SHA512_256_DIGEST_LENGTH]); diff --git a/util/sbase/sha512-256sum.1 b/util/sbase/sha512-256sum.1 new file mode 100644 index 00000000..98b8a098 --- /dev/null +++ b/util/sbase/sha512-256sum.1 @@ -0,0 +1,32 @@ +.Dd February 24, 2016 +.Dt SHA512-256SUM 1 +.Os sbase +.Sh NAME +.Nm sha512-256sum +.Nd compute or check SHA-512/256 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes SHA-512/256 (256-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of SHA-512/256 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/util/sbase/sha512-256sum.c b/util/sbase/sha512-256sum.c new file mode 100644 index 00000000..ea556b8d --- /dev/null +++ b/util/sbase/sha512-256sum.c @@ -0,0 +1,45 @@ +/* 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 'b': +	case 't': +		/* ignore */ +		break; +	case 'c': +		cryptfunc = cryptcheck; +		break; +	default: +		usage(); +	} ARGEND + +	ret |= cryptfunc(argc, argv, &sha512_256_ops, md, sizeof(md)); +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/sha512.h b/util/sbase/sha512.h new file mode 100644 index 00000000..c761712a --- /dev/null +++ b/util/sbase/sha512.h @@ -0,0 +1,18 @@ +/* public domain sha512 implementation based on fips180-3 */ + +struct sha512 { +	uint64_t len;     /* processed message length */ +	uint64_t h[8];    /* hash state */ +	uint8_t buf[128]; /* message block buffer */ +}; + +enum { SHA512_DIGEST_LENGTH = 64 }; + +/* reset state */ +void sha512_init(void *ctx); +/* process message */ +void sha512_update(void *ctx, const void *m, unsigned long len); +/* get message digest */ +/* state is ruined after sum, keep a copy if multiple sum is needed */ +/* part of the message might be left in s, zero it if secrecy is needed */ +void sha512_sum(void *ctx, uint8_t md[SHA512_DIGEST_LENGTH]); diff --git a/util/sbase/sha512sum.1 b/util/sbase/sha512sum.1 new file mode 100644 index 00000000..14ef105f --- /dev/null +++ b/util/sbase/sha512sum.1 @@ -0,0 +1,32 @@ +.Dd October 8, 2015 +.Dt SHA512SUM 1 +.Os sbase +.Sh NAME +.Nm sha512sum +.Nd compute or check SHA-512 message digests +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Ar file ... +.Sh DESCRIPTION +.Nm +writes SHA-512 (512-bit) checksums of each +.Ar file +to stdout. +If no +.Ar file +is given +.Nm +reads from stdin. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Read list of SHA-512 checksums from each +.Ar file +and check them. +If no +.Ar file +is given +.Nm +reads from stdin. +.El diff --git a/util/sbase/sha512sum.c b/util/sbase/sha512sum.c new file mode 100644 index 00000000..a76e685b --- /dev/null +++ b/util/sbase/sha512sum.c @@ -0,0 +1,45 @@ +/* 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 'b': +	case 't': +		/* ignore */ +		break; +	case 'c': +		cryptfunc = cryptcheck; +		break; +	default: +		usage(); +	} ARGEND + +	ret |= cryptfunc(argc, argv, &sha512_ops, md, sizeof(md)); +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/sleep.1 b/util/sbase/sleep.1 new file mode 100644 index 00000000..3c58fb95 --- /dev/null +++ b/util/sbase/sleep.1 @@ -0,0 +1,18 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/sleep.c b/util/sbase/sleep.c new file mode 100644 index 00000000..00ea2688 --- /dev/null +++ b/util/sbase/sleep.c @@ -0,0 +1,30 @@ +/* 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; + +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	if (argc != 1) +		usage(); + +	seconds = estrtonum(argv[0], 0, UINT_MAX); +	while ((seconds = sleep(seconds)) > 0) +		; + +	return 0; +} diff --git a/util/sbase/sort.1 b/util/sbase/sort.1 new file mode 100644 index 00000000..f5a2121e --- /dev/null +++ b/util/sbase/sort.1 @@ -0,0 +1,98 @@ +.Dd February 17, 2016 +.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 +POSIX.1-2013. diff --git a/util/sbase/sort.c b/util/sbase/sort.c new file mode 100644 index 00000000..fbb1abfe --- /dev/null +++ b/util/sbase/sort.c @@ -0,0 +1,437 @@ +/* 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; +}; + +struct column { +	struct line line; +	size_t cap; +}; + +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 column 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->len -= s - a->data; +			a->data = s; +		} else { +			a->data += a->len - 1; +			a->len = 1; +		} +	} else { +		skipblank(a); +		skipnonblank(a); +	} +} + +static void +columns(struct line *line, const struct keydef *kd, struct column *col) +{ +	Rune r; +	struct line start, end; +	size_t 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; +	} +	col->line.len = MAX(0, end.data - start.data); +	if (!(col->line.data) || col->cap < col->line.len + 1) { +		free(col->line.data); +		col->line.data = emalloc(col->line.len + 1); +	} +	memcpy(col->line.data, start.data, col->line.len); +	col->line.data[col->line.len] = '\0'; +} + +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; +	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 = strtod(col1.line.data, NULL); +			y = strtod(col2.line.data, NULL); +			res = (x < y) ? -1 : (x > y); +		} else if (kd->flags & (MOD_D | MOD_F | MOD_I)) { +			res = skipmodcmp(&col1.line, &col2.line, kd->flags); +		} else { +			res = linecmp(&col1.line, &col2.line); +		} + +		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)); +	if (TAILQ_EMPTY(&kdhead) || (!Cflag && !cflag)) +		addkeydef("1", global_flags & MOD_R); + +	if (!argc) { +		if (Cflag || cflag) { +			if (check(stdin, "<stdin>") && !ret) +				ret = 1; +		} else { +			getlines(stdin, &linebuf); +		} +	} else for (; *argv; argc--, argv++) { +		if (!strcmp(*argv, "-")) { +			*argv = "<stdin>"; +			fp = stdin; +		} else if (!(fp = fopen(*argv, "r"))) { +			enprintf(2, "fopen %s:", *argv); +			continue; +		} +		if (Cflag || cflag) { +			if (check(fp, *argv) && !ret) +				ret = 1; +		} else { +			getlines(fp, &linebuf); +		} +		if (fp != stdin && fshut(fp, *argv)) +			ret = 2; +	} + +	if (!Cflag && !cflag) { +		if (outfile && !(ofp = fopen(outfile, "w"))) +			eprintf("fopen %s:", outfile); + +		qsort(linebuf.lines, linebuf.nlines, sizeof(*linebuf.lines), +				(int (*)(const void *, const void *))slinecmp); + +		for (i = 0; i < linebuf.nlines; i++) { +			if (!uflag || i == 0 || +			    slinecmp(&linebuf.lines[i], &linebuf.lines[i - 1])) { +				fwrite(linebuf.lines[i].data, 1, +				       linebuf.lines[i].len, ofp); +			} +		} +	} + +	if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>") | +	    fshut(stderr, "<stderr>")) +		ret = 2; + +	return ret; +} diff --git a/util/sbase/split.1 b/util/sbase/split.1 new file mode 100644 index 00000000..63b3b521 --- /dev/null +++ b/util/sbase/split.1 @@ -0,0 +1,46 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +.Pp +The +.Op Fl d +flag and g unit are an extension to that specification. diff --git a/util/sbase/split.c b/util/sbase/split.c new file mode 100644 index 00000000..7033a284 --- /dev/null +++ b/util/sbase/split.c @@ -0,0 +1,111 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +static int base = 26, start = 'a'; + +static int +itostr(char *str, int x, int n) +{ +	str[n] = '\0'; +	while (n-- > 0) { +		str[n] = start + (x % base); +		x /= base; +	} + +	return x ? -1 : 0; +} + +static FILE * +nextfile(FILE *f, char *buf, int plen, int slen) +{ +	static int filecount = 0; + +	if (f) +		fshut(f, "<file>"); +	if (itostr(buf + plen, filecount++, slen) < 0) +		return NULL; + +	if (!(f = fopen(buf, "w"))) +		eprintf("'%s':", buf); + +	return f; +} + +static void +usage(void) +{ +	eprintf("usage: %s [-a num] [-b num[k|m|g] | -l num] [-d] " +	        "[file [prefix]]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	FILE *in = stdin, *out = NULL; +	off_t size = 1000, n; +	int ret = 0, ch, plen, slen = 2, always = 0; +	char name[NAME_MAX + 1], *prefix = "x", *file = NULL; + +	ARGBEGIN { +	case 'a': +		slen = estrtonum(EARGF(usage()), 0, INT_MAX); +		break; +	case 'b': +		always = 1; +		if ((size = parseoffset(EARGF(usage()))) < 0) +			return 1; +		if (!size) +			eprintf("size needs to be positive\n"); +		break; +	case 'd': +		base = 10; +		start = '0'; +		break; +	case 'l': +		always = 0; +		size = estrtonum(EARGF(usage()), 1, MIN(LLONG_MAX, SSIZE_MAX)); +		break; +	default: +		usage(); +	} ARGEND + +	if (*argv) +		file = *argv++; +	if (*argv) +		prefix = *argv++; +	if (*argv) +		usage(); + +	plen = strlen(prefix); +	if (plen + slen > NAME_MAX) +		eprintf("names cannot exceed %d bytes\n", NAME_MAX); +	estrlcpy(name, prefix, sizeof(name)); + +	if (file && strcmp(file, "-")) { +		if (!(in = fopen(file, "r"))) +			eprintf("fopen %s:", file); +	} + +	n = 0; +	while ((ch = getc(in)) != EOF) { +		if (!out || n >= size) { +			if (!(out = nextfile(out, name, plen, slen))) +				eprintf("fopen: %s:", name); +			n = 0; +		} +		n += (always || ch == '\n'); +		putc(ch, out); +	} + +	ret |= (in != stdin) && fshut(in, "<infile>"); +	ret |= out && (out != stdout) && fshut(out, "<outfile>"); +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/sponge.1 b/util/sbase/sponge.1 new file mode 100644 index 00000000..9b668b42 --- /dev/null +++ b/util/sbase/sponge.1 @@ -0,0 +1,19 @@ +.Dd October 8, 2015 +.Dt SPONGE 1 +.Os sbase +.Sh NAME +.Nm sponge +.Nd soak up standard input and write to a file +.Sh SYNOPSIS +.Nm +.Ar file +.Sh DESCRIPTION +.Nm +reads stdin completely, then writes the saved contents to +.Ar file . +This makes it possible to easily create pipes which read from and write to +the same file. +.Pp +If +.Ar file +is a symbolic link, it writes to its destination instead. diff --git a/util/sbase/sponge.c b/util/sbase/sponge.c new file mode 100644 index 00000000..7a0b272b --- /dev/null +++ b/util/sbase/sponge.c @@ -0,0 +1,42 @@ +/* See LICENSE file for copyright and license details. */ +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s file\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	char tmp[] = "/tmp/sponge-XXXXXX"; +	int fd, tmpfd; + +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	if (argc != 1) +		usage(); + +	if ((tmpfd = mkstemp(tmp)) < 0) +		eprintf("mkstemp:"); +	unlink(tmp); +	if (concat(0, "<stdin>", tmpfd, "<tmpfile>") < 0) +		return 1; +	if (lseek(tmpfd, 0, SEEK_SET) < 0) +		eprintf("lseek:"); + +	if ((fd = creat(argv[0], 0666)) < 0) +		eprintf("creat %s:", argv[0]); +	if (concat(tmpfd, "<tmpfile>", fd, argv[0]) < 0) +		return 1; + +	return 0; +} diff --git a/util/sbase/strings.1 b/util/sbase/strings.1 new file mode 100644 index 00000000..c7bed734 --- /dev/null +++ b/util/sbase/strings.1 @@ -0,0 +1,50 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +.Pp +The +.Op Fl t +output format has been changed from "%F %s" to "%8lF: %s", with +.Sy F +being one of +.Sy d , o , x . diff --git a/util/sbase/strings.c b/util/sbase/strings.c new file mode 100644 index 00000000..8f5a1540 --- /dev/null +++ b/util/sbase/strings.c @@ -0,0 +1,98 @@ +/* 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 min) +{ +	Rune r, *rbuf; +	size_t i, bread; +	off_t off; + +	rbuf = ereallocarray(NULL, min, sizeof(*rbuf)); + +	for (off = 0, i = 0; (bread = efgetrune(&r, fp, fname)); ) { +		off += bread; +		if (r == Runeerror) +			continue; +		if (!isprintrune(r)) { +			if (i == min) +				putchar('\n'); +			i = 0; +			continue; +		} +		if (i == min) { +			efputrune(&r, stdout, "<stdout>"); +			continue; +		} +		rbuf[i++] = r; +		if (i < min) +			continue; +		printf(format, (long)off - i); +		for (i = 0; i < min; i++) +			efputrune(rbuf + i, stdout, "<stdout>"); +	} +	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 min = 4; +	int ret = 0; +	char f; + +	ARGBEGIN { +	case 'a': +		break; +	case 'n': +		min = 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>", min); +	} 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, min); +			if (fp != stdin && fshut(fp, *argv)) +				ret = 1; +		} +	} + +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/sync.1 b/util/sbase/sync.1 new file mode 100644 index 00000000..85f4526e --- /dev/null +++ b/util/sbase/sync.1 @@ -0,0 +1,17 @@ +.Dd October 8, 2015 +.Dt SYNC 1 +.Os sbase +.Sh NAME +.Nm sync +.Nd flush disk cache +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +.Nm +invokes +.Xr sync 2 +to flush all unwritten changes to disk. +This is usually done before shutting down, rebooting or halting. +.Sh SEE ALSO +.Xr fsync 2 , +.Xr sync 2 diff --git a/util/sbase/sync.c b/util/sbase/sync.c new file mode 100644 index 00000000..f1b98183 --- /dev/null +++ b/util/sbase/sync.c @@ -0,0 +1,25 @@ +/* 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[]) +{ +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	if (argc) +		usage(); +	sync(); + +	return 0; +} diff --git a/util/sbase/tail.1 b/util/sbase/tail.1 new file mode 100644 index 00000000..c425aacf --- /dev/null +++ b/util/sbase/tail.1 @@ -0,0 +1,51 @@ +.Dd October 8, 2015 +.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 m 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 m Ar num | Fl n Ar num | Fl Ns Ar num +Display final +.Ar num +bytes | 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 +POSIX.1-2013. +.Pp +The +.Op Fl Ns Ar num +syntax is an extension to that specification. diff --git a/util/sbase/tail.c b/util/sbase/tail.c new file mode 100644 index 00000000..bbc5ad58 --- /dev/null +++ b/util/sbase/tail.c @@ -0,0 +1,229 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include <fcntl.h> +#include <unistd.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "utf.h" +#include "util.h" + +static char mode = 'n'; + +static int +dropinit(int fd, const char *fname, size_t count) +{ +	Rune r; +	char buf[BUFSIZ], *p; +	ssize_t n; +	int nr; + +	if (count < 2) +		goto copy; +	count--;  /* numbering starts at 1 */ +	while (count && (n = read(fd, buf, sizeof(buf))) > 0) { +		switch (mode) { +		case 'n':  /* lines */ +			for (p = buf; count && n > 0; p++, n--) { +				if (*p == '\n') +					count--; +			} +			break; +		case 'c':  /* bytes */ +			if (count > n) { +				count -= n; +			} else { +				p = buf + count; +				n -= count; +				count = 0; +			} +			break; +		case 'm':  /* runes */ +			for (p = buf; count && n > 0; p += nr, n -= nr, count--) { +				nr = charntorune(&r, p, n); +				if (!nr) { +					/* we don't have a full rune, move +					 * remaining data to beginning and read +					 * again */ +					memmove(buf, p, n); +					break; +				} +			} +			break; +		} +	} +	if (count) { +		if (n < 0) +			weprintf("read %s:", fname); +		if (n <= 0) +			return n; +	} + +	/* write the rest of the buffer */ +	if (writeall(1, p, n) < 0) +		eprintf("write:"); +copy: +	switch (concat(fd, fname, 1, "<stdout>")) { +	case -1:  /* read error */ +		return -1; +	case -2:  /* write error */ +		exit(1); +	default: +		return 0; +	} +} + +static int +taketail(int fd, const char *fname, size_t count) +{ +	static char *buf = NULL; +	static size_t size = 0; +	char *p; +	size_t len = 0, left; +	ssize_t n; + +	if (!count) +		return 0; +	for (;;) { +		if (len + BUFSIZ > size) { +			/* make sure we have at least BUFSIZ to read */ +			size += 2 * BUFSIZ; +			buf = erealloc(buf, size); +		} +		n = read(fd, buf + len, size - len); +		if (n < 0) { +			weprintf("read %s:", fname); +			return -1; +		} +		if (n == 0) +			break; +		len += n; +		switch (mode) { +		case 'n':  /* lines */ +			/* ignore the last character; if it is a newline, it +			 * ends the last line */ +			for (p = buf + len - 2, left = count; p >= buf; p--) { +				if (*p != '\n') +					continue; +				left--; +				if (!left) { +					p++; +					break; +				} +			} +			break; +		case 'c':  /* bytes */ +			p = count < len ? buf + len - count : buf; +			break; +		case 'm':  /* runes */ +			for (p = buf + len - 1, left = count; p >= buf; p--) { +				/* skip utf-8 continuation bytes */ +				if (UTF8_POINT(*p)) +					continue; +				left--; +				if (!left) +					break; +			} +			break; +		} +		if (p > buf) { +			len -= p - buf; +			memmove(buf, p, len); +		} +	} +	if (writeall(1, buf, len) < 0) +		eprintf("write:"); +	return 0; +} + +static void +usage(void) +{ +	eprintf("usage: %s [-f] [-c num | -m num | -n num | -num] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	struct stat st1, st2; +	int fd; +	size_t n = 10; +	int fflag = 0, ret = 0, newline = 0, many = 0; +	char *numstr; +	int (*tail)(int, const char *, size_t) = taketail; + +	ARGBEGIN { +	case 'f': +		fflag = 1; +		break; +	case 'c': +	case 'm': +	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) { +		if (tail(0, "<stdin>", n) < 0) +			ret = 1; +	} else { +		if ((many = argc > 1) && fflag) +			usage(); +		for (newline = 0; *argv; argc--, argv++) { +			if (!strcmp(*argv, "-")) { +				*argv = "<stdin>"; +				fd = 0; +			} else if ((fd = open(*argv, O_RDONLY)) < 0) { +				weprintf("open %s:", *argv); +				ret = 1; +				continue; +			} +			if (many) +				printf("%s==> %s <==\n", newline ? "\n" : "", *argv); +			if (fstat(fd, &st1) < 0) +				eprintf("fstat %s:", *argv); +			if (!(S_ISFIFO(st1.st_mode) || S_ISREG(st1.st_mode))) +				fflag = 0; +			newline = 1; +			if (tail(fd, *argv, n) < 0) { +				ret = 1; +				fflag = 0; +			} + +			if (!fflag) { +				if (fd != 0) +					close(fd); +				continue; +			} +			for (;;) { +				if (concat(fd, *argv, 1, "<stdout>") < 0) +					exit(1); +				if (fstat(fd, &st2) < 0) +					eprintf("fstat %s:", *argv); +				if (st2.st_size < st1.st_size) { +					fprintf(stderr, "%s: file truncated\n", *argv); +					if (lseek(fd, SEEK_SET, 0) < 0) +						eprintf("lseek:"); +				} +				st1 = st2; +				sleep(1); +			} +		} +	} + +	return ret; +} diff --git a/util/sbase/tar.1 b/util/sbase/tar.1 new file mode 100644 index 00000000..bcc07c21 --- /dev/null +++ b/util/sbase/tar.1 @@ -0,0 +1,76 @@ +.Dd October 8, 2015 +.Dt TAR 1 +.Os sbase +.Sh NAME +.Nm tar +.Nd create, list or extract a tape archive +.Sh SYNOPSIS +.Nm +.Cm x | Cm t | Fl x | Fl t +.Op Fl C Ar dir +.Op Fl J | Fl Z | Fl a | Fl j | Fl z +.Op Fl m +.Op Fl p +.Op Fl f Ar file +.Op Ar file ... +.Nm +.Cm c | Fl c Op Fl C Ar dir +.Op Fl J | Fl Z | Fl a | Fl j | Fl z +.Op Fl h +.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. +For long file paths (>99 bytes), the UStar, 'L' and 'x' header formats are +supported for reading (to a maximum size of PATH_MAX or 255 bytes, depending on +format), and the 'L' format is supported for writing (with unlimited path +size). +Link targets are limited to the UStar maximum of 100 bytes. diff --git a/util/sbase/tar.c b/util/sbase/tar.c new file mode 100644 index 00000000..4d44ec06 --- /dev/null +++ b/util/sbase/tar.c @@ -0,0 +1,662 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#ifndef major +#include <sys/sysmacros.h> +#endif + +#include <assert.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 (sizeof (struct header)) /* must equal 512 bytes */ + +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]; +	char padding[12]; +}; + +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 = ereallocarray(dirtimes, dirtimeslen + 1, sizeof(*dirtimes)); +	dirtimes[dirtimeslen].name = estrdup(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 unsigned +chksum(struct header *h) +{ +	unsigned sum, i; + +	memset(h->chksum, ' ', sizeof(h->chksum)); +	for (i = 0, sum = 0, assert(BLKSIZ == 512); i < BLKSIZ; i++) +		sum += *((unsigned char *)h + i); +	return sum; +} + +static void +putoctal(char *dst, unsigned num, int size) +{ +	if (snprintf(dst, size, "%.*o", size - 1, num) >= size) +		eprintf("putoctal: input number '%o' too large\n", num); +} + +static int +archive(const char *path) +{ +	static const struct header blank = { +		"././@LongLink", "0000600", "0000000", "0000000", "00000000000", +		"00000000000"  , "       ",  AREG    , ""       , "ustar", "00", +	}; +	char   b[BLKSIZ + BLKSIZ], *p; +	struct header *h = (struct header *)b; +	struct group  *gr; +	struct passwd *pw; +	struct stat st; +	ssize_t l, n, 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 = blank; +	n  = strlcpy(h->name, path, sizeof(h->name)); +	if (n >= sizeof(h->name)) { +		*++h = blank; +		h->type = 'L'; +		putoctal(h->size,   n,         sizeof(h->size)); +		putoctal(h->chksum, chksum(h), sizeof(h->chksum)); +		ewrite(tarfd, (char *)h, BLKSIZ); + +		for (p = (char *)path; n > 0; n -= BLKSIZ, p += BLKSIZ) { +			if (n < BLKSIZ) { +				p = memcpy(h--, p, n); +				memset(p + n, 0, BLKSIZ - n); +			} +			ewrite(tarfd, p, BLKSIZ); +		} +	} + +	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->mtime,   (unsigned)st.st_mtime,       sizeof(h->mtime)); +	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, 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; +	} + +	putoctal(h->chksum, chksum(h), 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]) +{ +	struct header *h = (struct header *)b; +	struct timespec times[2]; +	char lname[101], *tmp, *p; +	long mode, major, minor, type, mtime, uid, gid; +	int  fd = -1, lnk = h->type == SYMLINK; + +	if (!mflag && ((mtime = strtol(h->mtime, &p, 8)) < 0 || *p != '\0')) +		eprintf("strtol %s: invalid mtime\n", h->mtime); +	if (strcmp(fname, ".") && strcmp(fname, "./") && remove(fname) < 0) +		if (errno != ENOENT) weprintf("remove %s:", fname); + +	tmp = estrdup(fname); +	mkdirp(dirname(tmp), 0777, 0777); +	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 mode\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 ((lnk ? symlink:link)(lname, fname) < 0) +			eprintf("%s %s -> %s:", lnk ? "symlink":"link", fname, lname); +		lnk++; +		break; +	case DIRECTORY: +		if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0') +			eprintf("strtol %s: invalid mode\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 mode\n", h->mode); +		if ((major = strtol(h->major, &p, 8)) < 0 || *p != '\0') +			eprintf("strtol %s: invalid major device\n", h->major); +		if ((minor = strtol(h->minor, &p, 8)) < 0 || *p != '\0') +			eprintf("strtol %s: invalid minor device\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 mode\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 uid\n", h->uid); +	if ((gid = strtol(h->gid, &p, 8)) < 0 || *p != '\0') +		eprintf("strtol %s: invalid gid\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 (lnk == 1) +		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:", fname); +	if (lnk) { +		if (!getuid() && lchown(fname, uid, gid)) +			weprintf("lchown %s:", fname); +	} else { +		if (!getuid() && chown(fname, uid, gid)) +			weprintf("chown %s:", fname); +		if (chmod(fname, mode) < 0) +			eprintf("fchmod %s:", 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(int dirfd, const char *name, struct stat *st, void *data, struct recursor *r) +{ +	archive(r->path); +	if (vflag) +		puts(r->path); + +	if (S_ISDIR(st->st_mode)) +		recurse(dirfd, name, NULL, r); +} + +static void +sanitize(struct header *h) +{ +	size_t i, j, l; +	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++){ +		j = 0, l = fields[i].l - 1; +		for (; j < l && fields[i].f[j] == ' '; j++); +		for (; j <= l; j++) +			if (fields[i].f[j] == ' ') +				fields[i].f[j] = '\0'; +		if (fields[i].f[l]) +			eprintf("numeric field #%d (%.*s) is not null or space terminated\n", +			        i, l+1, fields[i].f); +	} +} + +static void +chktar(struct header *h) +{ +	const char *reason; +	char tmp[sizeof h->chksum], *err; +	long sum, i; + +	if (h->prefix[0] == '\0' && h->name[0] == '\0') { +		reason = "empty filename"; +		goto bad; +	} +	if (h->magic[0] && strncmp("ustar", h->magic, 5)) { +		reason = "not ustar format"; +		goto bad; +	} +	memcpy(tmp, h->chksum, sizeof(tmp)); +	for (i = sizeof(tmp)-1; i > 0 && tmp[i] == ' '; i--) { +		tmp[i] = '\0'; +	} +	sum = strtol(tmp, &err, 8); +	if (sum < 0 || sum >= BLKSIZ*256 || *err != '\0') { +		reason = "invalid checksum"; +		goto bad; +	} +	if (sum != chksum(h)) { +		reason = "incorrect checksum"; +		goto bad; +	} +	memcpy(h->chksum, tmp, sizeof(tmp)); +	return; +bad: +	eprintf("malformed tar archive: %s\n", reason); +} + +static void +xt(int argc, char *argv[], int mode) +{ +	long size, l; +	char b[BLKSIZ], fname[l = PATH_MAX + 1], *p, *q = NULL; +	int i, m, n; +	int (*fn)(char *, ssize_t, char[BLKSIZ]) = (mode == 'x') ? unarchive : print; +	struct timespec times[2]; +	struct header *h = (struct header *)b; +	struct dirtime *dirtime; + +	while (eread(tarfd, b, BLKSIZ) > 0 && (h->name[0] || h->prefix[0])) { +		chktar(h); +		sanitize(h); + +		if ((size = strtol(h->size, &p, 8)) < 0 || *p != '\0') +			eprintf("strtol %s: invalid size\n", h->size); + +		/* Long file path is read directly into fname*/ +		if (h->type == 'L' || h->type == 'x' || h->type == 'g') { + +			/* Read header only up to size of fname buffer */ +			for (q = fname; q < fname+size; q += BLKSIZ) { +				if (q + BLKSIZ >= fname + l) +					eprintf("name exceeds buffer: %.*s\n", q-fname, fname); +				eread(tarfd, q, BLKSIZ); +			} + +			/* Convert pax x header with 'path=' field into L header */ +			if (h->type == 'x') for (q = fname; q < fname+size-16; q += n) { +				if ((n = strtol(q, &p, 10)) < 0 || *p != ' ') +					eprintf("strtol %.*s: invalid number\n", p+1-q, q); +				if (n && strncmp(p+1, "path=", 5) == 0) { +					memmove(fname, p+6, size = q+n - p-6 - 1); +					h->type = 'L'; +					break; +				} +			} +			fname[size] = '\0'; + +			/* Non L-like header (eg. pax 'g') is skipped by setting q=null */ +			if (h->type != 'L') +				q = NULL; +			continue; +		} + +		/* Ustar path is copied into fname if no L header (ie: q is NULL) */ +		if (!q) { +			m = sizeof h->prefix, n = sizeof h->name; +			p = "/" + !h->prefix[0]; +			snprintf(fname, l, "%.*s%s%.*s", m, h->prefix, p, n, h->name); +		} +		q = NULL; + +		/* If argc > 0 then only extract the given files/dirs */ +		if (argc) { +			for (i = 0; i < argc; i++) { +				if (strncmp(argv[i], fname, n = strlen(argv[i])) == 0) +					if (strchr("/", fname[n]) || argv[i][n-1] == '/') +						break; +			} +			if (i == argc) { +				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; +	} +} + +char **args; +int argn; + +static void +usage(void) +{ +	eprintf("usage: %s [x | t | -x | -t] [-C dir] [-J | -Z | -a | -j | -z] [-m] [-p] " +	        "[-f file] [file ...]\n" +	        "       %s [c | -c] [-C dir] [-J | -Z | -a | -j | -z] [-h] path ... " +	        "[-f file]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ +	struct recursor r = { .fn = c, .follow = 'P', .flags = DIRFIRST }; +	struct stat st; +	char *file = NULL, *dir = ".", mode = '\0'; +	int fd; + +	argv0 = argv[0]; +	if (argc > 1 && strchr("cxt", mode = *argv[1])) +		*(argv[1]+1) ? *argv[1] = '-' : (*++argv = argv0, --argc); + +	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; +	case 'p': +		break;  /* Do nothing as already default behaviour */ +	default: +		usage(); +	} ARGEND + +	switch (mode) { +	case 'c': +		if (!argc) +			usage(); +		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(AT_FDCWD, *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, "-cdf"); +			close(fd); +		} + +		if (chdir(dir) < 0) +			eprintf("chdir %s:", dir); +		xt(argc, argv, mode); +		break; +	default: +		usage(); +	} + +	return recurse_status; +} diff --git a/util/sbase/tee.1 b/util/sbase/tee.1 new file mode 100644 index 00000000..eaf2e202 --- /dev/null +++ b/util/sbase/tee.1 @@ -0,0 +1,26 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/tee.c b/util/sbase/tee.c new file mode 100644 index 00000000..eac106ca --- /dev/null +++ b/util/sbase/tee.c @@ -0,0 +1,60 @@ +/* See LICENSE file for copyright and license details. */ +#include <fcntl.h> +#include <signal.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s [-ai] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	int *fds = NULL; +	size_t i, nfds; +	ssize_t n; +	int ret = 0, aflag = O_TRUNC, iflag = 0; +	char buf[BUFSIZ]; + +	ARGBEGIN { +	case 'a': +		aflag = O_APPEND; +		break; +	case 'i': +		iflag = 1; +		break; +	default: +		usage(); +	} ARGEND + +	if (iflag && signal(SIGINT, SIG_IGN) == SIG_ERR) +		eprintf("signal:"); +	nfds = argc + 1; +	fds = ecalloc(nfds, sizeof(*fds)); + +	for (i = 0; i < argc; i++) { +		if ((fds[i] = open(argv[i], O_WRONLY|O_CREAT|aflag, 0666)) < 0) { +			weprintf("open %s:", argv[i]); +			ret = 1; +		} +	} +	fds[i] = 1; + +	while ((n = read(0, buf, sizeof(buf))) > 0) { +		for (i = 0; i < nfds; i++) { +			if (fds[i] >= 0 && writeall(fds[i], buf, n) < 0) { +				weprintf("write %s:", (i != argc) ? argv[i] : "<stdout>"); +				fds[i] = -1; +				ret = 1; +			} +		} +	} +	if (n < 0) +		eprintf("read <stdin>:"); + +	return ret; +} diff --git a/util/sbase/test.1 b/util/sbase/test.1 new file mode 100644 index 00000000..c7d4f50c --- /dev/null +++ b/util/sbase/test.1 @@ -0,0 +1,131 @@ +.Dd October 8, 2015 +.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 ) . +.It Ar f1 ( Fl ef | Fl ot | Fl nt ) Ar f2 +True if file +.Ar f1 +(refer to the same inode as +.Op Fl ef +| has an older mtime than +.Op Fl ot +| has a newer mtime than +.Op Fl nt ) +file +.Ar f2 . +.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 +POSIX.1-2013. diff --git a/util/sbase/test.c b/util/sbase/test.c new file mode 100644 index 00000000..1e88a221 --- /dev/null +++ b/util/sbase/test.c @@ -0,0 +1,247 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include <ctype.h> +#include <fcntl.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 !faccessat(AT_FDCWD, s, F_OK, AT_EACCESS); } +static int unary_r(char *s) { return !faccessat(AT_FDCWD, s, R_OK, AT_EACCESS); } +static int unary_w(char *s) { return !faccessat(AT_FDCWD, s, W_OK, AT_EACCESS); } +static int unary_x(char *s) { return !faccessat(AT_FDCWD, s, X_OK, AT_EACCESS); } + +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; +	union { +		int (*u)(char *); +		int (*b)(char *, char *); +	} func; +}; + +static struct test unary[] = { +	{ "-b", { .u = unary_b } }, +	{ "-c", { .u = unary_c } }, +	{ "-d", { .u = unary_d } }, +	{ "-e", { .u = unary_e } }, +	{ "-f", { .u = unary_f } }, +	{ "-g", { .u = unary_g } }, +	{ "-h", { .u = unary_h } }, +	{ "-k", { .u = unary_k } }, +	{ "-L", { .u = unary_h } }, +	{ "-n", { .u = unary_n } }, +	{ "-p", { .u = unary_p } }, +	{ "-r", { .u = unary_r } }, +	{ "-S", { .u = unary_S } }, +	{ "-s", { .u = unary_s } }, +	{ "-t", { .u = unary_t } }, +	{ "-u", { .u = unary_u } }, +	{ "-w", { .u = unary_w } }, +	{ "-x", { .u = unary_x } }, +	{ "-z", { .u = unary_z } }, + +	{ NULL }, +}; + +static struct test binary[] = { +	{ "="  , { .b = binary_se } }, +	{ "!=" , { .b = binary_sn } }, +	{ "-eq", { .b = binary_eq } }, +	{ "-ne", { .b = binary_ne } }, +	{ "-gt", { .b = binary_gt } }, +	{ "-ge", { .b = binary_ge } }, +	{ "-lt", { .b = binary_lt } }, +	{ "-le", { .b = binary_le } }, +	{ "-ef", { .b = binary_ef } }, +	{ "-ot", { .b = binary_ot } }, +	{ "-nt", { .b = binary_nt } }, + +	{ 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.u(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.b(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, argv0 ? (argc--, argv++) : (void *)0; + +	len = argv0 ? strlen(argv0) : 0; +	if (len && argv0[--len] == '[' && (!len || argv0[--len] == '/') && strcmp(argv[--argc], "]")) +		enprintf(2, "no matching ]\n"); + +	if (argc > 4) +		enprintf(2, "too many arguments\n"); + +	return !narg[argc](argv); +} diff --git a/util/sbase/text.h b/util/sbase/text.h new file mode 100644 index 00000000..9858592b --- /dev/null +++ b/util/sbase/text.h @@ -0,0 +1,16 @@ +/* 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 *); + +int linecmp(struct line *, struct line *); diff --git a/util/sbase/tftp.1 b/util/sbase/tftp.1 new file mode 100644 index 00000000..4ad73a23 --- /dev/null +++ b/util/sbase/tftp.1 @@ -0,0 +1,32 @@ +.Dd October 8, 2015 +.Dt TFTP 1 +.Os sbase +.Sh NAME +.Nm tftp +.Nd trivial file transfer protocol client +.Sh SYNOPSIS +.Nm +.Fl h Ar host +.Op Fl p Ar port +.Op Fl x | c +.Ar file +.Sh DESCRIPTION +.Nm +is a client that implements the trivial file transfer protocol over +either IPv4 or IPv6 as specified in RFC 1350. +It can be used to transfer files to and from remote machines. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl h Ar host +Set the remote hostname. +.It Fl p Ar port +Set the remote port. +It defaults to port 69. +.It Fl x +Extract a file from the server. +This is the default if no flags are specified. +Output goes to stdout. +.It Fl c +Create a file on the server. +Input comes from stdin. +.El diff --git a/util/sbase/tftp.c b/util/sbase/tftp.c new file mode 100644 index 00000000..0a099ff2 --- /dev/null +++ b/util/sbase/tftp.c @@ -0,0 +1,309 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/time.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <netdb.h> +#include <netinet/in.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +#define BLKSIZE 512 +#define HDRSIZE 4 +#define PKTSIZE (BLKSIZE + HDRSIZE) + +#define TIMEOUT_SEC 5 +/* transfer will time out after NRETRIES * TIMEOUT_SEC */ +#define NRETRIES 5 + +#define RRQ  1 +#define WWQ  2 +#define DATA 3 +#define ACK  4 +#define ERR  5 + +static char *errtext[] = { +	"Undefined", +	"File not found", +	"Access violation", +	"Disk full or allocation exceeded", +	"Illegal TFTP operation", +	"Unknown transfer ID", +	"File already exists", +	"No such user" +}; + +static struct sockaddr_storage to; +static socklen_t tolen; +static int timeout; +static int state; +static int s; + +static int +packreq(unsigned char *buf, int op, char *path, char *mode) +{ +	unsigned char *p = buf; + +	*p++ = op >> 8; +	*p++ = op & 0xff; +	if (strlen(path) + 1 > 256) +		eprintf("filename too long\n"); +	memcpy(p, path, strlen(path) + 1); +	p += strlen(path) + 1; +	memcpy(p, mode, strlen(mode) + 1); +	p += strlen(mode) + 1; +	return p - buf; +} + +static int +packack(unsigned char *buf, int blkno) +{ +	buf[0] = ACK >> 8; +	buf[1] = ACK & 0xff; +	buf[2] = blkno >> 8; +	buf[3] = blkno & 0xff; +	return 4; +} + +static int +packdata(unsigned char *buf, int blkno) +{ +	buf[0] = DATA >> 8; +	buf[1] = DATA & 0xff; +	buf[2] = blkno >> 8; +	buf[3] = blkno & 0xff; +	return 4; +} + +static int +unpackop(unsigned char *buf) +{ +	return (buf[0] << 8) | (buf[1] & 0xff); +} + +static int +unpackblkno(unsigned char *buf) +{ +	return (buf[2] << 8) | (buf[3] & 0xff); +} + +static int +unpackerrc(unsigned char *buf) +{ +	int errc; + +	errc = (buf[2] << 8) | (buf[3] & 0xff); +	if (errc < 0 || errc >= LEN(errtext)) +		eprintf("bad error code: %d\n", errc); +	return errc; +} + +static int +writepkt(unsigned char *buf, int len) +{ +	int n; + +	n = sendto(s, buf, len, 0, (struct sockaddr *)&to, +	           tolen); +	if (n < 0) +		if (errno != EINTR) +			eprintf("sendto:"); +	return n; +} + +static int +readpkt(unsigned char *buf, int len) +{ +	int n; + +	n = recvfrom(s, buf, len, 0, (struct sockaddr *)&to, +	             &tolen); +	if (n < 0) { +		if (errno != EINTR && errno != EWOULDBLOCK) +			eprintf("recvfrom:"); +		timeout++; +		if (timeout == NRETRIES) +			eprintf("transfer timed out\n"); +	} else { +		timeout = 0; +	} +	return n; +} + +static void +getfile(char *file) +{ +	unsigned char buf[PKTSIZE]; +	int n, op, blkno, nextblkno = 1, done = 0; + +	state = RRQ; +	for (;;) { +		switch (state) { +		case RRQ: +			n = packreq(buf, RRQ, file, "octet"); +			writepkt(buf, n); +			n = readpkt(buf, sizeof(buf)); +			if (n > 0) { +				op = unpackop(buf); +				if (op != DATA && op != ERR) +					eprintf("bad opcode: %d\n", op); +				state = op; +			} +			break; +		case DATA: +			n -= HDRSIZE; +			if (n < 0) +				eprintf("truncated packet\n"); +			blkno = unpackblkno(buf); +			if (blkno == nextblkno) { +				nextblkno++; +				write(1, &buf[HDRSIZE], n); +			} +			if (n < BLKSIZE) +				done = 1; +			state = ACK; +			break; +		case ACK: +			n = packack(buf, blkno); +			writepkt(buf, n); +			if (done) +				return; +			n = readpkt(buf, sizeof(buf)); +			if (n > 0) { +				op = unpackop(buf); +				if (op != DATA && op != ERR) +					eprintf("bad opcode: %d\n", op); +				state = op; +			} +			break; +		case ERR: +			eprintf("error: %s\n", errtext[unpackerrc(buf)]); +		} +	} +} + +static void +putfile(char *file) +{ +	unsigned char inbuf[PKTSIZE], outbuf[PKTSIZE]; +	int inb, outb, op, blkno, nextblkno = 0, done = 0; + +	state = WWQ; +	for (;;) { +		switch (state) { +		case WWQ: +			outb = packreq(outbuf, WWQ, file, "octet"); +			writepkt(outbuf, outb); +			inb = readpkt(inbuf, sizeof(inbuf)); +			if (inb > 0) { +				op = unpackop(inbuf); +				if (op != ACK && op != ERR) +					eprintf("bad opcode: %d\n", op); +				state = op; +			} +			break; +		case DATA: +			if (blkno == nextblkno) { +				nextblkno++; +				packdata(outbuf, nextblkno); +				outb = read(0, &outbuf[HDRSIZE], BLKSIZE); +				if (outb < BLKSIZE) +					done = 1; +			} +			writepkt(outbuf, outb + HDRSIZE); +			inb = readpkt(inbuf, sizeof(inbuf)); +			if (inb > 0) { +				op = unpackop(inbuf); +				if (op != ACK && op != ERR) +					eprintf("bad opcode: %d\n", op); +				state = op; +			} +			break; +		case ACK: +			if (inb < HDRSIZE) +				eprintf("truncated packet\n"); +			blkno = unpackblkno(inbuf); +			if (blkno == nextblkno) +				if (done) +					return; +			state = DATA; +			break; +		case ERR: +			eprintf("error: %s\n", errtext[unpackerrc(inbuf)]); +		} +	} +} + +static void +usage(void) +{ +	eprintf("usage: %s -h host [-p port] [-x | -c] file\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	struct addrinfo hints, *res, *r; +	struct timeval tv; +	char *host = NULL, *port = "tftp"; +	void (*fn)(char *) = getfile; +	int ret; + +	ARGBEGIN { +	case 'h': +		host = EARGF(usage()); +		break; +	case 'p': +		port = EARGF(usage()); +		break; +	case 'x': +		fn = getfile; +		break; +	case 'c': +		fn = putfile; +		break; +	default: +		usage(); +	} ARGEND + +	if (!host || !argc) +		usage(); + +	memset(&hints, 0, sizeof(hints)); +	hints.ai_family = AF_UNSPEC; +	hints.ai_socktype = SOCK_DGRAM; +	hints.ai_protocol = IPPROTO_UDP; +	ret = getaddrinfo(host, port, &hints, &res); +	if (ret) +		eprintf("getaddrinfo: %s\n", gai_strerror(ret)); + +	for (r = res; r; r = r->ai_next) { +		if (r->ai_family != AF_INET && +		    r->ai_family != AF_INET6) +			continue; +		s = socket(r->ai_family, r->ai_socktype, +		           r->ai_protocol); +		if (s < 0) +			continue; +		break; +	} +	if (!r) +		eprintf("cannot create socket\n"); +	memcpy(&to, r->ai_addr, r->ai_addrlen); +	tolen = r->ai_addrlen; +	freeaddrinfo(res); + +	tv.tv_sec = TIMEOUT_SEC; +	tv.tv_usec = 0; +	if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) +		eprintf("setsockopt:"); + +	fn(argv[0]); +	return 0; +} diff --git a/util/sbase/time.1 b/util/sbase/time.1 new file mode 100644 index 00000000..645da5ef --- /dev/null +++ b/util/sbase/time.1 @@ -0,0 +1,45 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/time.c b/util/sbase/time.c new file mode 100644 index 00000000..60a8c8df --- /dev/null +++ b/util/sbase/time.c @@ -0,0 +1,73 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/times.h> +#include <sys/wait.h> + +#include <errno.h> +#include <stdio.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s [-p] cmd [arg ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	pid_t pid; +	struct tms tms; /* user and sys times */ +	clock_t r0, r1; /* real time */ +	long ticks;     /* per second */ +	int status, savederrno, ret = 0; + +	ARGBEGIN { +	case 'p': +		break; +	default: +		usage(); +	} ARGEND + +	if (!argc) +		usage(); + +	if ((ticks = sysconf(_SC_CLK_TCK)) <= 0) +		eprintf("sysconf _SC_CLK_TCK:"); + +	if ((r0 = times(&tms)) == (clock_t)-1) +		eprintf("times:"); + +	switch ((pid = fork())) { +	case -1: +		eprintf("fork:"); +	case 0: +		execvp(argv[0], argv); +		savederrno = errno; +		weprintf("execvp %s:", argv[0]); +		_exit(126 + (savederrno == ENOENT)); +	default: +		break; +	} +	waitpid(pid, &status, 0); + +	if ((r1 = times(&tms)) == (clock_t)-1) +		eprintf("times:"); + +	if (WIFSIGNALED(status)) { +		fprintf(stderr, "Command terminated by signal %d\n", +		        WTERMSIG(status)); +		ret = 128 + WTERMSIG(status); +	} + +	fprintf(stderr, "real %f\nuser %f\nsys %f\n", +	        (r1 - r0)      / (double)ticks, +	        tms.tms_cutime / (double)ticks, +	        tms.tms_cstime / (double)ticks); + +	if (WIFEXITED(status)) +		ret = WEXITSTATUS(status); + +	return ret; +} diff --git a/util/sbase/touch.1 b/util/sbase/touch.1 new file mode 100644 index 00000000..80c6ebb6 --- /dev/null +++ b/util/sbase/touch.1 @@ -0,0 +1,63 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +Except for fractional seconds with +.Op Fl d . +.Pp +The +.Op Fl T +flag is an extension to that specification. diff --git a/util/sbase/touch.c b/util/sbase/touch.c new file mode 100644 index 00000000..6e63bf80 --- /dev/null +++ b/util/sbase/touch.c @@ -0,0 +1,159 @@ +/* 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 "util.h" + +static int aflag; +static int cflag; +static int mflag; +static struct timespec times[2] = {{.tv_nsec = UTIME_NOW}}; + +static void +touch(const char *file) +{ +	int fd, ret; + +	if (utimensat(AT_FDCWD, file, times, 0) == 0) +		return; +	if (errno != ENOENT) +		eprintf("utimensat %s:", file); +	if (cflag) +		return; +	if ((fd = open(file, O_WRONLY | O_CREAT | O_EXCL, 0666)) < 0) +		eprintf("open %s:", file); +	ret = futimens(fd, times); +	close(fd); +	if (ret < 0) +		eprintf("futimens %s:", file); +} + +static time_t +parsetime(char *str) +{ +	time_t now; +	struct tm *cur, t = { 0 }; +	int zulu = 0; +	char *format; +	size_t len = strlen(str); + +	if ((now = time(NULL)) == -1) +		eprintf("time:"); +	if (!(cur = localtime(&now))) +		eprintf("localtime:"); +	t.tm_isdst = -1; + +	switch (len) { +	/* -t flag argument */ +	case 8: +		t.tm_year = cur->tm_year; +		format = "%m%d%H%M"; +		break; +	case 10: +		format = "%y%m%d%H%M"; +		break; +	case 11: +		t.tm_year = cur->tm_year; +		format = "%m%d%H%M.%S"; +		break; +	case 12: +		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)) +		eprintf("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; + +	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_nsec = 0; +		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); +		times[0].tv_nsec = 0; +		break; +	default: +		usage(); +	} ARGEND + +	if (!argc) +		usage(); +	if (!aflag && !mflag) +		aflag = mflag = 1; +	if (!ref) +		times[1] = times[0]; +	if (!aflag) +		times[0].tv_nsec = UTIME_OMIT; +	if (!mflag) +		times[1].tv_nsec = UTIME_OMIT; + +	for (; *argv; argc--, argv++) +		touch(*argv); + +	return 0; +} diff --git a/util/sbase/tr.1 b/util/sbase/tr.1 new file mode 100644 index 00000000..087bd4bf --- /dev/null +++ b/util/sbase/tr.1 @@ -0,0 +1,84 @@ +.Dd October 5, 2016 +.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 +.Fl d +is not set, +.Nm +translates from +.Ar set1 +to +.Ar set2 +by index or character class. +.Pp +If +.Ar set2 +is shorter than +.Ar set1 +or +.Ar set1 +is a character class, +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 +POSIX.1-2013. +Except from equivalence classes. diff --git a/util/sbase/tr.c b/util/sbase/tr.c new file mode 100644 index 00000000..c96dbdd3 --- /dev/null +++ b/util/sbase/tr.c @@ -0,0 +1,300 @@ +/* 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] == '*') { +				/* 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, MAX(set1ranges, 1)); +				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, 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 || (argc == 2 && sflag)) { +		/* sanity checks as we are translating */ +		if (!sflag && !set2ranges && !set2check) +			eprintf("cannot map to an empty set.\n"); +		if (set2check && set2check != islowerrune && +		    set2check != isupperrune) { +			eprintf("can only map to 'lower' and 'upper' class.\n"); +		} +	} +read: +	if (!efgetrune(&r, stdin, "<stdin>")) { +		ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); +		return ret; +	} +	if (argc == 1 && sflag) +		goto write; +	for (i = 0, off1 = 0; i < set1ranges; off1 += rangelen(set1[i]), i++) { +		if (set1[i].start <= r && r <= set1[i].end) { +			if (dflag) { +				if (cflag) +					goto write; +				else +					goto read; +			} +			if (cflag) +				goto write; + +			/* map r to set2 */ +			if (set2check) { +				if (set2check == islowerrune) +					r = tolowerrune(r); +				else +					r = toupperrune(r); +			} else { +				off1 += r - set1[i].start; +				if (off1 > setlen(set2, set2ranges) - 1) { +					r = set2[set2ranges - 1].end; +					goto write; +				} +				for (m = 0, off2 = 0; m < set2ranges; m++) { +					if (off2 + rangelen(set2[m]) > off1) { +						m++; +						break; +					} +					off2 += rangelen(set2[m]); +				} +				m--; +				r = set2[m].start + (off1 - off2) / set2[m].quant; +			} +			goto write; +		} +	} +	if (set1check && set1check(r)) { +		if (cflag) +			goto write; +		if (dflag) +			goto read; +		if (set2check) { +			if (set2check == islowerrune) +				r = tolowerrune(r); +			else +				r = toupperrune(r); +		} else { +			r = set2[set2ranges - 1].end; +		} +		goto write; +	} +	if (!dflag && cflag) { +		if (set2check) { +			if (set2check == islowerrune) +				r = tolowerrune(r); +			else +				r = toupperrune(r); +		} else { +			r = set2[set2ranges - 1].end; +		} +		goto write; +	} +	if (dflag && cflag) +		goto read; +write: +	if (argc == 1 && sflag && r == lastrune) { +		if (set1check && set1check(r)) +			goto read; +		for (i = 0; i < set1ranges; i++) { +			if (set1[i].start <= r && r <= set1[i].end) +				goto read; +		} +	} +	if (argc == 2 && sflag && r == lastrune) { +		if (set2check && set2check(r)) +			goto read; +		for (i = 0; i < set2ranges; i++) { +			if (set2[i].start <= r && r <= set2[i].end) +				goto read; +		} +	} +	efputrune(&r, stdout, "<stdout>"); +	lastrune = r; +	goto read; +} diff --git a/util/sbase/true.1 b/util/sbase/true.1 new file mode 100644 index 00000000..7c2104a6 --- /dev/null +++ b/util/sbase/true.1 @@ -0,0 +1,13 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/true.c b/util/sbase/true.c new file mode 100644 index 00000000..cb081ec0 --- /dev/null +++ b/util/sbase/true.c @@ -0,0 +1,6 @@ +/* See LICENSE file for copyright and license details. */ +int +main(void) +{ +	return 0; +} diff --git a/util/sbase/tsort.1 b/util/sbase/tsort.1 new file mode 100644 index 00000000..b2e0c6ae --- /dev/null +++ b/util/sbase/tsort.1 @@ -0,0 +1,70 @@ +.Dd February 16, 2016 +.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 +POSIX.1-2013. diff --git a/util/sbase/tsort.c b/util/sbase/tsort.c new file mode 100644 index 00000000..f147e3b2 --- /dev/null +++ b/util/sbase/tsort.c @@ -0,0 +1,209 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> + +#include "util.h" + +enum { WHITE = 0, GREY, BLACK }; + +struct vertex; + +struct edge { +	struct vertex *to; +	struct edge *next; +}; + +struct vertex { +	char *name; +	struct vertex *next; +	struct edge edges; +	size_t in_edges; +	int colour; +}; + +static struct vertex graph; + +static void +find_vertex(const char *name, struct vertex **it, struct vertex **prev) +{ +	for (*prev = &graph; (*it = (*prev)->next); *prev = *it) { +		int cmp = strcmp(name, (*it)->name); +		if (cmp > 0) +			continue; +		if (cmp < 0) +			*it = 0; +		return; +	} +} + +static void +find_edge(struct vertex *from, const char *to, struct edge **it, struct edge **prev) +{ +	for (*prev = &(from->edges); (*it = (*prev)->next); *prev = *it) { +		int cmp = strcmp(to, (*it)->to->name); +		if (cmp > 0) +			continue; +		if (cmp < 0) +			*it = 0; +		return; +	} +} + +static struct vertex * +add_vertex(char *name) +{ +	struct vertex *vertex; +	struct vertex *prev; + +	find_vertex(name, &vertex, &prev); +	if (vertex) +		return vertex; + +	vertex = encalloc(2, 1, sizeof(*vertex)); +	vertex->name = name; +	vertex->next = prev->next; +	prev->next = vertex; + +	return vertex; +} + +static struct edge * +add_edge(struct vertex *from, struct vertex* to) +{ +	struct edge *edge; +	struct edge *prev; + +	find_edge(from, to->name, &edge, &prev); +	if (edge) +		return edge; + +	edge = encalloc(2, 1, sizeof(*edge)); +	edge->to = to; +	edge->next = prev->next; +	prev->next = edge; +	to->in_edges += 1; + +	return edge; +} + +static void +load_graph(FILE *fp) +{ +#define SKIP(VAR, START, FUNC)  for (VAR = START; FUNC(*VAR) && *VAR; VAR++) +#define TOKEN_END(P)            do { if (*P) *P++ = 0; else P = 0; } while (0) + +	char *line = 0; +	size_t size = 0; +	ssize_t len; +	char *p; +	char *name; +	struct vertex *from = 0; + +	while ((len = getline(&line, &size, fp)) != -1) { +		if (line[len - 1] == '\n') +			line[--len] = 0; +		for (p = line; p;) { +			SKIP(name, p, isspace); +			if (!*name) +				break; +			SKIP(p, name, !isspace); +			TOKEN_END(p); +			if (!from) { +				from = add_vertex(enstrdup(2, name)); +			} else if (strcmp(from->name, name)) { +				add_edge(from, add_vertex(enstrdup(2, name))); +				from = 0; +			} else { +				from = 0; +			} +		} +	} + +	free(line); + +	if (from) +		enprintf(2, "odd number of tokens in input\n"); +} + +static int +sort_graph_visit(struct vertex *u) +{ +	struct edge *e = &(u->edges); +	struct vertex *v; +	int r = 0; +	u->colour = GREY; +	printf("%s\n", u->name); +	while ((e = e->next)) { +		v = e->to; +		if (v->colour == WHITE) { +			v->in_edges -= 1; +			if (v->in_edges == 0) +				r |= sort_graph_visit(v); +		} else if (v->colour == GREY) { +			r = 1; +			fprintf(stderr, "%s: loop detected between %s and %s\n", +			        argv0, u->name, v->name); +		} +	} +	u->colour = BLACK; +	return r; +} + +static int +sort_graph(void) +{ +	struct vertex *u, *prev; +	int r = 0; +	size_t in_edges; +	for (in_edges = 0; graph.next; in_edges++) { +		for (prev = &graph; (u = prev->next); prev = u) { +			if (u->colour != WHITE) +				goto unlist; +			if (u->in_edges > in_edges) +				continue; +			r |= sort_graph_visit(u); +		unlist: +			prev->next = u->next; +			u = prev; +		} +	} +	return r; +} + +static void +usage(void) +{ +	enprintf(2, "usage: %s [file]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	FILE *fp = stdin; +	const char *fn = "<stdin>"; +	int ret = 0; + +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	if (argc > 1) +		usage(); +	if (argc && strcmp(*argv, "-")) +		if (!(fp = fopen(fn = *argv, "r"))) +			enprintf(2, "fopen %s:", *argv); + +	memset(&graph, 0, sizeof(graph)); +	load_graph(fp); +	enfshut(2, fp, fn); + +	ret = sort_graph(); + +	if (fshut(stdout, "<stdout>") | fshut(stderr, "<stderr>")) +		ret = 2; + +	return ret; +} diff --git a/util/sbase/tty.1 b/util/sbase/tty.1 new file mode 100644 index 00000000..11580e32 --- /dev/null +++ b/util/sbase/tty.1 @@ -0,0 +1,24 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/tty.c b/util/sbase/tty.c new file mode 100644 index 00000000..65151287 --- /dev/null +++ b/util/sbase/tty.c @@ -0,0 +1,31 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ +	enprintf(2, "usage: %s\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	char *tty; + +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	if (argc) +		usage(); + +	tty = ttyname(STDIN_FILENO); +	puts(tty ? tty : "not a tty"); + +	enfshut(2, stdout, "<stdout>"); +	return !tty; +} diff --git a/util/sbase/uname.1 b/util/sbase/uname.1 new file mode 100644 index 00000000..65c3d318 --- /dev/null +++ b/util/sbase/uname.1 @@ -0,0 +1,35 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/uname.c b/util/sbase/uname.c new file mode 100644 index 00000000..122c1721 --- /dev/null +++ b/util/sbase/uname.c @@ -0,0 +1,62 @@ +/* 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 (argc) +		usage(); + +	if (uname(&u) < 0) +		eprintf("uname:"); + +	if (sflag || !(nflag || rflag || vflag || mflag)) +		putword(stdout, u.sysname); +	if (nflag) +		putword(stdout, u.nodename); +	if (rflag) +		putword(stdout, u.release); +	if (vflag) +		putword(stdout, u.version); +	if (mflag) +		putword(stdout, u.machine); +	putchar('\n'); + +	return fshut(stdout, "<stdout>"); +} diff --git a/util/sbase/unexpand.1 b/util/sbase/unexpand.1 new file mode 100644 index 00000000..1637c090 --- /dev/null +++ b/util/sbase/unexpand.1 @@ -0,0 +1,41 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/unexpand.c b/util/sbase/unexpand.c new file mode 100644 index 00000000..1818691e --- /dev/null +++ b/util/sbase/unexpand.c @@ -0,0 +1,174 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "utf.h" +#include "util.h" + +static int     aflag      = 0; +static size_t *tablist    = NULL; +static size_t  tablistlen = 8; + +static size_t +parselist(const char *s) +{ +	size_t i; +	char  *p, *tmp; + +	tmp = estrdup(s); +	for (i = 0; (p = strsep(&tmp, " ,")); i++) { +		if (*p == '\0') +			eprintf("empty field in tablist\n"); +		tablist = ereallocarray(tablist, i + 1, sizeof(*tablist)); +		tablist[i] = estrtonum(p, 1, MIN(LLONG_MAX, SIZE_MAX)); +		if (i > 0 && tablist[i - 1] >= tablist[i]) +			eprintf("tablist must be ascending\n"); +	} +	tablist = ereallocarray(tablist, i + 1, sizeof(*tablist)); + +	return i; +} + +static void +unexpandspan(size_t last, size_t col) +{ +	size_t off, i, j; +	Rune r; + +	if (tablistlen == 1) { +		i = 0; +		off = last % tablist[i]; + +		if ((col - last) + off >= tablist[i] && last < col) +			last -= off; + +		r = '\t'; +		for (; last + tablist[i] <= col; last += tablist[i]) +			efputrune(&r, stdout, "<stdout>"); +		r = ' '; +		for (; last < col; last++) +			efputrune(&r, stdout, "<stdout>"); +	} else { +		for (i = 0; i < tablistlen; i++) +			if (col < tablist[i]) +				break; +		for (j = 0; j < tablistlen; j++) +			if (last < tablist[j]) +				break; +		r = '\t'; +		for (; j < i; j++) { +			efputrune(&r, stdout, "<stdout>"); +			last = tablist[j]; +		} +		r = ' '; +		for (; last < col; last++) +			efputrune(&r, stdout, "<stdout>"); +	} +} + +static void +unexpand(const char *file, FILE *fp) +{ +	Rune r; +	size_t last = 0, col = 0, i; +	int bol = 1; + +	while (efgetrune(&r, fp, file)) { +		switch (r) { +		case ' ': +			if (!bol && !aflag) +				last++; +			col++; +			break; +		case '\t': +			if (tablistlen == 1) { +				if (!bol && !aflag) +					last += tablist[0] - col % tablist[0]; +				col += tablist[0] - col % tablist[0]; +			} else { +				for (i = 0; i < tablistlen; i++) +					if (col < tablist[i]) +						break; +				if (!bol && !aflag) +					last = tablist[i]; +				col = tablist[i]; +			} +			break; +		case '\b': +			if (bol || aflag) +				unexpandspan(last, col); +			col -= (col > 0); +			last = col; +			bol = 0; +			break; +		case '\n': +			if (bol || aflag) +				unexpandspan(last, col); +			last = col = 0; +			bol = 1; +			break; +		default: +			if (bol || aflag) +				unexpandspan(last, col); +			last = ++col; +			bol = 0; +			break; +		} +		if ((r != ' ' && r != '\t') || (!aflag && !bol)) +			efputrune(&r, stdout, "<stdout>"); +	} +	if (last < col && (bol || aflag)) +		unexpandspan(last, col); +} + +static void +usage(void) +{ +	eprintf("usage: %s [-a] [-t tablist] [file ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	FILE *fp; +	int ret = 0; +	char *tl = "8"; + +	ARGBEGIN { +	case 't': +		tl = EARGF(usage()); +		if (!*tl) +			eprintf("tablist cannot be empty\n"); +		/* Fallthrough: -t implies -a */ +	case 'a': +		aflag = 1; +		break; +	default: +		usage(); +	} ARGEND + +	tablistlen = parselist(tl); + +	if (!argc) { +		unexpand("<stdin>", stdin); +	} else { +		for (; *argv; argc--, argv++) { +			if (!strcmp(*argv, "-")) { +				*argv = "<stdin>"; +				fp = stdin; +			} else if (!(fp = fopen(*argv, "r"))) { +				weprintf("fopen %s:", *argv); +				ret = 1; +				continue; +			} +			unexpand(*argv, fp); +			if (fp != stdin && fshut(fp, *argv)) +				ret = 1; +		} +	} + +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/uniq.1 b/util/sbase/uniq.1 new file mode 100644 index 00000000..8ed1a015 --- /dev/null +++ b/util/sbase/uniq.1 @@ -0,0 +1,45 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/uniq.c b/util/sbase/uniq.c new file mode 100644 index 00000000..f1ad6a7b --- /dev/null +++ b/util/sbase/uniq.c @@ -0,0 +1,144 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "text.h" +#include "util.h" + +static const char *countfmt = ""; +static int dflag = 0; +static int uflag = 0; +static int fskip = 0; +static int sskip = 0; + +static struct line prevl; +static ssize_t prevoff    = -1; +static long prevlinecount = 0; + +static size_t +uniqskip(struct line *l) +{ +	size_t i; +	int f = fskip, s = sskip; + +	for (i = 0; i < l->len && f; --f) { +		while (isblank(l->data[i])) +			i++; +		while (i < l->len && !isblank(l->data[i])) +			i++; +	} +	for (; s && i < l->len && l->data[i] != '\n'; --s, i++) +		; + +	return i; +} + +static void +uniqline(FILE *ofp, struct line *l) +{ +	size_t loff; + +	if (l) { +		loff = uniqskip(l); + +		if (prevoff >= 0 && (l->len - loff) == (prevl.len - prevoff) && +		    !memcmp(l->data + loff, prevl.data + prevoff, l->len - loff)) { +			++prevlinecount; +			return; +		} +	} + +	if (prevoff >= 0) { +		if ((prevlinecount == 1 && !dflag) || +		    (prevlinecount != 1 && !uflag)) { +			if (*countfmt) +				fprintf(ofp, countfmt, prevlinecount); +			fwrite(prevl.data, 1, prevl.len, ofp); +		} +		prevoff = -1; +	} + +	if (l) { +		if (!prevl.data || l->len >= prevl.len) { +			prevl.data = erealloc(prevl.data, l->len); +		} +		prevl.len = l->len; +		memcpy(prevl.data, l->data, prevl.len); +		prevoff = loff; +	} +	prevlinecount = 1; +} + +static void +uniq(FILE *fp, FILE *ofp) +{ +	static struct line line; +	static size_t size; +	ssize_t len; + +	while ((len = getline(&line.data, &size, fp)) > 0) { +		line.len = len; +		uniqline(ofp, &line); +	} +} + +static void +uniqfinish(FILE *ofp) +{ +	uniqline(ofp, NULL); +} + +static void +usage(void) +{ +	eprintf("usage: %s [-c] [-d | -u] [-f fields] [-s chars]" +	        " [input [output]]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	FILE *fp[2] = { stdin, stdout }; +	int ret = 0, i; +	char *fname[2] = { "<stdin>", "<stdout>" }; + +	ARGBEGIN { +	case 'c': +		countfmt = "%7ld "; +		break; +	case 'd': +		dflag = 1; +		break; +	case 'u': +		uflag = 1; +		break; +	case 'f': +		fskip = estrtonum(EARGF(usage()), 0, INT_MAX); +		break; +	case 's': +		sskip = estrtonum(EARGF(usage()), 0, INT_MAX); +		break; +	default: +		usage(); +	} ARGEND + +	if (argc > 2) +		usage(); + +	for (i = 0; i < argc; i++) { +		if (strcmp(argv[i], "-")) { +			fname[i] = argv[i]; +			if (!(fp[i] = fopen(argv[i], (i == 0) ? "r" : "w"))) +				eprintf("fopen %s:", argv[i]); +		} +	} + +	uniq(fp[0], fp[1]); +	uniqfinish(fp[1]); + +	ret |= fshut(fp[0], fname[0]) | fshut(fp[1], fname[1]); + +	return ret; +} diff --git a/util/sbase/unlink.1 b/util/sbase/unlink.1 new file mode 100644 index 00000000..777c89da --- /dev/null +++ b/util/sbase/unlink.1 @@ -0,0 +1,19 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/unlink.c b/util/sbase/unlink.c new file mode 100644 index 00000000..c695fa80 --- /dev/null +++ b/util/sbase/unlink.c @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ +#include <unistd.h> + +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s file\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	if (argc != 1) +		usage(); + +	if (unlink(argv[0]) < 0) +		eprintf("unlink: '%s':", argv[0]); + +	return 0; +} diff --git a/util/sbase/utf.h b/util/sbase/utf.h new file mode 100644 index 00000000..8e0707a7 --- /dev/null +++ b/util/sbase/utf.h @@ -0,0 +1,69 @@ +/* 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(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); +size_t utfmemlen(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); + +size_t utftorunestr(const char *, Rune *); +size_t utfntorunestr(const char *, size_t, Rune *); + +int fgetrune(Rune *, FILE *); +int efgetrune(Rune *, FILE *, const char *); +int fputrune(const Rune *, FILE *); +int efputrune(const Rune *, FILE *, const char *); diff --git a/util/sbase/util.h b/util/sbase/util.h new file mode 100644 index 00000000..6b6a084b --- /dev/null +++ b/util/sbase/util.h @@ -0,0 +1,97 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/types.h> + +#include <regex.h> +#include <stddef.h> +#include <stdio.h> +#include <stdarg.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); +void *enreallocarray(int, void *, size_t, 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 *, ...); +void xvprintf(const char *, va_list); + +int confirm(const char*, ...); + +double estrtod(const char *); + +#undef strcasestr +#define strcasestr xstrcasestr +char *strcasestr(const char *, const char *); + +#undef strlcat +#define strlcat xstrlcat +size_t strlcat(char *, const char *, size_t); +size_t estrlcat(char *, const char *, size_t); +#undef strlcpy +#define strlcpy xstrlcpy +size_t strlcpy(char *, const char *, size_t); +size_t estrlcpy(char *, const char *, size_t); + +#undef strsep +#define strsep xstrsep +char *strsep(char **, const char *); + +void strnsubst(char **, const char *, const char *, size_t); + +/* regex */ +int enregcomp(int, regex_t *, const char *, int); +int eregcomp(regex_t *, const char *, int); + +/* io */ +ssize_t writeall(int, const void *, size_t); +int concat(int, const char *, int, const char *); + +/* 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 +#define strtonum xstrtonum +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 *, mode_t, mode_t); +#undef memmem +#define memmem xmemmem +void *memmem(const void *, size_t, const void *, size_t); diff --git a/util/sbase/uudecode.1 b/util/sbase/uudecode.1 new file mode 100644 index 00000000..d6408659 --- /dev/null +++ b/util/sbase/uudecode.1 @@ -0,0 +1,46 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. +.Pp +The +.Op Fl m +flag is an extension to that specification. diff --git a/util/sbase/uudecode.c b/util/sbase/uudecode.c new file mode 100644 index 00000000..1d0bf72a --- /dev/null +++ b/util/sbase/uudecode.c @@ -0,0 +1,282 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +static int mflag = 0; +static int oflag = 0; + +static FILE * +parsefile(const char *fname) +{ +	struct stat st; +	int ret; + +	if (!strcmp(fname, "/dev/stdout") || !strcmp(fname, "-")) +		return stdout; +	ret = lstat(fname, &st); +	/* if it is a new file, try to open it */ +	if (ret < 0 && errno == ENOENT) +		goto tropen; +	if (ret < 0) { +		weprintf("lstat %s:", fname); +		return NULL; +	} +	if (!S_ISREG(st.st_mode)) { +		weprintf("for safety uudecode operates only on regular files and /dev/stdout\n"); +		return NULL; +	} +tropen: +	return fopen(fname, "w"); +} + +static void +parseheader(FILE *fp, const char *s, char **header, mode_t *mode, char **fname) +{ +	static char bufs[PATH_MAX + 18]; /* len header + mode + maxname */ +	char *p, *q; +	size_t n; + +	if (!fgets(bufs, sizeof(bufs), fp)) +		if (ferror(fp)) +			eprintf("%s: read error:", s); +	if (bufs[0] == '\0' || feof(fp)) +		eprintf("empty or nil header string\n"); +	if (!(p = strchr(bufs, '\n'))) +		eprintf("header string too long or non-newline terminated file\n"); +	p = bufs; +	if (!(q = strchr(p, ' '))) +		eprintf("malformed mode string in header, expected ' '\n"); +	*header = bufs; +	*q++ = '\0'; +	p = q; +	/* now header should be null terminated, q points to mode */ +	if (!(q = strchr(p, ' '))) +		eprintf("malformed mode string in header, expected ' '\n"); +	*q++ = '\0'; +	/* now mode should be null terminated, q points to fname */ +	*mode = parsemode(p, *mode, 0); +	n = strlen(q); +	while (n > 0 && (q[n - 1] == '\n' || q[n - 1] == '\r')) +		q[--n] = '\0'; +	if (n > 0) +		*fname = q; +	else +		eprintf("header string does not contain output file\n"); +} + +static const char b64dt[] = { +	-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-2,-2,-2,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +	-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, +	52,53,54,55,56,57,58,59,60,61,-1,-1,-1, 0,-1,-1,-1, 0, 1, 2, 3, 4, 5, 6, +	 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, +	-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, +	49,50,51,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +}; + +static void +uudecodeb64(FILE *fp, FILE *outfp) +{ +	char bufb[60], *pb; +	char out[45], *po; +	size_t n; +	int b = 0, e, t = -1, l = 1; +	unsigned char b24[3] = {0, 0, 0}; + +	while ((n = fread(bufb, 1, sizeof(bufb), fp))) { +		for (pb = bufb, po = out; pb < bufb + n; pb++) { +			if (*pb == '\n') { +				l++; +				continue; +			} else if (*pb == '=') { +				switch (b) { +				case 0: +					/* expected '=' remaining +					 * including footer */ +					if (--t) { +						fwrite(out, 1, +						       (po - out), +						       outfp); +						return; +					} +					continue; +				case 1: +					eprintf("%d: unexpected \"=\"" +					        "appeared\n", l); +				case 2: +					*po++ = b24[0]; +					b = 0; +					t = 5; /* expect 5 '=' */ +					continue; +				case 3: +					*po++ = b24[0]; +					*po++ = b24[1]; +					b = 0; +					t = 6; /* expect 6 '=' */ +					continue; +				} +			} else if ((e = b64dt[(int)*pb]) == -1) +				eprintf("%d: invalid byte \"%c\"\n", l, *pb); +			else if (e == -2) /* whitespace */ +				continue; +			else if (t > 0) /* state is parsing pad/footer */ +				eprintf("%d: invalid byte \"%c\"" +					" after padding\n", +				        l, *pb); +			switch (b) { /* decode next base64 chr based on state */ +				case 0: b24[0] |= e << 2; break; +				case 1: b24[0] |= (e >> 4) & 0x3; +				        b24[1] |= (e & 0xf) << 4; break; +				case 2: b24[1] |= (e >> 2) & 0xf; +				        b24[2] |= (e & 0x3) << 6; break; +				case 3: b24[2] |= e; break; +			} +			if (++b == 4) { /* complete decoding an octet */ +				*po++ = b24[0]; +				*po++ = b24[1]; +				*po++ = b24[2]; +				b24[0] = b24[1] = b24[2] = 0; +				b = 0; +			} +		} +		fwrite(out, 1, (po - out), outfp); +	} +	eprintf("%d: invalid uudecode footer \"====\" not found\n", l); +} + +static void +uudecode(FILE *fp, FILE *outfp) +{ +	char *bufb = NULL, *p; +	size_t n = 0; +	ssize_t len; +	int ch, i; + +#define DEC(c)  (((c) - ' ') & 077) /* single character decode */ +#define IS_DEC(c) ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) ) +#define OUT_OF_RANGE(c) eprintf("character %c out of range: [%d-%d]\n", (c), 1 + ' ', 077 + ' ' + 1) + +	while ((len = getline(&bufb, &n, fp)) > 0) { +		p = bufb; +		/* trim newlines */ +		if (!len || bufb[len - 1] != '\n') +			eprintf("no newline found, aborting\n"); +		bufb[len - 1] = '\0'; + +		/* check for last line */ +		if ((i = DEC(*p)) <= 0) +			break; +		for (++p; i > 0; p += 4, i -= 3) { +			if (i >= 3) { +				if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) && +				      IS_DEC(*(p + 2)) && IS_DEC(*(p + 3)))) +					OUT_OF_RANGE(*p); + +				ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; +				putc(ch, outfp); +				ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; +				putc(ch, outfp); +				ch = DEC(p[2]) << 6 | DEC(p[3]); +				putc(ch, outfp); +			} else { +				if (i >= 1) { +					if (!(IS_DEC(*p) && IS_DEC(*(p + 1)))) +						OUT_OF_RANGE(*p); + +					ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; +					putc(ch, outfp); +				} +				if (i >= 2) { +					if (!(IS_DEC(*(p + 1)) && +					      IS_DEC(*(p + 2)))) +						OUT_OF_RANGE(*p); + +					ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; +					putc(ch, outfp); +				} +			} +		} +		if (ferror(fp)) +			eprintf("read error:"); +	} +	/* check for end or fail */ +	if ((len = getline(&bufb, &n, fp)) < 0) +		eprintf("getline:"); +	if (len < 3 || strncmp(bufb, "end", 3) || bufb[3] != '\n') +		eprintf("invalid uudecode footer \"end\" not found\n"); +	free(bufb); +} + +static void +usage(void) +{ +	eprintf("usage: %s [-m] [-o output] [file]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	FILE *fp = NULL, *nfp = NULL; +	mode_t mode = 0; +	int ret = 0; +	char *fname, *header, *ifname, *ofname = NULL; +	void (*d) (FILE *, FILE *) = NULL; + +	ARGBEGIN { +	case 'm': +		mflag = 1; /* accepted but unused (autodetect file type) */ +		break; +	case 'o': +		oflag = 1; +		ofname = EARGF(usage()); +		break; +	default: +		usage(); +	} ARGEND + +	if (argc > 1) +		usage(); + +	if (!argc || !strcmp(argv[0], "-")) { +		fp = stdin; +		ifname = "<stdin>"; +	} else { +		if (!(fp = fopen(argv[0], "r"))) +			eprintf("fopen %s:", argv[0]); +		ifname = argv[0]; +	} + +	parseheader(fp, ifname, &header, &mode, &fname); + +	if (!strncmp(header, "begin", sizeof("begin"))) +		d = uudecode; +	else if (!strncmp(header, "begin-base64", sizeof("begin-base64"))) +		d = uudecodeb64; +	else +		eprintf("unknown header %s:", header); + +	if (oflag) +		fname = ofname; +	if (!(nfp = parsefile(fname))) +		eprintf("fopen %s:", fname); + +	d(fp, nfp); + +	if (nfp != stdout && chmod(fname, mode) < 0) +		eprintf("chmod %s:", fname); + +	ret |= fshut(fp, (fp == stdin) ? "<stdin>" : argv[0]); +	ret |= fshut(nfp, (nfp == stdout) ? "<stdout>" : fname); + +	return ret; +} diff --git a/util/sbase/uuencode.1 b/util/sbase/uuencode.1 new file mode 100644 index 00000000..859f4a79 --- /dev/null +++ b/util/sbase/uuencode.1 @@ -0,0 +1,34 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/uuencode.c b/util/sbase/uuencode.c new file mode 100644 index 00000000..b5317205 --- /dev/null +++ b/util/sbase/uuencode.c @@ -0,0 +1,129 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include <stdio.h> +#include <string.h> + +#include "util.h" + +static unsigned int +b64e(unsigned char *b) +{ +	unsigned int o, p = b[2] | (b[1] << 8) | (b[0] << 16); +	const char b64et[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +	o = b64et[p & 0x3f]; p >>= 6; +	o = (o << 8) | b64et[p & 0x3f]; p >>= 6; +	o = (o << 8) | b64et[p & 0x3f]; p >>= 6; +	o = (o << 8) | b64et[p & 0x3f]; + +	return o; +} + +static void +uuencodeb64(FILE *fp, const char *name, const char *s) +{ +	struct stat st; +	ssize_t n, m = 0; +	unsigned char buf[45], *pb; +	unsigned int out[sizeof(buf)/3 + 1], *po; + +	if (fstat(fileno(fp), &st) < 0) +		eprintf("fstat %s:", s); +	printf("begin-base64 %o %s\n", st.st_mode & 0777, name); +	/* write line by line */ +	while ((n = fread(buf, 1, sizeof(buf), fp))) { +		/* clear old buffer if converting with non-multiple of 3 */ +		if (n != sizeof(buf) && (m = n % 3) != 0) { +			buf[n] = '\0'; /* m == 2 */ +			if (m == 1) buf[n+1] = '\0'; /* m == 1 */ +		} +		for (pb = buf, po = out; pb < buf + n; pb += 3) +			*po++ = b64e(pb); +		if (m != 0) { +			unsigned int mask = 0xffffffff, dest = 0x3d3d3d3d; +			/* m==2 -> 0x00ffffff; m==1 -> 0x0000ffff */ +			mask >>= ((3-m) << 3); +			po[-1] = (po[-1] & mask) | (dest & ~mask); +		} +		*po++ = '\n'; +		fwrite(out, 1, (po - out) * sizeof(unsigned int) - 3, stdout); +	} +	if (ferror(fp)) +		eprintf("'%s' read error:", s); +	puts("===="); +} + +static void +uuencode(FILE *fp, const char *name, const char *s) +{ +	struct stat st; +	unsigned char buf[45], *p; +	ssize_t n; +	int ch; + +	if (fstat(fileno(fp), &st) < 0) +		eprintf("fstat %s:", s); +	printf("begin %o %s\n", st.st_mode & 0777, name); +	while ((n = fread(buf, 1, sizeof(buf), fp))) { +		ch = ' ' + (n & 0x3f); +		putchar(ch == ' ' ? '`' : ch); +		for (p = buf; n > 0; n -= 3, p += 3) { +			if (n < 3) { +				p[2] = '\0'; +				if (n < 2) +					p[1] = '\0'; +			} +			ch = ' ' + ((p[0] >> 2) & 0x3f); +			putchar(ch == ' ' ? '`' : ch); +			ch = ' ' + (((p[0] << 4) | ((p[1] >> 4) & 0xf)) & 0x3f); +			putchar(ch == ' ' ? '`' : ch); +			ch = ' ' + (((p[1] << 2) | ((p[2] >> 6) & 0x3)) & 0x3f); +			putchar(ch == ' ' ? '`' : ch); +			ch = ' ' + (p[2] & 0x3f); +			putchar(ch == ' ' ? '`' : ch); +		} +		putchar('\n'); +	} +	if (ferror(fp)) +		eprintf("'%s' read error:", s); +	printf("%c\nend\n", '`'); +} + +static void +usage(void) +{ +	eprintf("usage: %s [-m] [file] name\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	FILE *fp = NULL; +	void (*uuencode_f)(FILE *, const char *, const char *) = uuencode; +	int ret = 0; + +	ARGBEGIN { +	case 'm': +		uuencode_f = uuencodeb64; +		break; +	default: +		usage(); +	} ARGEND + +	if (!argc || argc > 2) +		usage(); + +	if (argc == 1 || !strcmp(argv[0], "-")) { +		uuencode_f(stdin, argv[0], "<stdin>"); +	} else { +		if (!(fp = fopen(argv[0], "r"))) +			eprintf("fopen %s:", argv[0]); +		uuencode_f(fp, argv[1], argv[0]); +	} + +	ret |= fp && fshut(fp, argv[0]); +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/wc.1 b/util/sbase/wc.1 new file mode 100644 index 00000000..91c05018 --- /dev/null +++ b/util/sbase/wc.1 @@ -0,0 +1,28 @@ +.Dd October 8, 2015 +.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 +POSIX.1-2013. diff --git a/util/sbase/wc.c b/util/sbase/wc.c new file mode 100644 index 00000000..c89d19f8 --- /dev/null +++ b/util/sbase/wc.c @@ -0,0 +1,122 @@ +/* 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 first = 1; + +	if (lflag) { +		first = 0; +		printf("%zu", nl); +	} +	if (wflag) { +		if (!first) +			putchar(' '); +		first = 0; +		printf("%zu", nw); +	} +	if (cmode) { +		if (!first) +			putchar(' '); +		printf("%zu", 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') ? 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 (!lflag && !wflag && !cmode) { +		cmode = 'c'; +		lflag = 1; +		wflag = 1; +	} + +	if (!argc) { +		wc(stdin, NULL); +	} else { +		for (many = (argc > 1); *argv; argc--, argv++) { +			if (!strcmp(*argv, "-")) { +				*argv = "<stdin>"; +				fp = stdin; +			} else if (!(fp = fopen(*argv, "r"))) { +				weprintf("fopen %s:", *argv); +				ret = 1; +				continue; +			} +			wc(fp, *argv); +			if (fp != stdin && fshut(fp, *argv)) +				ret = 1; +		} +		if (many) +			output("total", tc, tl, tw); +	} + +	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); + +	return ret; +} diff --git a/util/sbase/which.1 b/util/sbase/which.1 new file mode 100644 index 00000000..9ba4b224 --- /dev/null +++ b/util/sbase/which.1 @@ -0,0 +1,44 @@ +.Dd October 8, 2015 +.Dt WHICH 1 +.Os sbase +.Sh NAME +.Nm which +.Nd locate programs in the path +.Sh SYNOPSIS +.Nm +.Op Fl a +.Ar name ... +.Sh DESCRIPTION +.Nm +looks for each +.Ar name +in the +.Ev PATH +directories, stopping at the first match and printing +the full path to stdout. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl a +Don't stop at the first match and search all +.Ev PATH +directories. +.El +.Sh EXIT STATUS +.Bl -tag -width Ds +.It 0 +Each +.Ar name +was found. +.It 1 +At least one +.Ar name +was not found. +.It 2 +No +.Ar name +was found. +.It 3 +An error occurred. +.El +.Sh SEE ALSO +.Xr environ 7 diff --git a/util/sbase/which.c b/util/sbase/which.c new file mode 100644 index 00000000..abd030e7 --- /dev/null +++ b/util/sbase/which.c @@ -0,0 +1,101 @@ +/* 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 +canexec(int fd, const char *name) +{ +	struct stat st; + +	if (fstatat(fd, name, &st, 0) < 0 || !S_ISREG(st.st_mode)) +		return 0; +	return faccessat(fd, name, X_OK, AT_EACCESS) == 0; +} + +static int +which(const char *path, const char *name) +{ +	char *ptr, *p; +	size_t i, len; +	int dirfd, found = 0; + +	if (strchr(name, '/')) { +		found = canexec(AT_FDCWD, name); +		if (found) +			puts(name); +		return found; +	} + +	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) { +			if (canexec(dirfd, name)) { +				found = 1; +				fputs(p, stdout); +				if (i && ptr[i - 1] != '/') +					fputc('/', stdout); +				puts(name); +			} +			close(dirfd); +			if (!aflag && found) +				break; +		} +		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: not an external command\n", *argv); +			foundall = 0; +		} +	} + +	return found ? foundall ? 0 : 1 : 2; +} diff --git a/util/sbase/whoami.1 b/util/sbase/whoami.1 new file mode 100644 index 00000000..d552a327 --- /dev/null +++ b/util/sbase/whoami.1 @@ -0,0 +1,9 @@ +.Dd December 14, 2015 +.Dt WHOAMI 1 +.Os sbase +.Sh NAME +.Nm whoami +.Nd show effective uid +.Sh SYNOPSIS +.Nm +writes the name of the effective uid to stdout. diff --git a/util/sbase/whoami.c b/util/sbase/whoami.c new file mode 100644 index 00000000..c991ac29 --- /dev/null +++ b/util/sbase/whoami.c @@ -0,0 +1,37 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <pwd.h> + +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	uid_t uid; +	struct passwd *pw; + +	argv0 = *argv, argv0 ? (argc--, argv++) : (void *)0; + +	if (argc) +		usage(); + +	uid = geteuid(); +	errno = 0; +	if (!(pw = getpwuid(uid))) { +		if (errno) +			eprintf("getpwuid %d:", uid); +		else +			eprintf("getpwuid %d: no such user\n", uid); +	} +	puts(pw->pw_name); + +	return fshut(stdout, "<stdout>"); +} diff --git a/util/sbase/xargs.1 b/util/sbase/xargs.1 new file mode 100644 index 00000000..e988278d --- /dev/null +++ b/util/sbase/xargs.1 @@ -0,0 +1,121 @@ +.Dd July 30, 2025 +.Dt XARGS 1 +.Os sbase +.Sh NAME +.Nm xargs +.Nd construct argument lists and execute command +.Sh SYNOPSIS +.Nm +.Op Fl 0prtx +.Op Fl E Ar eofstr +.Op Fl I Ar replstr +.Op Fl n Ar num +.Op Fl P Ar maxprocs +.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 ('\e'). +Single quotes escape all non-single quote characters, excluding newlines, up +to the matching single quote. +Double quotes escape all non-double quote characters, excluding newlines, up +to the matching double quote. +Any single character, including newlines, may be escaped by a backslash. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl 0 +Change +.Nm +to expect NUL ('\e0') characters as separators, instead of spaces +and newlines. +The quoting mechanisms described above are not performed. +.It Fl E Ar eofstr +Use +.Ar eofstr +as a logical EOF marker. +.It Fl I Ar replstr +Use +.Ar replstr +as the placeholder for the argument. +Sets the arguments count to 1 per command line. +It also implies the option x. +.It Fl n Ar num +Use at most +.Ar num +arguments per command line. +.It Fl p +Prompt mode: the user is asked whether to execute +.Ar cmd +at each invocation. +Trace mode (-t) is turned on to write the command instance to be executed, +followed by a prompt to standard error. +An affirmative response read from +.Pa /dev/tty +executes the command, otherwise it is skipped. +.It Fl P Ar maxprocs +Parallel mode: run at most maxprocs invocations of +.Ar cmd +at once. +.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 s Ar num +Use at most +.Ar num +bytes per command line. +.It Fl t +Enable trace mode. +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 +POSIX.1-2013. +.Pp +The +.Op Fl r +and +.Op Fl P +flag is an extension to that specification. diff --git a/util/sbase/xargs.c b/util/sbase/xargs.c new file mode 100644 index 00000000..b3b2a81c --- /dev/null +++ b/util/sbase/xargs.c @@ -0,0 +1,362 @@ +/* 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(int); +static void spawn(void); + +static size_t argbsz; +static size_t argbpos; +static size_t maxargs; +static size_t curprocs, maxprocs = 1; +static int    nerrors; +static int    nulflag, nflag, pflag, rflag, tflag, xflag, Iflag; +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) { +		if (nulflag || !(ch == ' ' || ch == '\t' || ch == '\n')) { +			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) { +		/* NUL separator: no escaping */ +		if (nulflag) { +			if (ch == '\0') +				goto out; +			else +				goto fill; +		} + +		switch (ch) { +		case ' ': +		case '\t': +			if (Iflag) +				goto fill; +		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: +		fill: +			fillargbuf(ch); +			argbpos++; +			break; +		} +	} +out: +	fillargbuf('\0'); + +	return (eofstr && !strcmp(argb, eofstr)) ? NULL : argb; +} + +static void +waitchld(int waitall) +{ +	pid_t pid; +	int status; + +	while ((pid = waitpid(-1, &status, !waitall && curprocs < maxprocs ? +	       WNOHANG : 0)) > 0) { +	       curprocs--; + +		if (WIFEXITED(status)) { +			if (WEXITSTATUS(status) == 255) +				exit(124); +			if (WEXITSTATUS(status) == 127 || +			    WEXITSTATUS(status) == 126) +				exit(WEXITSTATUS(status)); +			if (WEXITSTATUS(status)) +				nerrors++; +		} +		if (WIFSIGNALED(status)) +			exit(125); +	} +	if (pid == -1 && errno != ECHILD) +		eprintf("waitpid:"); +} + +static int +prompt(void) +{ +	FILE *fp; +	int ch, ret; + +	if (!(fp = fopen("/dev/tty", "r"))) +		return -1; + +	fputs("?...", stderr); +	fflush(stderr); + +	ch = fgetc(fp); +	ret = (ch == 'y' || ch == 'Y'); +	if (ch != EOF && ch != '\n') { +		while ((ch = fgetc(fp)) != EOF) { +			if (ch == '\n') +				break; +		} +	} + +	fclose(fp); + +	return ret; +} + +static void +spawn(void) +{ +	int savederrno; +	int first = 1; +	char **p; + +	if (pflag || tflag) { +		for (p = cmd; *p; p++) { +			if (!first) +				fputc(' ', stderr); +			fputs(*p, stderr); +			first = 0; +		} +		if (pflag) { +			switch (prompt()) { +			case -1: break; /* error */ +			case 0: return; /* no */ +			case 1: goto dospawn; /* yes */ +			} +		} +		fputc('\n', stderr); +		fflush(stderr); +	} + +dospawn: +	switch (fork()) { +	case -1: +		eprintf("fork:"); +	case 0: +		execvp(*cmd, cmd); +		savederrno = errno; +		weprintf("execvp %s:", *cmd); +		_exit(126 + (savederrno == ENOENT)); +	} +	curprocs++; +	waitchld(0); +} + +static void +usage(void) +{ +	eprintf("usage: %s [-0prtx] [-E eofstr] [-n num] [-P maxprocs] [-s num] " +	        "[cmd [arg ...]]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	int ret = 0, leftover = 0, i, j; +	size_t argsz, argmaxsz; +	size_t arglen, a; +	char *arg = ""; +	char *replstr; + +	if ((argmaxsz = sysconf(_SC_ARG_MAX)) == (size_t)-1) +		argmaxsz = _POSIX_ARG_MAX; +	/* Leave some room for environment variables */ +	argmaxsz -= 4096; + +	ARGBEGIN { +	case '0': +		nulflag = 1; +		break; +	case 'n': +		nflag = 1; +		maxargs = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX)); +		break; +	case 'p': +		pflag = 1; +		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; +	case 'I': +		Iflag = 1; +		xflag = 1; +		nflag = 1; +		maxargs = 1; +		replstr = EARGF(usage()); +		break; +	case 'P': +		maxprocs = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX)); +		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 (xflag || arglen >= argmaxsz || leftover) +					eprintf("insufficient argument space\n"); +				leftover = 1; +				break; +			} + +			if (!Iflag) { +				cmd[i] = estrdup(arg); +				argsz += arglen + 1; +			} else { +				for (j = 1; j < i; j++) { +					char *p = cmd[j]; +					argsz -= strlen(cmd[j]); +					strnsubst(&cmd[j], replstr, arg, 255); +					argsz += strlen(cmd[j]); +					free(p); +				} +			} + +			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); + +	waitchld(1); + +	if (nerrors || (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))) +		ret = 123; + +	return ret; +} diff --git a/util/sbase/xinstall.1 b/util/sbase/xinstall.1 new file mode 100644 index 00000000..2d9535c0 --- /dev/null +++ b/util/sbase/xinstall.1 @@ -0,0 +1,86 @@ +.Dd December 3, 2016 +.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 D +.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 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 +.Sh STANDARDS +The +.Nm +utility is not standardized. +This implementation is a subset of the GNU implementation and a subset +with extensions to the FreeBSD implementation. diff --git a/util/sbase/xinstall.c b/util/sbase/xinstall.c new file mode 100644 index 00000000..fe1a160e --- /dev/null +++ b/util/sbase/xinstall.c @@ -0,0 +1,194 @@ +/* See LICENSE file for copyright and license details. */ +#include <grp.h> +#include <pwd.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include "util.h" + +static int Dflag = 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 int +install(const char *s1, const char *s2, int depth) +{ +	int f1, f2; + +	if ((f1 = open(s1, O_RDONLY)) < 0) +		eprintf("open %s:", s1); +	if ((f2 = creat(s2, 0600)) < 0) { +		if (unlink(s2) < 0 && errno != ENOENT) +			eprintf("unlink %s:", s2); +		if ((f2 = creat(s2, 0600)) < 0) +			eprintf("creat %s:", s2); +	} +	if (concat(f1, s1, f2, s2) < 0) +		goto fail; +	if (fchmod(f2, mode) < 0) { +		weprintf("fchmod %s:", s2); +		goto fail; +	} +	if (fchown(f2, owner, group) < 0) { +		weprintf("fchown %s:", s2); +		goto fail; +	} + +	close(f1); +	close(f2); + +	return 0; + +fail: +	unlink(s2); +	exit(1); +} + +static void +usage(void) +{ +	eprintf("usage: %s [-g group] [-o owner] [-m mode] (-d dir ... | [-D] (-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 'c': +		/* no-op for compatibility */ +		break; +	case 'd': +		dflag = 1; +		break; +	case 'D': +		Dflag = 1; +		break; +	case 's': +		/* no-op for compatibility */ +		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 | !!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 (dflag) { +		for (; *argv; argc--, argv++) +			make_dirs(*argv, 0); +		return 0; +	} + +	if (tflag) { +		argv = memmove(argv - 1, argv, argc * sizeof(*argv)); +		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 (stat(argv[argc - 1], &st) < 0) { +		if (errno != ENOENT) +			eprintf("stat %s:", argv[argc - 1]); +		if (tflag || Dflag || argc > 2) { +			if ((p = strrchr(argv[argc - 1], '/')) != NULL) { +				*p = '\0'; +				make_dirs(argv[argc - 1], 1); +				*p = '/'; +			} +		} +	} +	enmasse(argc, argv, install); + +	return 0; +} diff --git a/util/sbase/yes.1 b/util/sbase/yes.1 new file mode 100644 index 00000000..8415857c --- /dev/null +++ b/util/sbase/yes.1 @@ -0,0 +1,14 @@ +.Dd October 8, 2015 +.Dt YES 1 +.Os sbase +.Sh NAME +.Nm yes +.Nd output string repeatedly +.Sh SYNOPSIS +.Nm +.Op Ar string +.Sh DESCRIPTION +.Nm +will repeatedly write 'y' or +.Ar string +to stdout. diff --git a/util/sbase/yes.c b/util/sbase/yes.c new file mode 100644 index 00000000..b5c3c109 --- /dev/null +++ b/util/sbase/yes.c @@ -0,0 +1,25 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> + +#include "util.h" + +static void +usage(void) +{ +	eprintf("usage: %s [string]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ +	const char *s; + +	ARGBEGIN { +	default: +		usage(); +	} ARGEND + +	s = argc ? argv[0] : "y"; +	for (;;) +		puts(s); +}  | 
