commit c28e6b723b17b1463f8c7425c6c0b09c9311272d
Author: sin <sin@2f30.org>
Date: Wed, 21 Jan 2015 01:05:19 +0000
Initial commit
Diffstat:
401 files changed, 36226 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
@@ -0,0 +1,16 @@
+TARG = awk sbase sdhcp sed sinit smdev ubase
+
+all:
+ @echo "CFLAGS = $(CFLAGS)"
+ @echo "LDFLAGS = $(LDFLAGS)"
+ @echo "CC = $(CC)"
+ for i in $(TARG); do cd $$i && $(MAKE) CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" || exit; cd ..; done
+
+install: all
+ for i in $(TARG); do cd $$i && $(MAKE) install || exit; cd ..; done
+
+uninstall:
+ for i in $(TARG); do cd $$i && $(MAKE) uninstall || exit; cd ..; done
+
+clean:
+ for i in $(TARG); do cd $$i && $(MAKE) clean || exit; cd ..; done
diff --git a/awk/Makefile b/awk/Makefile
@@ -0,0 +1,21 @@
+OBJ = arc4random.o b.o lex.o lib.o main.o parse.o proctab.o reallocarray.o run.o strlcat.o strlcpy.o tran.o ytab.o
+TARG = awk
+LDLIBS = -lm
+
+all: ytab.c proctab.c $(TARG)
+
+ytab.c ytab.h: awkgram.y
+ $(YACC) -d awkgram.y
+ mv y.tab.c ytab.c
+ mv y.tab.h ytab.h
+
+proctab.c: maketab
+ ./maketab > proctab.c
+
+maketab: ytab.h maketab.c
+ $(CC) maketab.c -o $@
+
+include ../std.mk
+
+clean:
+ rm -f $(TARG) $(OBJ) maketab proctab.c ytab.[ch]
diff --git a/awk/arc4random.c b/awk/arc4random.c
@@ -0,0 +1,12 @@
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+uint32_t
+arc4random(void)
+{
+ srand(time(NULL) ^ getpid() ^ 0x42);
+ return rand();
+}
diff --git a/awk/awk.1 b/awk/awk.1
@@ -0,0 +1,800 @@
+.\" $OpenBSD: awk.1,v 1.41 2014/03/17 21:48:51 jmc Exp $
+.\"
+.\" Copyright (C) Lucent Technologies 1997
+.\" All Rights Reserved
+.\"
+.\" Permission to use, copy, modify, and distribute this software and
+.\" its documentation for any purpose and without fee is hereby
+.\" granted, provided that the above copyright notice appear in all
+.\" copies and that both that the copyright notice and this
+.\" permission notice and warranty disclaimer appear in supporting
+.\" documentation, and that the name Lucent Technologies or any of
+.\" its entities not be used in advertising or publicity pertaining
+.\" to distribution of the software without specific, written prior
+.\" permission.
+.\"
+.\" LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+.\" INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+.\" IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
+.\" SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+.\" ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+.\" THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: March 17 2014 $
+.Dt AWK 1
+.Os
+.Sh NAME
+.Nm awk
+.Nd pattern-directed scanning and processing language
+.Sh SYNOPSIS
+.Nm awk
+.Op Fl safe
+.Op Fl V
+.Op Fl d Ns Op Ar n
+.Op Fl F Ar fs
+.Op Fl v Ar var Ns = Ns Ar value
+.Op Ar prog | Fl f Ar progfile
+.Ar
+.Sh DESCRIPTION
+.Nm
+scans each input
+.Ar file
+for lines that match any of a set of patterns specified literally in
+.Ar prog
+or in one or more files specified as
+.Fl f Ar progfile .
+With each pattern there can be an associated action that will be performed
+when a line of a
+.Ar file
+matches the pattern.
+Each line is matched against the
+pattern portion of every pattern-action statement;
+the associated action is performed for each matched pattern.
+The file name
+.Sq -
+means the standard input.
+Any
+.Ar file
+of the form
+.Ar var Ns = Ns Ar value
+is treated as an assignment, not a filename,
+and is executed at the time it would have been opened if it were a filename.
+.Pp
+The options are as follows:
+.Bl -tag -width "-safe "
+.It Fl d Ns Op Ar n
+Debug mode.
+Set debug level to
+.Ar n ,
+or 1 if
+.Ar n
+is not specified.
+A value greater than 1 causes
+.Nm
+to dump core on fatal errors.
+.It Fl F Ar fs
+Define the input field separator to be the regular expression
+.Ar fs .
+.It Fl f Ar progfile
+Read program code from the specified file
+.Ar progfile
+instead of from the command line.
+.It Fl safe
+Disable file output
+.Pf ( Ic print No > ,
+.Ic print No >> ) ,
+process creation
+.Po
+.Ar cmd | Ic getline ,
+.Ic print | ,
+.Ic system
+.Pc
+and access to the environment
+.Pf ( Va ENVIRON ;
+see the section on variables below).
+This is a first
+.Pq and not very reliable
+approximation to a
+.Dq safe
+version of
+.Nm .
+.It Fl V
+Print the version number of
+.Nm
+to standard output and exit.
+.It Fl v Ar var Ns = Ns Ar value
+Assign
+.Ar value
+to variable
+.Ar var
+before
+.Ar prog
+is executed;
+any number of
+.Fl v
+options may be present.
+.El
+.Pp
+The input is normally made up of input lines
+.Pq records
+separated by newlines, or by the value of
+.Va RS .
+If
+.Va RS
+is null, then any number of blank lines are used as the record separator,
+and newlines are used as field separators
+(in addition to the value of
+.Va FS ) .
+This is convenient when working with multi-line records.
+.Pp
+An input line is normally made up of fields separated by whitespace,
+or by the regular expression
+.Va FS .
+The fields are denoted
+.Va $1 , $2 , ... ,
+while
+.Va $0
+refers to the entire line.
+If
+.Va FS
+is null, the input line is split into one field per character.
+.Pp
+Normally, any number of blanks separate fields.
+In order to set the field separator to a single blank, use the
+.Fl F
+option with a value of
+.Sq [\ \&] .
+If a field separator of
+.Sq t
+is specified,
+.Nm
+treats it as if
+.Sq \et
+had been specified and uses
+.Aq TAB
+as the field separator.
+In order to use a literal
+.Sq t
+as the field separator, use the
+.Fl F
+option with a value of
+.Sq [t] .
+.Pp
+A pattern-action statement has the form
+.Pp
+.D1 Ar pattern Ic \&{ Ar action Ic \&}
+.Pp
+A missing
+.Ic \&{ Ar action Ic \&}
+means print the line;
+a missing pattern always matches.
+Pattern-action statements are separated by newlines or semicolons.
+.Pp
+Newlines are permitted after a terminating statement or following a comma
+.Pq Sq ,\& ,
+an open brace
+.Pq Sq { ,
+a logical AND
+.Pq Sq && ,
+a logical OR
+.Pq Sq || ,
+after the
+.Sq do
+or
+.Sq else
+keywords,
+or after the closing parenthesis of an
+.Sq if ,
+.Sq for ,
+or
+.Sq while
+statement.
+Additionally, a backslash
+.Pq Sq \e
+can be used to escape a newline between tokens.
+.Pp
+An action is a sequence of statements.
+A statement can be one of the following:
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It Xo Ic if ( Ar expression ) Ar statement
+.Op Ic else Ar statement
+.Xc
+.It Ic while ( Ar expression ) Ar statement
+.It Xo Ic for
+.No ( Ar expression ; expression ; expression ) statement
+.Xc
+.It Xo Ic for
+.No ( Ar var Ic in Ar array ) statement
+.Xc
+.It Xo Ic do
+.Ar statement Ic while ( Ar expression )
+.Xc
+.It Ic break
+.It Ic continue
+.It Xo Ic {
+.Op Ar statement ...
+.Ic }
+.Xc
+.It Xo Ar expression
+.No # commonly
+.Ar var No = Ar expression
+.Xc
+.It Xo Ic print
+.Op Ar expression-list
+.Op > Ns Ar expression
+.Xc
+.It Xo Ic printf Ar format
+.Op Ar ... , expression-list
+.Op > Ns Ar expression
+.Xc
+.It Ic return Op Ar expression
+.It Xo Ic next
+.No # skip remaining patterns on this input line
+.Xc
+.It Xo Ic nextfile
+.No # skip rest of this file, open next, start at top
+.Xc
+.It Xo Ic delete
+.Sm off
+.Ar array Ic \&[ Ar expression Ic \&]
+.Sm on
+.No # delete an array element
+.Xc
+.It Xo Ic delete Ar array
+.No # delete all elements of array
+.Xc
+.It Xo Ic exit
+.Op Ar expression
+.No # exit immediately; status is Ar expression
+.Xc
+.El
+.Pp
+Statements are terminated by
+semicolons, newlines or right braces.
+An empty
+.Ar expression-list
+stands for
+.Ar $0 .
+String constants are quoted
+.Li \&"" ,
+with the usual C escapes recognized within
+(see
+.Xr printf 1
+for a complete list of these).
+Expressions take on string or numeric values as appropriate,
+and are built using the operators
+.Ic + \- * / % ^
+.Pq exponentiation ,
+and concatenation
+.Pq indicated by whitespace .
+The operators
+.Ic \&! ++ \-\- += \-= *= /= %= ^=
+.Ic > >= < <= == != ?:
+are also available in expressions.
+Variables may be scalars, array elements
+(denoted
+.Li x[i] )
+or fields.
+Variables are initialized to the null string.
+Array subscripts may be any string,
+not necessarily numeric;
+this allows for a form of associative memory.
+Multiple subscripts such as
+.Li [i,j,k]
+are permitted; the constituents are concatenated,
+separated by the value of
+.Va SUBSEP
+.Pq see the section on variables below .
+.Pp
+The
+.Ic print
+statement prints its arguments on the standard output
+(or on a file if
+.Pf > Ns Ar file
+or
+.Pf >> Ns Ar file
+is present or on a pipe if
+.Pf |\ \& Ar cmd
+is present), separated by the current output field separator,
+and terminated by the output record separator.
+.Ar file
+and
+.Ar cmd
+may be literal names or parenthesized expressions;
+identical string values in different statements denote
+the same open file.
+The
+.Ic printf
+statement formats its expression list according to the format
+(see
+.Xr printf 1 ) .
+.Pp
+Patterns are arbitrary Boolean combinations
+(with
+.Ic "\&! || &&" )
+of regular expressions and
+relational expressions.
+.Nm
+supports extended regular expressions
+.Pq EREs .
+See
+.Xr re_format 7
+for more information on regular expressions.
+Isolated regular expressions
+in a pattern apply to the entire line.
+Regular expressions may also occur in
+relational expressions, using the operators
+.Ic ~
+and
+.Ic !~ .
+.Pf / Ns Ar re Ns /
+is a constant regular expression;
+any string (constant or variable) may be used
+as a regular expression, except in the position of an isolated regular expression
+in a pattern.
+.Pp
+A pattern may consist of two patterns separated by a comma;
+in this case, the action is performed for all lines
+from an occurrence of the first pattern
+through an occurrence of the second.
+.Pp
+A relational expression is one of the following:
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It Ar expression matchop regular-expression
+.It Ar expression relop expression
+.It Ar expression Ic in Ar array-name
+.It Xo Ic \&( Ns
+.Ar expr , expr , \&... Ns Ic \&) in
+.Ar array-name
+.Xc
+.El
+.Pp
+where a
+.Ar relop
+is any of the six relational operators in C, and a
+.Ar matchop
+is either
+.Ic ~
+(matches)
+or
+.Ic !~
+(does not match).
+A conditional is an arithmetic expression,
+a relational expression,
+or a Boolean combination
+of these.
+.Pp
+The special patterns
+.Ic BEGIN
+and
+.Ic END
+may be used to capture control before the first input line is read
+and after the last.
+.Ic BEGIN
+and
+.Ic END
+do not combine with other patterns.
+.Pp
+Variable names with special meanings:
+.Pp
+.Bl -tag -width "FILENAME " -compact
+.It Va ARGC
+Argument count, assignable.
+.It Va ARGV
+Argument array, assignable;
+non-null members are taken as filenames.
+.It Va CONVFMT
+Conversion format when converting numbers
+(default
+.Qq Li %.6g ) .
+.It Va ENVIRON
+Array of environment variables; subscripts are names.
+.It Va FILENAME
+The name of the current input file.
+.It Va FNR
+Ordinal number of the current record in the current file.
+.It Va FS
+Regular expression used to separate fields; also settable
+by option
+.Fl F Ar fs .
+.It Va NF
+Number of fields in the current record.
+.Va $NF
+can be used to obtain the value of the last field in the current record.
+.It Va NR
+Ordinal number of the current record.
+.It Va OFMT
+Output format for numbers (default
+.Qq Li %.6g ) .
+.It Va OFS
+Output field separator (default blank).
+.It Va ORS
+Output record separator (default newline).
+.It Va RLENGTH
+The length of the string matched by the
+.Fn match
+function.
+.It Va RS
+Input record separator (default newline).
+.It Va RSTART
+The starting position of the string matched by the
+.Fn match
+function.
+.It Va SUBSEP
+Separates multiple subscripts (default 034).
+.El
+.Sh FUNCTIONS
+The awk language has a variety of built-in functions:
+arithmetic, string, input/output, general, and bit-operation.
+.Pp
+Functions may be defined (at the position of a pattern-action statement)
+thusly:
+.Pp
+.Dl function foo(a, b, c) { ...; return x }
+.Pp
+Parameters are passed by value if scalar, and by reference if array name;
+functions may be called recursively.
+Parameters are local to the function; all other variables are global.
+Thus local variables may be created by providing excess parameters in
+the function definition.
+.Ss Arithmetic Functions
+.Bl -tag -width "atan2(y, x)"
+.It Fn atan2 y x
+Return the arctangent of
+.Fa y Ns / Ns Fa x
+in radians.
+.It Fn cos x
+Return the cosine of
+.Fa x ,
+where
+.Fa x
+is in radians.
+.It Fn exp x
+Return the exponential of
+.Fa x .
+.It Fn int x
+Return
+.Fa x
+truncated to an integer value.
+.It Fn log x
+Return the natural logarithm of
+.Fa x .
+.It Fn rand
+Return a random number,
+.Fa n ,
+such that
+.Sm off
+.Pf 0 \*(Le Fa n No \*(Lt 1 .
+.Sm on
+.It Fn sin x
+Return the sine of
+.Fa x ,
+where
+.Fa x
+is in radians.
+.It Fn sqrt x
+Return the square root of
+.Fa x .
+.It Fn srand expr
+Sets seed for
+.Fn rand
+to
+.Fa expr
+and returns the previous seed.
+If
+.Fa expr
+is omitted, the time of day is used instead.
+.El
+.Ss String Functions
+.Bl -tag -width "split(s, a, fs)"
+.It Fn gsub r t s
+The same as
+.Fn sub
+except that all occurrences of the regular expression are replaced.
+.Fn gsub
+returns the number of replacements.
+.It Fn index s t
+The position in
+.Fa s
+where the string
+.Fa t
+occurs, or 0 if it does not.
+.It Fn length s
+The length of
+.Fa s
+taken as a string,
+or of
+.Va $0
+if no argument is given.
+.It Fn match s r
+The position in
+.Fa s
+where the regular expression
+.Fa r
+occurs, or 0 if it does not.
+The variable
+.Va RSTART
+is set to the starting position of the matched string
+.Pq which is the same as the returned value
+or zero if no match is found.
+The variable
+.Va RLENGTH
+is set to the length of the matched string,
+or \-1 if no match is found.
+.It Fn split s a fs
+Splits the string
+.Fa s
+into array elements
+.Va a[1] , a[2] , ... , a[n]
+and returns
+.Va n .
+The separation is done with the regular expression
+.Ar fs
+or with the field separator
+.Va FS
+if
+.Ar fs
+is not given.
+An empty string as field separator splits the string
+into one array element per character.
+.It Fn sprintf fmt expr ...
+The string resulting from formatting
+.Fa expr , ...
+according to the
+.Xr printf 1
+format
+.Fa fmt .
+.It Fn sub r t s
+Substitutes
+.Fa t
+for the first occurrence of the regular expression
+.Fa r
+in the string
+.Fa s .
+If
+.Fa s
+is not given,
+.Va $0
+is used.
+An ampersand
+.Pq Sq &
+in
+.Fa t
+is replaced in string
+.Fa s
+with regular expression
+.Fa r .
+A literal ampersand can be specified by preceding it with two backslashes
+.Pq Sq \e\e .
+A literal backslash can be specified by preceding it with another backslash
+.Pq Sq \e\e .
+.Fn sub
+returns the number of replacements.
+.It Fn substr s m n
+Return at most the
+.Fa n Ns -character
+substring of
+.Fa s
+that begins at position
+.Fa m
+counted from 1.
+If
+.Fa n
+is omitted, or if
+.Fa n
+specifies more characters than are left in the string,
+the length of the substring is limited by the length of
+.Fa s .
+.It Fn tolower str
+Returns a copy of
+.Fa str
+with all upper-case characters translated to their
+corresponding lower-case equivalents.
+.It Fn toupper str
+Returns a copy of
+.Fa str
+with all lower-case characters translated to their
+corresponding upper-case equivalents.
+.El
+.Ss Input/Output and General Functions
+.Bl -tag -width "getline [var] < file"
+.It Fn close expr
+Closes the file or pipe
+.Fa expr .
+.Fa expr
+should match the string that was used to open the file or pipe.
+.It Ar cmd | Ic getline Op Va var
+Read a record of input from a stream piped from the output of
+.Ar cmd .
+If
+.Va var
+is omitted, the variables
+.Va $0
+and
+.Va NF
+are set.
+Otherwise
+.Va var
+is set.
+If the stream is not open, it is opened.
+As long as the stream remains open, subsequent calls
+will read subsequent records from the stream.
+The stream remains open until explicitly closed with a call to
+.Fn close .
+.Ic getline
+returns 1 for a successful input, 0 for end of file, and \-1 for an error.
+.It Fn fflush [expr]
+Flushes any buffered output for the file or pipe
+.Fa expr ,
+or all open files or pipes if
+.Fa expr
+is omitted.
+.Fa expr
+should match the string that was used to open the file or pipe.
+.It Ic getline
+Sets
+.Va $0
+to the next input record from the current input file.
+This form of
+.Ic getline
+sets the variables
+.Va NF ,
+.Va NR ,
+and
+.Va FNR .
+.Ic getline
+returns 1 for a successful input, 0 for end of file, and \-1 for an error.
+.It Ic getline Va var
+Sets
+.Va $0
+to variable
+.Va var .
+This form of
+.Ic getline
+sets the variables
+.Va NR
+and
+.Va FNR .
+.Ic getline
+returns 1 for a successful input, 0 for end of file, and \-1 for an error.
+.It Xo
+.Ic getline Op Va var
+.Pf \ \&< Ar file
+.Xc
+Sets
+.Va $0
+to the next record from
+.Ar file .
+If
+.Va var
+is omitted, the variables
+.Va $0
+and
+.Va NF
+are set.
+Otherwise
+.Va var
+is set.
+If
+.Ar file
+is not open, it is opened.
+As long as the stream remains open, subsequent calls will read subsequent
+records from
+.Ar file .
+.Ar file
+remains open until explicitly closed with a call to
+.Fn close .
+.It Fn system cmd
+Executes
+.Fa cmd
+and returns its exit status.
+.El
+.Ss Bit-Operation Functions
+.Bl -tag -width "lshift(a, b)"
+.It Fn compl x
+Returns the bitwise complement of integer argument x.
+.It Fn and x y
+Performs a bitwise AND on integer arguments x and y.
+.It Fn or x y
+Performs a bitwise OR on integer arguments x and y.
+.It Fn xor x y
+Performs a bitwise Exclusive-OR on integer arguments x and y.
+.It Fn lshift x n
+Returns integer argument x shifted by n bits to the left.
+.It Fn rshift x n
+Returns integer argument x shifted by n bits to the right.
+.El
+.Sh EXIT STATUS
+.Ex -std awk
+.Pp
+But note that the
+.Ic exit
+expression can modify the exit status.
+.Sh EXAMPLES
+Print lines longer than 72 characters:
+.Pp
+.Dl length($0) > 72
+.Pp
+Print first two fields in opposite order:
+.Pp
+.Dl { print $2, $1 }
+.Pp
+Same, with input fields separated by comma and/or blanks and tabs:
+.Bd -literal -offset indent
+BEGIN { FS = ",[ \et]*|[ \et]+" }
+ { print $2, $1 }
+.Ed
+.Pp
+Add up first column, print sum and average:
+.Bd -literal -offset indent
+{ s += $1 }
+END { print "sum is", s, " average is", s/NR }
+.Ed
+.Pp
+Print all lines between start/stop pairs:
+.Pp
+.Dl /start/, /stop/
+.Pp
+Simulate echo(1):
+.Bd -literal -offset indent
+BEGIN { # Simulate echo(1)
+ for (i = 1; i < ARGC; i++) printf "%s ", ARGV[i]
+ printf "\en"
+ exit }
+.Ed
+.Pp
+Print an error message to standard error:
+.Bd -literal -offset indent
+{ print "error!" > "/dev/stderr" }
+.Ed
+.Sh SEE ALSO
+.Xr lex 1 ,
+.Xr printf 1 ,
+.Xr sed 1 ,
+.Xr re_format 7 ,
+.Xr script 7
+.Rs
+.%A A. V. Aho
+.%A B. W. Kernighan
+.%A P. J. Weinberger
+.%T The AWK Programming Language
+.%I Addison-Wesley
+.%D 1988
+.%O ISBN 0-201-07981-X
+.Re
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification,
+except
+.Nm
+does not support {n,m} pattern matching.
+.Pp
+The flags
+.Op Fl \&dV
+and
+.Op Fl safe ,
+as well as the commands
+.Cm fflush , compl , and , or ,
+.Cm xor , lshift , rshift ,
+are extensions to that specification.
+.Sh HISTORY
+An
+.Nm
+utility appeared in
+.At v7 .
+.Sh BUGS
+There are no explicit conversions between numbers and strings.
+To force an expression to be treated as a number add 0 to it;
+to force it to be treated as a string concatenate
+.Li \&""
+to it.
+.Pp
+The scope rules for variables in functions are a botch;
+the syntax is worse.
diff --git a/awk/awk.h b/awk/awk.h
@@ -0,0 +1,240 @@
+/* $OpenBSD: awk.h,v 1.13 2008/10/06 20:38:33 millert Exp $ */
+/****************************************************************
+Copyright (C) Lucent Technologies 1997
+All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and
+its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that the copyright notice and this
+permission notice and warranty disclaimer appear in supporting
+documentation, and that the name Lucent Technologies or any of
+its entities not be used in advertising or publicity pertaining
+to distribution of the software without specific, written prior
+permission.
+
+LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
+SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+****************************************************************/
+
+#include <assert.h>
+
+typedef double Awkfloat;
+
+/* unsigned char is more trouble than it's worth */
+
+typedef unsigned char uschar;
+
+#define xfree(a) { if ((a) != NULL) { free((void *) (a)); (a) = NULL; } }
+
+#define NN(p) ((p) ? (p) : "(null)") /* guaranteed non-null for dprintf
+*/
+#define DEBUG
+#ifdef DEBUG
+ /* uses have to be doubly parenthesized */
+# define dprintf(x) if (dbg) printf x
+#else
+# define dprintf(x)
+#endif
+
+extern int compile_time; /* 1 if compiling, 0 if running */
+extern int safe; /* 0 => unsafe, 1 => safe */
+
+#define RECSIZE (8 * 1024) /* sets limit on records, fields, etc., etc. */
+extern int recsize; /* size of current record, orig RECSIZE */
+
+extern char **FS;
+extern char **RS;
+extern char **ORS;
+extern char **OFS;
+extern char **OFMT;
+extern Awkfloat *NR;
+extern Awkfloat *FNR;
+extern Awkfloat *NF;
+extern char **FILENAME;
+extern char **SUBSEP;
+extern Awkfloat *RSTART;
+extern Awkfloat *RLENGTH;
+
+extern char *record; /* points to $0 */
+extern int lineno; /* line number in awk program */
+extern int errorflag; /* 1 if error has occurred */
+extern int donefld; /* 1 if record broken into fields */
+extern int donerec; /* 1 if record is valid (no fld has changed */
+extern char inputFS[]; /* FS at time of input, for field splitting */
+
+extern int dbg;
+
+extern char *patbeg; /* beginning of pattern matched */
+extern int patlen; /* length of pattern matched. set in b.c */
+
+/* Cell: all information about a variable or constant */
+
+typedef struct Cell {
+ uschar ctype; /* OCELL, OBOOL, OJUMP, etc. */
+ uschar csub; /* CCON, CTEMP, CFLD, etc. */
+ char *nval; /* name, for variables only */
+ char *sval; /* string value */
+ Awkfloat fval; /* value as number */
+ int tval; /* type info: STR|NUM|ARR|FCN|FLD|CON|DONTFREE */
+ struct Cell *cnext; /* ptr to next if chained */
+} Cell;
+
+typedef struct Array { /* symbol table array */
+ int nelem; /* elements in table right now */
+ int size; /* size of tab */
+ Cell **tab; /* hash table pointers */
+} Array;
+
+#define NSYMTAB 50 /* initial size of a symbol table */
+extern Array *symtab;
+
+extern Cell *nrloc; /* NR */
+extern Cell *fnrloc; /* FNR */
+extern Cell *nfloc; /* NF */
+extern Cell *rstartloc; /* RSTART */
+extern Cell *rlengthloc; /* RLENGTH */
+
+/* Cell.tval values: */
+#define NUM 01 /* number value is valid */
+#define STR 02 /* string value is valid */
+#define DONTFREE 04 /* string space is not freeable */
+#define CON 010 /* this is a constant */
+#define ARR 020 /* this is an array */
+#define FCN 040 /* this is a function name */
+#define FLD 0100 /* this is a field $1, $2, ... */
+#define REC 0200 /* this is $0 */
+
+
+/* function types */
+#define FLENGTH 1
+#define FSQRT 2
+#define FEXP 3
+#define FLOG 4
+#define FINT 5
+#define FSYSTEM 6
+#define FRAND 7
+#define FSRAND 8
+#define FSIN 9
+#define FCOS 10
+#define FATAN 11
+#define FTOUPPER 12
+#define FTOLOWER 13
+#define FFLUSH 14
+#define FAND 15
+#define FFOR 16
+#define FXOR 17
+#define FCOMPL 18
+#define FLSHIFT 19
+#define FRSHIFT 20
+
+/* Node: parse tree is made of nodes, with Cell's at bottom */
+
+typedef struct Node {
+ int ntype;
+ struct Node *nnext;
+ int lineno;
+ int nobj;
+ struct Node *narg[1]; /* variable: actual size set by calling malloc */
+} Node;
+
+#define NIL ((Node *) 0)
+
+extern Node *winner;
+extern Node *nullstat;
+extern Node *nullnode;
+
+/* ctypes */
+#define OCELL 1
+#define OBOOL 2
+#define OJUMP 3
+
+/* Cell subtypes: csub */
+#define CFREE 7
+#define CCOPY 6
+#define CCON 5
+#define CTEMP 4
+#define CNAME 3
+#define CVAR 2
+#define CFLD 1
+#define CUNK 0
+
+/* bool subtypes */
+#define BTRUE 11
+#define BFALSE 12
+
+/* jump subtypes */
+#define JEXIT 21
+#define JNEXT 22
+#define JBREAK 23
+#define JCONT 24
+#define JRET 25
+#define JNEXTFILE 26
+
+/* node types */
+#define NVALUE 1
+#define NSTAT 2
+#define NEXPR 3
+
+
+extern int pairstack[], paircnt;
+
+#define notlegal(n) (n <= FIRSTTOKEN || n >= LASTTOKEN || proctab[n-FIRSTTOKEN] == nullproc)
+#define isvalue(n) ((n)->ntype == NVALUE)
+#define isexpr(n) ((n)->ntype == NEXPR)
+#define isjump(n) ((n)->ctype == OJUMP)
+#define isexit(n) ((n)->csub == JEXIT)
+#define isbreak(n) ((n)->csub == JBREAK)
+#define iscont(n) ((n)->csub == JCONT)
+#define isnext(n) ((n)->csub == JNEXT || (n)->csub == JNEXTFILE)
+#define isret(n) ((n)->csub == JRET)
+#define isrec(n) ((n)->tval & REC)
+#define isfld(n) ((n)->tval & FLD)
+#define isstr(n) ((n)->tval & STR)
+#define isnum(n) ((n)->tval & NUM)
+#define isarr(n) ((n)->tval & ARR)
+#define isfcn(n) ((n)->tval & FCN)
+#define istrue(n) ((n)->csub == BTRUE)
+#define istemp(n) ((n)->csub == CTEMP)
+#define isargument(n) ((n)->nobj == ARG)
+/* #define freeable(p) (!((p)->tval & DONTFREE)) */
+#define freeable(p) ( ((p)->tval & (STR|DONTFREE)) == STR )
+
+/* structures used by regular expression matching machinery, mostly b.c: */
+
+#define NCHARS (256+3) /* 256 handles 8-bit chars; 128 does 7-bit */
+ /* watch out in match(), etc. */
+#define NSTATES 32
+
+typedef struct rrow {
+ long ltype; /* long avoids pointer warnings on 64-bit */
+ union {
+ int i;
+ Node *np;
+ uschar *up;
+ } lval; /* because Al stores a pointer in it! */
+ int *lfollow;
+} rrow;
+
+typedef struct fa {
+ uschar gototab[NSTATES][NCHARS];
+ uschar out[NSTATES];
+ uschar *restr;
+ int *posns[NSTATES];
+ int anchor;
+ int use;
+ int initstat;
+ int curstat;
+ int accept;
+ int reset;
+ struct rrow re[1]; /* variable: actual size set by calling malloc */
+} fa;
+
+
+#include "proto.h"
diff --git a/awk/awkgram.y b/awk/awkgram.y
@@ -0,0 +1,487 @@
+/* $OpenBSD: awkgram.y,v 1.9 2011/09/28 19:27:18 millert Exp $ */
+/****************************************************************
+Copyright (C) Lucent Technologies 1997
+All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and
+its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that the copyright notice and this
+permission notice and warranty disclaimer appear in supporting
+documentation, and that the name Lucent Technologies or any of
+its entities not be used in advertising or publicity pertaining
+to distribution of the software without specific, written prior
+permission.
+
+LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
+SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+****************************************************************/
+
+%{
+#include <stdio.h>
+#include <string.h>
+#include "awk.h"
+
+void checkdup(Node *list, Cell *item);
+int yywrap(void) { return(1); }
+
+Node *beginloc = 0;
+Node *endloc = 0;
+int infunc = 0; /* = 1 if in arglist or body of func */
+int inloop = 0; /* = 1 if in while, for, do */
+char *curfname = 0; /* current function name */
+Node *arglist = 0; /* list of args for current function */
+%}
+
+%union {
+ Node *p;
+ Cell *cp;
+ int i;
+ char *s;
+}
+
+%token <i> FIRSTTOKEN /* must be first */
+%token <p> PROGRAM PASTAT PASTAT2 XBEGIN XEND
+%token <i> NL ',' '{' '(' '|' ';' '/' ')' '}' '[' ']'
+%token <i> ARRAY
+%token <i> MATCH NOTMATCH MATCHOP
+%token <i> FINAL DOT ALL CCL NCCL CHAR OR STAR QUEST PLUS EMPTYRE
+%token <i> AND BOR APPEND EQ GE GT LE LT NE IN
+%token <i> ARG BLTIN BREAK CLOSE CONTINUE DELETE DO EXIT FOR FUNC
+%token <i> SUB GSUB IF INDEX LSUBSTR MATCHFCN NEXT NEXTFILE
+%token <i> ADD MINUS MULT DIVIDE MOD
+%token <i> ASSIGN ASGNOP ADDEQ SUBEQ MULTEQ DIVEQ MODEQ POWEQ
+%token <i> PRINT PRINTF SPRINTF
+%token <p> ELSE INTEST CONDEXPR
+%token <i> POSTINCR PREINCR POSTDECR PREDECR
+%token <cp> VAR IVAR VARNF CALL NUMBER STRING
+%token <s> REGEXPR
+
+%type <p> pas pattern ppattern plist pplist patlist prarg term re
+%type <p> pa_pat pa_stat pa_stats
+%type <s> reg_expr
+%type <p> simple_stmt opt_simple_stmt stmt stmtlist
+%type <p> var varname funcname varlist
+%type <p> for if else while
+%type <i> do st
+%type <i> pst opt_pst lbrace rbrace rparen comma nl opt_nl and bor
+%type <i> subop print
+
+%right ASGNOP
+%right '?'
+%right ':'
+%left BOR
+%left AND
+%left GETLINE
+%nonassoc APPEND EQ GE GT LE LT NE MATCHOP IN '|'
+%left ARG BLTIN BREAK CALL CLOSE CONTINUE DELETE DO EXIT FOR FUNC
+%left GSUB IF INDEX LSUBSTR MATCHFCN NEXT NUMBER
+%left PRINT PRINTF RETURN SPLIT SPRINTF STRING SUB SUBSTR
+%left REGEXPR VAR VARNF IVAR WHILE '('
+%left CAT
+%left '+' '-'
+%left '*' '/' '%'
+%left NOT UMINUS
+%right POWER
+%right DECR INCR
+%left INDIRECT
+%token LASTTOKEN /* must be last */
+
+%%
+
+program:
+ pas { if (errorflag==0)
+ winner = (Node *)stat3(PROGRAM, beginloc, $1, endloc); }
+ | error { yyclearin; bracecheck(); SYNTAX("bailing out"); }
+ ;
+
+and:
+ AND | and NL
+ ;
+
+bor:
+ BOR | bor NL
+ ;
+
+comma:
+ ',' | comma NL
+ ;
+
+do:
+ DO | do NL
+ ;
+
+else:
+ ELSE | else NL
+ ;
+
+for:
+ FOR '(' opt_simple_stmt ';' opt_nl pattern ';' opt_nl opt_simple_stmt rparen {inloop++;} stmt
+ { --inloop; $$ = stat4(FOR, $3, notnull($6), $9, $12); }
+ | FOR '(' opt_simple_stmt ';' ';' opt_nl opt_simple_stmt rparen {inloop++;} stmt
+ { --inloop; $$ = stat4(FOR, $3, NIL, $7, $10); }
+ | FOR '(' varname IN varname rparen {inloop++;} stmt
+ { --inloop; $$ = stat3(IN, $3, makearr($5), $8); }
+ ;
+
+funcname:
+ VAR { setfname($1); }
+ | CALL { setfname($1); }
+ ;
+
+if:
+ IF '(' pattern rparen { $$ = notnull($3); }
+ ;
+
+lbrace:
+ '{' | lbrace NL
+ ;
+
+nl:
+ NL | nl NL
+ ;
+
+opt_nl:
+ /* empty */ { $$ = 0; }
+ | nl
+ ;
+
+opt_pst:
+ /* empty */ { $$ = 0; }
+ | pst
+ ;
+
+
+opt_simple_stmt:
+ /* empty */ { $$ = 0; }
+ | simple_stmt
+ ;
+
+pas:
+ opt_pst { $$ = 0; }
+ | opt_pst pa_stats opt_pst { $$ = $2; }
+ ;
+
+pa_pat:
+ pattern { $$ = notnull($1); }
+ ;
+
+pa_stat:
+ pa_pat { $$ = stat2(PASTAT, $1, stat2(PRINT, rectonode(), NIL)); }
+ | pa_pat lbrace stmtlist '}' { $$ = stat2(PASTAT, $1, $3); }
+ | pa_pat ',' opt_nl pa_pat { $$ = pa2stat($1, $4, stat2(PRINT, rectonode(), NIL)); }
+ | pa_pat ',' opt_nl pa_pat lbrace stmtlist '}' { $$ = pa2stat($1, $4, $6); }
+ | lbrace stmtlist '}' { $$ = stat2(PASTAT, NIL, $2); }
+ | XBEGIN lbrace stmtlist '}'
+ { beginloc = linkum(beginloc, $3); $$ = 0; }
+ | XEND lbrace stmtlist '}'
+ { endloc = linkum(endloc, $3); $$ = 0; }
+ | FUNC funcname '(' varlist rparen {infunc++;} lbrace stmtlist '}'
+ { infunc--; curfname=0; defn((Cell *)$2, $4, $8); $$ = 0; }
+ ;
+
+pa_stats:
+ pa_stat
+ | pa_stats opt_pst pa_stat { $$ = linkum($1, $3); }
+ ;
+
+patlist:
+ pattern
+ | patlist comma pattern { $$ = linkum($1, $3); }
+ ;
+
+ppattern:
+ var ASGNOP ppattern { $$ = op2($2, $1, $3); }
+ | ppattern '?' ppattern ':' ppattern %prec '?'
+ { $$ = op3(CONDEXPR, notnull($1), $3, $5); }
+ | ppattern bor ppattern %prec BOR
+ { $$ = op2(BOR, notnull($1), notnull($3)); }
+ | ppattern and ppattern %prec AND
+ { $$ = op2(AND, notnull($1), notnull($3)); }
+ | ppattern MATCHOP reg_expr { $$ = op3($2, NIL, $1, (Node*)makedfa($3, 0)); }
+ | ppattern MATCHOP ppattern
+ { if (constnode($3))
+ $$ = op3($2, NIL, $1, (Node*)makedfa(strnode($3), 0));
+ else
+ $$ = op3($2, (Node *)1, $1, $3); }
+ | ppattern IN varname { $$ = op2(INTEST, $1, makearr($3)); }
+ | '(' plist ')' IN varname { $$ = op2(INTEST, $2, makearr($5)); }
+ | ppattern term %prec CAT { $$ = op2(CAT, $1, $2); }
+ | re
+ | term
+ ;
+
+pattern:
+ var ASGNOP pattern { $$ = op2($2, $1, $3); }
+ | pattern '?' pattern ':' pattern %prec '?'
+ { $$ = op3(CONDEXPR, notnull($1), $3, $5); }
+ | pattern bor pattern %prec BOR
+ { $$ = op2(BOR, notnull($1), notnull($3)); }
+ | pattern and pattern %prec AND
+ { $$ = op2(AND, notnull($1), notnull($3)); }
+ | pattern EQ pattern { $$ = op2($2, $1, $3); }
+ | pattern GE pattern { $$ = op2($2, $1, $3); }
+ | pattern GT pattern { $$ = op2($2, $1, $3); }
+ | pattern LE pattern { $$ = op2($2, $1, $3); }
+ | pattern LT pattern { $$ = op2($2, $1, $3); }
+ | pattern NE pattern { $$ = op2($2, $1, $3); }
+ | pattern MATCHOP reg_expr { $$ = op3($2, NIL, $1, (Node*)makedfa($3, 0)); }
+ | pattern MATCHOP pattern
+ { if (constnode($3))
+ $$ = op3($2, NIL, $1, (Node*)makedfa(strnode($3), 0));
+ else
+ $$ = op3($2, (Node *)1, $1, $3); }
+ | pattern IN varname { $$ = op2(INTEST, $1, makearr($3)); }
+ | '(' plist ')' IN varname { $$ = op2(INTEST, $2, makearr($5)); }
+ | pattern '|' GETLINE var {
+ if (safe) SYNTAX("cmd | getline is unsafe");
+ else $$ = op3(GETLINE, $4, itonp($2), $1); }
+ | pattern '|' GETLINE {
+ if (safe) SYNTAX("cmd | getline is unsafe");
+ else $$ = op3(GETLINE, (Node*)0, itonp($2), $1); }
+ | pattern term %prec CAT { $$ = op2(CAT, $1, $2); }
+ | re
+ | term
+ ;
+
+plist:
+ pattern comma pattern { $$ = linkum($1, $3); }
+ | plist comma pattern { $$ = linkum($1, $3); }
+ ;
+
+pplist:
+ ppattern
+ | pplist comma ppattern { $$ = linkum($1, $3); }
+ ;
+
+prarg:
+ /* empty */ { $$ = rectonode(); }
+ | pplist
+ | '(' plist ')' { $$ = $2; }
+ ;
+
+print:
+ PRINT | PRINTF
+ ;
+
+pst:
+ NL | ';' | pst NL | pst ';'
+ ;
+
+rbrace:
+ '}' | rbrace NL
+ ;
+
+re:
+ reg_expr
+ { $$ = op3(MATCH, NIL, rectonode(), (Node*)makedfa($1, 0)); }
+ | NOT re { $$ = op1(NOT, notnull($2)); }
+ ;
+
+reg_expr:
+ '/' {startreg();} REGEXPR '/' { $$ = $3; }
+ ;
+
+rparen:
+ ')' | rparen NL
+ ;
+
+simple_stmt:
+ print prarg '|' term {
+ if (safe) SYNTAX("print | is unsafe");
+ else $$ = stat3($1, $2, itonp($3), $4); }
+ | print prarg APPEND term {
+ if (safe) SYNTAX("print >> is unsafe");
+ else $$ = stat3($1, $2, itonp($3), $4); }
+ | print prarg GT term {
+ if (safe) SYNTAX("print > is unsafe");
+ else $$ = stat3($1, $2, itonp($3), $4); }
+ | print prarg { $$ = stat3($1, $2, NIL, NIL); }
+ | DELETE varname '[' patlist ']' { $$ = stat2(DELETE, makearr($2), $4); }
+ | DELETE varname { $$ = stat2(DELETE, makearr($2), 0); }
+ | pattern { $$ = exptostat($1); }
+ | error { yyclearin; SYNTAX("illegal statement"); }
+ ;
+
+st:
+ nl
+ | ';' opt_nl
+ ;
+
+stmt:
+ BREAK st { if (!inloop) SYNTAX("break illegal outside of loops");
+ $$ = stat1(BREAK, NIL); }
+ | CONTINUE st { if (!inloop) SYNTAX("continue illegal outside of loops");
+ $$ = stat1(CONTINUE, NIL); }
+ | do {inloop++;} stmt {--inloop;} WHILE '(' pattern ')' st
+ { $$ = stat2(DO, $3, notnull($7)); }
+ | EXIT pattern st { $$ = stat1(EXIT, $2); }
+ | EXIT st { $$ = stat1(EXIT, NIL); }
+ | for
+ | if stmt else stmt { $$ = stat3(IF, $1, $2, $4); }
+ | if stmt { $$ = stat3(IF, $1, $2, NIL); }
+ | lbrace stmtlist rbrace { $$ = $2; }
+ | NEXT st { if (infunc)
+ SYNTAX("next is illegal inside a function");
+ $$ = stat1(NEXT, NIL); }
+ | NEXTFILE st { if (infunc)
+ SYNTAX("nextfile is illegal inside a function");
+ $$ = stat1(NEXTFILE, NIL); }
+ | RETURN pattern st { $$ = stat1(RETURN, $2); }
+ | RETURN st { $$ = stat1(RETURN, NIL); }
+ | simple_stmt st
+ | while {inloop++;} stmt { --inloop; $$ = stat2(WHILE, $1, $3); }
+ | ';' opt_nl { $$ = 0; }
+ ;
+
+stmtlist:
+ stmt
+ | stmtlist stmt { $$ = linkum($1, $2); }
+ ;
+
+subop:
+ SUB | GSUB
+ ;
+
+term:
+ term '/' ASGNOP term { $$ = op2(DIVEQ, $1, $4); }
+ | term '+' term { $$ = op2(ADD, $1, $3); }
+ | term '-' term { $$ = op2(MINUS, $1, $3); }
+ | term '*' term { $$ = op2(MULT, $1, $3); }
+ | term '/' term { $$ = op2(DIVIDE, $1, $3); }
+ | term '%' term { $$ = op2(MOD, $1, $3); }
+ | term POWER term { $$ = op2(POWER, $1, $3); }
+ | '-' term %prec UMINUS { $$ = op1(UMINUS, $2); }
+ | '+' term %prec UMINUS { $$ = $2; }
+ | NOT term %prec UMINUS { $$ = op1(NOT, notnull($2)); }
+ | BLTIN '(' ')' { $$ = op2(BLTIN, itonp($1), rectonode()); }
+ | BLTIN '(' patlist ')' { $$ = op2(BLTIN, itonp($1), $3); }
+ | BLTIN { $$ = op2(BLTIN, itonp($1), rectonode()); }
+ | CALL '(' ')' { $$ = op2(CALL, celltonode($1,CVAR), NIL); }
+ | CALL '(' patlist ')' { $$ = op2(CALL, celltonode($1,CVAR), $3); }
+ | CLOSE term { $$ = op1(CLOSE, $2); }
+ | DECR var { $$ = op1(PREDECR, $2); }
+ | INCR var { $$ = op1(PREINCR, $2); }
+ | var DECR { $$ = op1(POSTDECR, $1); }
+ | var INCR { $$ = op1(POSTINCR, $1); }
+ | GETLINE var LT term { $$ = op3(GETLINE, $2, itonp($3), $4); }
+ | GETLINE LT term { $$ = op3(GETLINE, NIL, itonp($2), $3); }
+ | GETLINE var { $$ = op3(GETLINE, $2, NIL, NIL); }
+ | GETLINE { $$ = op3(GETLINE, NIL, NIL, NIL); }
+ | INDEX '(' pattern comma pattern ')'
+ { $$ = op2(INDEX, $3, $5); }
+ | INDEX '(' pattern comma reg_expr ')'
+ { SYNTAX("index() doesn't permit regular expressions");
+ $$ = op2(INDEX, $3, (Node*)$5); }
+ | '(' pattern ')' { $$ = $2; }
+ | MATCHFCN '(' pattern comma reg_expr ')'
+ { $$ = op3(MATCHFCN, NIL, $3, (Node*)makedfa($5, 1)); }
+ | MATCHFCN '(' pattern comma pattern ')'
+ { if (constnode($5))
+ $$ = op3(MATCHFCN, NIL, $3, (Node*)makedfa(strnode($5), 1));
+ else
+ $$ = op3(MATCHFCN, (Node *)1, $3, $5); }
+ | NUMBER { $$ = celltonode($1, CCON); }
+ | SPLIT '(' pattern comma varname comma pattern ')' /* string */
+ { $$ = op4(SPLIT, $3, makearr($5), $7, (Node*)STRING); }
+ | SPLIT '(' pattern comma varname comma reg_expr ')' /* const /regexp/ */
+ { $$ = op4(SPLIT, $3, makearr($5), (Node*)makedfa($7, 1), (Node *)REGEXPR); }
+ | SPLIT '(' pattern comma varname ')'
+ { $$ = op4(SPLIT, $3, makearr($5), NIL, (Node*)STRING); } /* default */
+ | SPRINTF '(' patlist ')' { $$ = op1($1, $3); }
+ | STRING { $$ = celltonode($1, CCON); }
+ | subop '(' reg_expr comma pattern ')'
+ { $$ = op4($1, NIL, (Node*)makedfa($3, 1), $5, rectonode()); }
+ | subop '(' pattern comma pattern ')'
+ { if (constnode($3))
+ $$ = op4($1, NIL, (Node*)makedfa(strnode($3), 1), $5, rectonode());
+ else
+ $$ = op4($1, (Node *)1, $3, $5, rectonode()); }
+ | subop '(' reg_expr comma pattern comma var ')'
+ { $$ = op4($1, NIL, (Node*)makedfa($3, 1), $5, $7); }
+ | subop '(' pattern comma pattern comma var ')'
+ { if (constnode($3))
+ $$ = op4($1, NIL, (Node*)makedfa(strnode($3), 1), $5, $7);
+ else
+ $$ = op4($1, (Node *)1, $3, $5, $7); }
+ | SUBSTR '(' pattern comma pattern comma pattern ')'
+ { $$ = op3(SUBSTR, $3, $5, $7); }
+ | SUBSTR '(' pattern comma pattern ')'
+ { $$ = op3(SUBSTR, $3, $5, NIL); }
+ | var
+ ;
+
+var:
+ varname
+ | varname '[' patlist ']' { $$ = op2(ARRAY, makearr($1), $3); }
+ | IVAR { $$ = op1(INDIRECT, celltonode($1, CVAR)); }
+ | INDIRECT term { $$ = op1(INDIRECT, $2); }
+ ;
+
+varlist:
+ /* nothing */ { arglist = $$ = 0; }
+ | VAR { arglist = $$ = celltonode($1,CVAR); }
+ | varlist comma VAR {
+ checkdup($1, $3);
+ arglist = $$ = linkum($1,celltonode($3,CVAR)); }
+ ;
+
+varname:
+ VAR { $$ = celltonode($1, CVAR); }
+ | ARG { $$ = op1(ARG, itonp($1)); }
+ | VARNF { $$ = op1(VARNF, (Node *) $1); }
+ ;
+
+
+while:
+ WHILE '(' pattern rparen { $$ = notnull($3); }
+ ;
+
+%%
+
+void setfname(Cell *p)
+{
+ if (isarr(p))
+ SYNTAX("%s is an array, not a function", p->nval);
+ else if (isfcn(p))
+ SYNTAX("you can't define function %s more than once", p->nval);
+ curfname = p->nval;
+}
+
+int constnode(Node *p)
+{
+ return isvalue(p) && ((Cell *) (p->narg[0]))->csub == CCON;
+}
+
+char *strnode(Node *p)
+{
+ return ((Cell *)(p->narg[0]))->sval;
+}
+
+Node *notnull(Node *n)
+{
+ switch (n->nobj) {
+ case LE: case LT: case EQ: case NE: case GT: case GE:
+ case BOR: case AND: case NOT:
+ return n;
+ default:
+ return op2(NE, n, nullnode);
+ }
+}
+
+void checkdup(Node *vl, Cell *cp) /* check if name already in list */
+{
+ char *s = cp->nval;
+ for ( ; vl; vl = vl->nnext) {
+ if (strcmp(s, ((Cell *)(vl->narg[0]))->nval) == 0) {
+ SYNTAX("duplicate argument %s", s);
+ break;
+ }
+ }
+}
diff --git a/awk/b.c b/awk/b.c
@@ -0,0 +1,958 @@
+/* $OpenBSD: b.c,v 1.17 2011/09/28 19:27:18 millert Exp $ */
+/****************************************************************
+Copyright (C) Lucent Technologies 1997
+All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and
+its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that the copyright notice and this
+permission notice and warranty disclaimer appear in supporting
+documentation, and that the name Lucent Technologies or any of
+its entities not be used in advertising or publicity pertaining
+to distribution of the software without specific, written prior
+permission.
+
+LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
+SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+****************************************************************/
+
+/* lasciate ogne speranza, voi ch'intrate. */
+
+#define DEBUG
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "awk.h"
+#include "ytab.h"
+
+#define HAT (NCHARS+2) /* matches ^ in regular expr */
+ /* NCHARS is 2**n */
+#define MAXLIN 22
+
+#define type(v) (v)->nobj /* badly overloaded here */
+#define info(v) (v)->ntype /* badly overloaded here */
+#define left(v) (v)->narg[0]
+#define right(v) (v)->narg[1]
+#define parent(v) (v)->nnext
+
+#define LEAF case CCL: case NCCL: case CHAR: case DOT: case FINAL: case ALL:
+#define ELEAF case EMPTYRE: /* empty string in regexp */
+#define UNARY case STAR: case PLUS: case QUEST:
+
+/* encoding in tree Nodes:
+ leaf (CCL, NCCL, CHAR, DOT, FINAL, ALL, EMPTYRE):
+ left is index, right contains value or pointer to value
+ unary (STAR, PLUS, QUEST): left is child, right is null
+ binary (CAT, OR): left and right are children
+ parent contains pointer to parent
+*/
+
+
+int *setvec;
+int *tmpset;
+int maxsetvec = 0;
+
+int rtok; /* next token in current re */
+int rlxval;
+static uschar *rlxstr;
+static uschar *prestr; /* current position in current re */
+static uschar *lastre; /* origin of last re */
+
+static int setcnt;
+static int poscnt;
+
+char *patbeg;
+int patlen;
+
+#define NFA 20 /* cache this many dynamic fa's */
+fa *fatab[NFA];
+int nfatab = 0; /* entries in fatab */
+
+fa *makedfa(const char *s, int anchor) /* returns dfa for reg expr s */
+{
+ int i, use, nuse;
+ fa *pfa;
+ static int now = 1;
+
+ if (setvec == 0) { /* first time through any RE */
+ maxsetvec = MAXLIN;
+ setvec = (int *) calloc(maxsetvec, sizeof(int));
+ tmpset = (int *) calloc(maxsetvec, sizeof(int));
+ if (setvec == 0 || tmpset == 0)
+ overflo("out of space initializing makedfa");
+ }
+
+ if (compile_time) /* a constant for sure */
+ return mkdfa(s, anchor);
+ for (i = 0; i < nfatab; i++) /* is it there already? */
+ if (fatab[i]->anchor == anchor
+ && strcmp((const char *) fatab[i]->restr, s) == 0) {
+ fatab[i]->use = now++;
+ return fatab[i];
+ }
+ pfa = mkdfa(s, anchor);
+ if (nfatab < NFA) { /* room for another */
+ fatab[nfatab] = pfa;
+ fatab[nfatab]->use = now++;
+ nfatab++;
+ return pfa;
+ }
+ use = fatab[0]->use; /* replace least-recently used */
+ nuse = 0;
+ for (i = 1; i < nfatab; i++)
+ if (fatab[i]->use < use) {
+ use = fatab[i]->use;
+ nuse = i;
+ }
+ freefa(fatab[nuse]);
+ fatab[nuse] = pfa;
+ pfa->use = now++;
+ return pfa;
+}
+
+fa *mkdfa(const char *s, int anchor) /* does the real work of making a dfa */
+ /* anchor = 1 for anchored matches, else 0 */
+{
+ Node *p, *p1;
+ fa *f;
+
+ p = reparse(s);
+ p1 = op2(CAT, op2(STAR, op2(ALL, NIL, NIL), NIL), p);
+ /* put ALL STAR in front of reg. exp. */
+ p1 = op2(CAT, p1, op2(FINAL, NIL, NIL));
+ /* put FINAL after reg. exp. */
+
+ poscnt = 0;
+ penter(p1); /* enter parent pointers and leaf indices */
+ if ((f = (fa *) calloc(1, sizeof(fa) + poscnt*sizeof(rrow))) == NULL)
+ overflo("out of space for fa");
+ f->accept = poscnt-1; /* penter has computed number of positions in re */
+ cfoll(f, p1); /* set up follow sets */
+ freetr(p1);
+ if ((f->posns[0] = (int *) calloc(*(f->re[0].lfollow), sizeof(int))) == NULL)
+ overflo("out of space in makedfa");
+ if ((f->posns[1] = (int *) calloc(1, sizeof(int))) == NULL)
+ overflo("out of space in makedfa");
+ *f->posns[1] = 0;
+ f->initstat = makeinit(f, anchor);
+ f->anchor = anchor;
+ f->restr = (uschar *) tostring(s);
+ return f;
+}
+
+int makeinit(fa *f, int anchor)
+{
+ int i, k;
+
+ f->curstat = 2;
+ f->out[2] = 0;
+ f->reset = 0;
+ k = *(f->re[0].lfollow);
+ xfree(f->posns[2]);
+ if ((f->posns[2] = (int *) calloc(k+1, sizeof(int))) == NULL)
+ overflo("out of space in makeinit");
+ for (i=0; i <= k; i++) {
+ (f->posns[2])[i] = (f->re[0].lfollow)[i];
+ }
+ if ((f->posns[2])[1] == f->accept)
+ f->out[2] = 1;
+ for (i=0; i < NCHARS; i++)
+ f->gototab[2][i] = 0;
+ f->curstat = cgoto(f, 2, HAT);
+ if (anchor) {
+ *f->posns[2] = k-1; /* leave out position 0 */
+ for (i=0; i < k; i++) {
+ (f->posns[0])[i] = (f->posns[2])[i];
+ }
+
+ f->out[0] = f->out[2];
+ if (f->curstat != 2)
+ --(*f->posns[f->curstat]);
+ }
+ return f->curstat;
+}
+
+void penter(Node *p) /* set up parent pointers and leaf indices */
+{
+ switch (type(p)) {
+ ELEAF
+ LEAF
+ info(p) = poscnt;
+ poscnt++;
+ break;
+ UNARY
+ penter(left(p));
+ parent(left(p)) = p;
+ break;
+ case CAT:
+ case OR:
+ penter(left(p));
+ penter(right(p));
+ parent(left(p)) = p;
+ parent(right(p)) = p;
+ break;
+ default: /* can't happen */
+ FATAL("can't happen: unknown type %d in penter", type(p));
+ break;
+ }
+}
+
+void freetr(Node *p) /* free parse tree */
+{
+ switch (type(p)) {
+ ELEAF
+ LEAF
+ xfree(p);
+ break;
+ UNARY
+ freetr(left(p));
+ xfree(p);
+ break;
+ case CAT:
+ case OR:
+ freetr(left(p));
+ freetr(right(p));
+ xfree(p);
+ break;
+ default: /* can't happen */
+ FATAL("can't happen: unknown type %d in freetr", type(p));
+ break;
+ }
+}
+
+/* in the parsing of regular expressions, metacharacters like . have */
+/* to be seen literally; \056 is not a metacharacter. */
+
+int hexstr(uschar **pp) /* find and eval hex string at pp, return new p */
+{ /* only pick up one 8-bit byte (2 chars) */
+ uschar *p;
+ int n = 0;
+ int i;
+
+ for (i = 0, p = (uschar *) *pp; i < 2 && isxdigit(*p); i++, p++) {
+ if (isdigit(*p))
+ n = 16 * n + *p - '0';
+ else if (*p >= 'a' && *p <= 'f')
+ n = 16 * n + *p - 'a' + 10;
+ else if (*p >= 'A' && *p <= 'F')
+ n = 16 * n + *p - 'A' + 10;
+ }
+ *pp = (uschar *) p;
+ return n;
+}
+
+#define isoctdigit(c) ((c) >= '0' && (c) <= '7') /* multiple use of arg */
+
+int quoted(uschar **pp) /* pick up next thing after a \\ */
+ /* and increment *pp */
+{
+ uschar *p = *pp;
+ int c;
+
+ if ((c = *p++) == 't')
+ c = '\t';
+ else if (c == 'n')
+ c = '\n';
+ else if (c == 'f')
+ c = '\f';
+ else if (c == 'r')
+ c = '\r';
+ else if (c == 'b')
+ c = '\b';
+ else if (c == '\\')
+ c = '\\';
+ else if (c == 'x') { /* hexadecimal goo follows */
+ c = hexstr(&p); /* this adds a null if number is invalid */
+ } else if (isoctdigit(c)) { /* \d \dd \ddd */
+ int n = c - '0';
+ if (isoctdigit(*p)) {
+ n = 8 * n + *p++ - '0';
+ if (isoctdigit(*p))
+ n = 8 * n + *p++ - '0';
+ }
+ c = n;
+ } /* else */
+ /* c = c; */
+ *pp = p;
+ return c;
+}
+
+char *cclenter(const char *argp) /* add a character class */
+{
+ int i, c, c2;
+ uschar *p = (uschar *) argp;
+ uschar *op, *bp;
+ static uschar *buf = 0;
+ static int bufsz = 100;
+
+ op = p;
+ if (buf == 0 && (buf = (uschar *) malloc(bufsz)) == NULL)
+ FATAL("out of space for character class [%.10s...] 1", p);
+ bp = buf;
+ for (i = 0; (c = *p++) != 0; ) {
+ if (c == '\\') {
+ c = quoted(&p);
+ } else if (c == '-' && i > 0 && bp[-1] != 0) {
+ if (*p != 0) {
+ c = bp[-1];
+ c2 = *p++;
+ if (c2 == '\\')
+ c2 = quoted(&p);
+ if (c > c2) { /* empty; ignore */
+ bp--;
+ i--;
+ continue;
+ }
+ while (c < c2) {
+ if (!adjbuf((char **) &buf, &bufsz, bp-buf+2, 100, (char **) &bp, "cclenter1"))
+ FATAL("out of space for character class [%.10s...] 2", p);
+ *bp++ = ++c;
+ i++;
+ }
+ continue;
+ }
+ }
+ if (!adjbuf((char **) &buf, &bufsz, bp-buf+2, 100, (char **) &bp, "cclenter2"))
+ FATAL("out of space for character class [%.10s...] 3", p);
+ *bp++ = c;
+ i++;
+ }
+ *bp = 0;
+ dprintf( ("cclenter: in = |%s|, out = |%s|\n", op, buf) );
+ xfree(op);
+ return (char *) tostring((char *) buf);
+}
+
+void overflo(const char *s)
+{
+ FATAL("regular expression too big: %.30s...", s);
+}
+
+void cfoll(fa *f, Node *v) /* enter follow set of each leaf of vertex v into lfollow[leaf] */
+{
+ int i;
+ int *p;
+
+ switch (type(v)) {
+ ELEAF
+ LEAF
+ f->re[info(v)].ltype = type(v);
+ f->re[info(v)].lval.np = right(v);
+ while (f->accept >= maxsetvec) { /* guessing here! */
+ maxsetvec *= 4;
+ setvec = (int *) realloc(setvec, maxsetvec * sizeof(int));
+ tmpset = (int *) realloc(tmpset, maxsetvec * sizeof(int));
+ if (setvec == 0 || tmpset == 0)
+ overflo("out of space in cfoll()");
+ }
+ for (i = 0; i <= f->accept; i++)
+ setvec[i] = 0;
+ setcnt = 0;
+ follow(v); /* computes setvec and setcnt */
+ if ((p = (int *) calloc(setcnt+1, sizeof(int))) == NULL)
+ overflo("out of space building follow set");
+ f->re[info(v)].lfollow = p;
+ *p = setcnt;
+ for (i = f->accept; i >= 0; i--)
+ if (setvec[i] == 1)
+ *++p = i;
+ break;
+ UNARY
+ cfoll(f,left(v));
+ break;
+ case CAT:
+ case OR:
+ cfoll(f,left(v));
+ cfoll(f,right(v));
+ break;
+ default: /* can't happen */
+ FATAL("can't happen: unknown type %d in cfoll", type(v));
+ }
+}
+
+int first(Node *p) /* collects initially active leaves of p into setvec */
+ /* returns 0 if p matches empty string */
+{
+ int b, lp;
+
+ switch (type(p)) {
+ ELEAF
+ LEAF
+ lp = info(p); /* look for high-water mark of subscripts */
+ while (setcnt >= maxsetvec || lp >= maxsetvec) { /* guessing here! */
+ maxsetvec *= 4;
+ setvec = (int *) realloc(setvec, maxsetvec * sizeof(int));
+ tmpset = (int *) realloc(tmpset, maxsetvec * sizeof(int));
+ if (setvec == 0 || tmpset == 0)
+ overflo("out of space in first()");
+ }
+ if (type(p) == EMPTYRE) {
+ setvec[lp] = 0;
+ return(0);
+ }
+ if (setvec[lp] != 1) {
+ setvec[lp] = 1;
+ setcnt++;
+ }
+ if (type(p) == CCL && (*(char *) right(p)) == '\0')
+ return(0); /* empty CCL */
+ else return(1);
+ case PLUS:
+ if (first(left(p)) == 0) return(0);
+ return(1);
+ case STAR:
+ case QUEST:
+ first(left(p));
+ return(0);
+ case CAT:
+ if (first(left(p)) == 0 && first(right(p)) == 0) return(0);
+ return(1);
+ case OR:
+ b = first(right(p));
+ if (first(left(p)) == 0 || b == 0) return(0);
+ return(1);
+ }
+ FATAL("can't happen: unknown type %d in first", type(p)); /* can't happen */
+ return(-1);
+}
+
+void follow(Node *v) /* collects leaves that can follow v into setvec */
+{
+ Node *p;
+
+ if (type(v) == FINAL)
+ return;
+ p = parent(v);
+ switch (type(p)) {
+ case STAR:
+ case PLUS:
+ first(v);
+ follow(p);
+ return;
+
+ case OR:
+ case QUEST:
+ follow(p);
+ return;
+
+ case CAT:
+ if (v == left(p)) { /* v is left child of p */
+ if (first(right(p)) == 0) {
+ follow(p);
+ return;
+ }
+ } else /* v is right child */
+ follow(p);
+ return;
+ }
+}
+
+int member(int c, const char *sarg) /* is c in s? */
+{
+ uschar *s = (uschar *) sarg;
+
+ while (*s)
+ if (c == *s++)
+ return(1);
+ return(0);
+}
+
+int match(fa *f, const char *p0) /* shortest match ? */
+{
+ int s, ns;
+ uschar *p = (uschar *) p0;
+
+ s = f->reset ? makeinit(f,0) : f->initstat;
+ if (f->out[s])
+ return(1);
+ do {
+ /* assert(*p < NCHARS); */
+ if ((ns = f->gototab[s][*p]) != 0)
+ s = ns;
+ else
+ s = cgoto(f, s, *p);
+ if (f->out[s])
+ return(1);
+ } while (*p++ != 0);
+ return(0);
+}
+
+int pmatch(fa *f, const char *p0) /* longest match, for sub */
+{
+ int s, ns;
+ uschar *p = (uschar *) p0;
+ uschar *q;
+ int i, k;
+
+ /* s = f->reset ? makeinit(f,1) : f->initstat; */
+ if (f->reset) {
+ f->initstat = s = makeinit(f,1);
+ } else {
+ s = f->initstat;
+ }
+ patbeg = (char *) p;
+ patlen = -1;
+ do {
+ q = p;
+ do {
+ if (f->out[s]) /* final state */
+ patlen = q-p;
+ /* assert(*q < NCHARS); */
+ if ((ns = f->gototab[s][*q]) != 0)
+ s = ns;
+ else
+ s = cgoto(f, s, *q);
+ if (s == 1) { /* no transition */
+ if (patlen >= 0) {
+ patbeg = (char *) p;
+ return(1);
+ }
+ else
+ goto nextin; /* no match */
+ }
+ } while (*q++ != 0);
+ if (f->out[s])
+ patlen = q-p-1; /* don't count $ */
+ if (patlen >= 0) {
+ patbeg = (char *) p;
+ return(1);
+ }
+ nextin:
+ s = 2;
+ if (f->reset) {
+ for (i = 2; i <= f->curstat; i++)
+ xfree(f->posns[i]);
+ k = *f->posns[0];
+ if ((f->posns[2] = (int *) calloc(k+1, sizeof(int))) == NULL)
+ overflo("out of space in pmatch");
+ for (i = 0; i <= k; i++)
+ (f->posns[2])[i] = (f->posns[0])[i];
+ f->initstat = f->curstat = 2;
+ f->out[2] = f->out[0];
+ for (i = 0; i < NCHARS; i++)
+ f->gototab[2][i] = 0;
+ }
+ } while (*p++ != 0);
+ return (0);
+}
+
+int nematch(fa *f, const char *p0) /* non-empty match, for sub */
+{
+ int s, ns;
+ uschar *p = (uschar *) p0;
+ uschar *q;
+ int i, k;
+
+ /* s = f->reset ? makeinit(f,1) : f->initstat; */
+ if (f->reset) {
+ f->initstat = s = makeinit(f,1);
+ } else {
+ s = f->initstat;
+ }
+ patlen = -1;
+ while (*p) {
+ q = p;
+ do {
+ if (f->out[s]) /* final state */
+ patlen = q-p;
+ /* assert(*q < NCHARS); */
+ if ((ns = f->gototab[s][*q]) != 0)
+ s = ns;
+ else
+ s = cgoto(f, s, *q);
+ if (s == 1) { /* no transition */
+ if (patlen > 0) {
+ patbeg = (char *) p;
+ return(1);
+ } else
+ goto nnextin; /* no nonempty match */
+ }
+ } while (*q++ != 0);
+ if (f->out[s])
+ patlen = q-p-1; /* don't count $ */
+ if (patlen > 0 ) {
+ patbeg = (char *) p;
+ return(1);
+ }
+ nnextin:
+ s = 2;
+ if (f->reset) {
+ for (i = 2; i <= f->curstat; i++)
+ xfree(f->posns[i]);
+ k = *f->posns[0];
+ if ((f->posns[2] = (int *) calloc(k+1, sizeof(int))) == NULL)
+ overflo("out of state space");
+ for (i = 0; i <= k; i++)
+ (f->posns[2])[i] = (f->posns[0])[i];
+ f->initstat = f->curstat = 2;
+ f->out[2] = f->out[0];
+ for (i = 0; i < NCHARS; i++)
+ f->gototab[2][i] = 0;
+ }
+ p++;
+ }
+ return (0);
+}
+
+Node *reparse(const char *p) /* parses regular expression pointed to by p */
+{ /* uses relex() to scan regular expression */
+ Node *np;
+
+ dprintf( ("reparse <%s>\n", p) );
+ lastre = prestr = (uschar *) p; /* prestr points to string to be parsed */
+ rtok = relex();
+ /* GNU compatibility: an empty regexp matches anything */
+ if (rtok == '\0') {
+ /* FATAL("empty regular expression"); previous */
+ return(op2(EMPTYRE, NIL, NIL));
+ }
+ np = regexp();
+ if (rtok != '\0')
+ FATAL("syntax error in regular expression %s at %s", lastre, prestr);
+ return(np);
+}
+
+Node *regexp(void) /* top-level parse of reg expr */
+{
+ return (alt(concat(primary())));
+}
+
+Node *primary(void)
+{
+ Node *np;
+
+ switch (rtok) {
+ case CHAR:
+ np = op2(CHAR, NIL, itonp(rlxval));
+ rtok = relex();
+ return (unary(np));
+ case ALL:
+ rtok = relex();
+ return (unary(op2(ALL, NIL, NIL)));
+ case EMPTYRE:
+ rtok = relex();
+ return (unary(op2(ALL, NIL, NIL)));
+ case DOT:
+ rtok = relex();
+ return (unary(op2(DOT, NIL, NIL)));
+ case CCL:
+ np = op2(CCL, NIL, (Node*) cclenter((char *) rlxstr));
+ rtok = relex();
+ return (unary(np));
+ case NCCL:
+ np = op2(NCCL, NIL, (Node *) cclenter((char *) rlxstr));
+ rtok = relex();
+ return (unary(np));
+ case '^':
+ rtok = relex();
+ return (unary(op2(CHAR, NIL, itonp(HAT))));
+ case '$':
+ rtok = relex();
+ return (unary(op2(CHAR, NIL, NIL)));
+ case '(':
+ rtok = relex();
+ if (rtok == ')') { /* special pleading for () */
+ rtok = relex();
+ return unary(op2(CCL, NIL, (Node *) tostring("")));
+ }
+ np = regexp();
+ if (rtok == ')') {
+ rtok = relex();
+ return (unary(np));
+ }
+ else
+ FATAL("syntax error in regular expression %s at %s", lastre, prestr);
+ default:
+ FATAL("illegal primary in regular expression %s at %s", lastre, prestr);
+ }
+ return 0; /*NOTREACHED*/
+}
+
+Node *concat(Node *np)
+{
+ switch (rtok) {
+ case CHAR: case DOT: case ALL: case EMPTYRE: case CCL: case NCCL: case '$': case '(':
+ return (concat(op2(CAT, np, primary())));
+ }
+ return (np);
+}
+
+Node *alt(Node *np)
+{
+ if (rtok == OR) {
+ rtok = relex();
+ return (alt(op2(OR, np, concat(primary()))));
+ }
+ return (np);
+}
+
+Node *unary(Node *np)
+{
+ switch (rtok) {
+ case STAR:
+ rtok = relex();
+ return (unary(op2(STAR, np, NIL)));
+ case PLUS:
+ rtok = relex();
+ return (unary(op2(PLUS, np, NIL)));
+ case QUEST:
+ rtok = relex();
+ return (unary(op2(QUEST, np, NIL)));
+ default:
+ return (np);
+ }
+}
+
+/*
+ * Character class definitions conformant to the POSIX locale as
+ * defined in IEEE P1003.1 draft 7 of June 2001, assuming the source
+ * and operating character sets are both ASCII (ISO646) or supersets
+ * thereof.
+ *
+ * Note that to avoid overflowing the temporary buffer used in
+ * relex(), the expanded character class (prior to range expansion)
+ * must be less than twice the size of their full name.
+ */
+
+/* Because isblank doesn't show up in any of the header files on any
+ * system i use, it's defined here. if some other locale has a richer
+ * definition of "blank", define HAS_ISBLANK and provide your own
+ * version.
+ * the parentheses here are an attempt to find a path through the maze
+ * of macro definition and/or function and/or version provided. thanks
+ * to nelson beebe for the suggestion; let's see if it works everywhere.
+ */
+
+#ifndef HAS_ISBLANK
+
+int (xisblank)(int c)
+{
+ return c==' ' || c=='\t';
+}
+
+#endif
+
+struct charclass {
+ const char *cc_name;
+ int cc_namelen;
+ int (*cc_func)(int);
+} charclasses[] = {
+ { "alnum", 5, isalnum },
+ { "alpha", 5, isalpha },
+#ifndef HAS_ISBLANK
+ { "blank", 5, isspace }, /* was isblank */
+#else
+ { "blank", 5, isblank },
+#endif
+ { "cntrl", 5, iscntrl },
+ { "digit", 5, isdigit },
+ { "graph", 5, isgraph },
+ { "lower", 5, islower },
+ { "print", 5, isprint },
+ { "punct", 5, ispunct },
+ { "space", 5, isspace },
+ { "upper", 5, isupper },
+ { "xdigit", 6, isxdigit },
+ { NULL, 0, NULL },
+};
+
+
+int relex(void) /* lexical analyzer for reparse */
+{
+ int c, n;
+ int cflag;
+ static uschar *buf = 0;
+ static int bufsz = 100;
+ uschar *bp;
+ struct charclass *cc;
+ int i;
+
+ switch (c = *prestr++) {
+ case '|': return OR;
+ case '*': return STAR;
+ case '+': return PLUS;
+ case '?': return QUEST;
+ case '.': return DOT;
+ case '\0': prestr--; return '\0';
+ case '^':
+ case '$':
+ case '(':
+ case ')':
+ return c;
+ case '\\':
+ rlxval = quoted(&prestr);
+ return CHAR;
+ default:
+ rlxval = c;
+ return CHAR;
+ case '[':
+ if (buf == 0 && (buf = (uschar *) malloc(bufsz)) == NULL)
+ FATAL("out of space in reg expr %.10s..", lastre);
+ bp = buf;
+ if (*prestr == '^') {
+ cflag = 1;
+ prestr++;
+ }
+ else
+ cflag = 0;
+ n = 2 * strlen((const char *) prestr)+1;
+ if (!adjbuf((char **) &buf, &bufsz, n, n, (char **) &bp, "relex1"))
+ FATAL("out of space for reg expr %.10s...", lastre);
+ for (; ; ) {
+ if ((c = *prestr++) == '\\') {
+ *bp++ = '\\';
+ if ((c = *prestr++) == '\0')
+ FATAL("nonterminated character class %.20s...", lastre);
+ *bp++ = c;
+ /* } else if (c == '\n') { */
+ /* FATAL("newline in character class %.20s...", lastre); */
+ } else if (c == '[' && *prestr == ':') {
+ /* POSIX char class names, Dag-Erling Smorgrav, des@ofug.org */
+ for (cc = charclasses; cc->cc_name; cc++)
+ if (strncmp((const char *) prestr + 1, (const char *) cc->cc_name, cc->cc_namelen) == 0)
+ break;
+ if (cc->cc_name != NULL && prestr[1 + cc->cc_namelen] == ':' &&
+ prestr[2 + cc->cc_namelen] == ']') {
+ prestr += cc->cc_namelen + 3;
+ for (i = 0; i < NCHARS; i++) {
+ if (!adjbuf((char **) &buf, &bufsz, bp-buf+1, 100, (char **) &bp, "relex2"))
+ FATAL("out of space for reg expr %.10s...", lastre);
+ if (cc->cc_func(i)) {
+ *bp++ = i;
+ n++;
+ }
+ }
+ } else
+ *bp++ = c;
+ } else if (c == '\0') {
+ FATAL("nonterminated character class %.20s", lastre);
+ } else if (bp == buf) { /* 1st char is special */
+ *bp++ = c;
+ } else if (c == ']') {
+ *bp++ = 0;
+ rlxstr = (uschar *) tostring((char *) buf);
+ if (cflag == 0)
+ return CCL;
+ else
+ return NCCL;
+ } else
+ *bp++ = c;
+ }
+ }
+}
+
+int cgoto(fa *f, int s, int c)
+{
+ int i, j, k;
+ int *p, *q;
+
+ assert(c == HAT || c < NCHARS);
+ while (f->accept >= maxsetvec) { /* guessing here! */
+ maxsetvec *= 4;
+ setvec = (int *) realloc(setvec, maxsetvec * sizeof(int));
+ tmpset = (int *) realloc(tmpset, maxsetvec * sizeof(int));
+ if (setvec == 0 || tmpset == 0)
+ overflo("out of space in cgoto()");
+ }
+ for (i = 0; i <= f->accept; i++)
+ setvec[i] = 0;
+ setcnt = 0;
+ /* compute positions of gototab[s,c] into setvec */
+ p = f->posns[s];
+ for (i = 1; i <= *p; i++) {
+ if ((k = f->re[p[i]].ltype) != FINAL) {
+ if ((k == CHAR && c == ptoi(f->re[p[i]].lval.np))
+ || (k == DOT && c != 0 && c != HAT)
+ || (k == ALL && c != 0)
+ || (k == EMPTYRE && c != 0)
+ || (k == CCL && member(c, (char *) f->re[p[i]].lval.up))
+ || (k == NCCL && !member(c, (char *) f->re[p[i]].lval.up) && c != 0 && c != HAT)) {
+ q = f->re[p[i]].lfollow;
+ for (j = 1; j <= *q; j++) {
+ if (q[j] >= maxsetvec) {
+ maxsetvec *= 4;
+ setvec = (int *) realloc(setvec, maxsetvec * sizeof(int));
+ tmpset = (int *) realloc(tmpset, maxsetvec * sizeof(int));
+ if (setvec == 0 || tmpset == 0)
+ overflo("cgoto overflow");
+ }
+ if (setvec[q[j]] == 0) {
+ setcnt++;
+ setvec[q[j]] = 1;
+ }
+ }
+ }
+ }
+ }
+ /* determine if setvec is a previous state */
+ tmpset[0] = setcnt;
+ j = 1;
+ for (i = f->accept; i >= 0; i--)
+ if (setvec[i]) {
+ tmpset[j++] = i;
+ }
+ /* tmpset == previous state? */
+ for (i = 1; i <= f->curstat; i++) {
+ p = f->posns[i];
+ if ((k = tmpset[0]) != p[0])
+ goto different;
+ for (j = 1; j <= k; j++)
+ if (tmpset[j] != p[j])
+ goto different;
+ /* setvec is state i */
+ f->gototab[s][c] = i;
+ return i;
+ different:;
+ }
+
+ /* add tmpset to current set of states */
+ if (f->curstat >= NSTATES-1) {
+ f->curstat = 2;
+ f->reset = 1;
+ for (i = 2; i < NSTATES; i++)
+ xfree(f->posns[i]);
+ } else
+ ++(f->curstat);
+ for (i = 0; i < NCHARS; i++)
+ f->gototab[f->curstat][i] = 0;
+ xfree(f->posns[f->curstat]);
+ if ((p = (int *) calloc(setcnt+1, sizeof(int))) == NULL)
+ overflo("out of space in cgoto");
+
+ f->posns[f->curstat] = p;
+ f->gototab[s][c] = f->curstat;
+ for (i = 0; i <= setcnt; i++)
+ p[i] = tmpset[i];
+ if (setvec[f->accept])
+ f->out[f->curstat] = 1;
+ else
+ f->out[f->curstat] = 0;
+ return f->curstat;
+}
+
+
+void freefa(fa *f) /* free a finite automaton */
+{
+ int i;
+
+ if (f == NULL)
+ return;
+ for (i = 0; i <= f->curstat; i++)
+ xfree(f->posns[i]);
+ for (i = 0; i <= f->accept; i++) {
+ xfree(f->re[i].lfollow);
+ if (f->re[i].ltype == CCL || f->re[i].ltype == NCCL)
+ xfree((f->re[i].lval.np));
+ }
+ xfree(f->restr);
+ xfree(f);
+}
diff --git a/awk/lex.c b/awk/lex.c
@@ -0,0 +1,597 @@
+/* $OpenBSD: lex.c,v 1.12 2011/09/28 19:27:18 millert Exp $ */
+/****************************************************************
+Copyright (C) Lucent Technologies 1997
+All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and
+its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that the copyright notice and this
+permission notice and warranty disclaimer appear in supporting
+documentation, and that the name Lucent Technologies or any of
+its entities not be used in advertising or publicity pertaining
+to distribution of the software without specific, written prior
+permission.
+
+LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
+SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+****************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "awk.h"
+#include "ytab.h"
+
+extern YYSTYPE yylval;
+extern int infunc;
+
+int lineno = 1;
+int bracecnt = 0;
+int brackcnt = 0;
+int parencnt = 0;
+
+typedef struct Keyword {
+ const char *word;
+ int sub;
+ int type;
+} Keyword;
+
+Keyword keywords[] ={ /* keep sorted: binary searched */
+ { "BEGIN", XBEGIN, XBEGIN },
+ { "END", XEND, XEND },
+ { "NF", VARNF, VARNF },
+ { "and", FAND, BLTIN },
+ { "atan2", FATAN, BLTIN },
+ { "break", BREAK, BREAK },
+ { "close", CLOSE, CLOSE },
+ { "compl", FCOMPL, BLTIN },
+ { "continue", CONTINUE, CONTINUE },
+ { "cos", FCOS, BLTIN },
+ { "delete", DELETE, DELETE },
+ { "do", DO, DO },
+ { "else", ELSE, ELSE },
+ { "exit", EXIT, EXIT },
+ { "exp", FEXP, BLTIN },
+ { "fflush", FFLUSH, BLTIN },
+ { "for", FOR, FOR },
+ { "func", FUNC, FUNC },
+ { "function", FUNC, FUNC },
+ { "getline", GETLINE, GETLINE },
+ { "gsub", GSUB, GSUB },
+ { "if", IF, IF },
+ { "in", IN, IN },
+ { "index", INDEX, INDEX },
+ { "int", FINT, BLTIN },
+ { "length", FLENGTH, BLTIN },
+ { "log", FLOG, BLTIN },
+ { "lshift", FLSHIFT, BLTIN },
+ { "match", MATCHFCN, MATCHFCN },
+ { "next", NEXT, NEXT },
+ { "nextfile", NEXTFILE, NEXTFILE },
+ { "or", FFOR, BLTIN },
+ { "print", PRINT, PRINT },
+ { "printf", PRINTF, PRINTF },
+ { "rand", FRAND, BLTIN },
+ { "return", RETURN, RETURN },
+ { "rshift", FRSHIFT, BLTIN },
+ { "sin", FSIN, BLTIN },
+ { "split", SPLIT, SPLIT },
+ { "sprintf", SPRINTF, SPRINTF },
+ { "sqrt", FSQRT, BLTIN },
+ { "srand", FSRAND, BLTIN },
+ { "sub", SUB, SUB },
+ { "substr", SUBSTR, SUBSTR },
+ { "system", FSYSTEM, BLTIN },
+ { "tolower", FTOLOWER, BLTIN },
+ { "toupper", FTOUPPER, BLTIN },
+ { "while", WHILE, WHILE },
+ { "xor", FXOR, BLTIN },
+};
+
+#define RET(x) { if(dbg)printf("lex %s\n", tokname(x)); return(x); }
+
+int peek(void);
+int gettok(char **, int *);
+int binsearch(char *, Keyword *, int);
+
+int peek(void)
+{
+ int c = input();
+ unput(c);
+ return c;
+}
+
+int gettok(char **pbuf, int *psz) /* get next input token */
+{
+ int c, retc;
+ char *buf = *pbuf;
+ int sz = *psz;
+ char *bp = buf;
+
+ c = input();
+ if (c == 0)
+ return 0;
+ buf[0] = c;
+ buf[1] = 0;
+ if (!isalnum(c) && c != '.' && c != '_')
+ return c;
+
+ *bp++ = c;
+ if (isalpha(c) || c == '_') { /* it's a varname */
+ for ( ; (c = input()) != 0; ) {
+ if (bp-buf >= sz)
+ if (!adjbuf(&buf, &sz, bp-buf+2, 100, &bp, "gettok"))
+ FATAL( "out of space for name %.10s...", buf );
+ if (isalnum(c) || c == '_')
+ *bp++ = c;
+ else {
+ *bp = 0;
+ unput(c);
+ break;
+ }
+ }
+ *bp = 0;
+ retc = 'a'; /* alphanumeric */
+ } else { /* maybe it's a number, but could be . */
+ char *rem;
+ /* read input until can't be a number */
+ for ( ; (c = input()) != 0; ) {
+ if (bp-buf >= sz)
+ if (!adjbuf(&buf, &sz, bp-buf+2, 100, &bp, "gettok"))
+ FATAL( "out of space for number %.10s...", buf );
+ if (isdigit(c) || c == 'e' || c == 'E'
+ || c == '.' || c == '+' || c == '-')
+ *bp++ = c;
+ else {
+ unput(c);
+ break;
+ }
+ }
+ *bp = 0;
+ strtod(buf, &rem); /* parse the number */
+ if (rem == buf) { /* it wasn't a valid number at all */
+ buf[1] = 0; /* return one character as token */
+ retc = buf[0]; /* character is its own type */
+ unputstr(rem+1); /* put rest back for later */
+ } else { /* some prefix was a number */
+ unputstr(rem); /* put rest back for later */
+ rem[0] = 0; /* truncate buf after number part */
+ retc = '0'; /* type is number */
+ }
+ }
+ *pbuf = buf;
+ *psz = sz;
+ return retc;
+}
+
+int word(char *);
+int string(void);
+int regexpr(void);
+int sc = 0; /* 1 => return a } right now */
+int reg = 0; /* 1 => return a REGEXPR now */
+
+int yylex(void)
+{
+ int c;
+ static char *buf = 0;
+ static int bufsize = 5; /* BUG: setting this small causes core dump! */
+
+ if (buf == 0 && (buf = (char *) malloc(bufsize)) == NULL)
+ FATAL( "out of space in yylex" );
+ if (sc) {
+ sc = 0;
+ RET('}');
+ }
+ if (reg) {
+ reg = 0;
+ return regexpr();
+ }
+ for (;;) {
+ c = gettok(&buf, &bufsize);
+ if (c == 0)
+ return 0;
+ if (isalpha(c) || c == '_')
+ return word(buf);
+ if (isdigit(c)) {
+ yylval.cp = setsymtab(buf, tostring(buf), atof(buf), CON|NUM, symtab);
+ /* should this also have STR set? */
+ RET(NUMBER);
+ }
+
+ yylval.i = c;
+ switch (c) {
+ case '\n': /* {EOL} */
+ RET(NL);
+ case '\r': /* assume \n is coming */
+ case ' ': /* {WS}+ */
+ case '\t':
+ break;
+ case '#': /* #.* strip comments */
+ while ((c = input()) != '\n' && c != 0)
+ ;
+ unput(c);
+ break;
+ case ';':
+ RET(';');
+ case '\\':
+ if (peek() == '\n') {
+ input();
+ } else if (peek() == '\r') {
+ input(); input(); /* \n */
+ lineno++;
+ } else {
+ RET(c);
+ }
+ break;
+ case '&':
+ if (peek() == '&') {
+ input(); RET(AND);
+ } else
+ RET('&');
+ case '|':
+ if (peek() == '|') {
+ input(); RET(BOR);
+ } else
+ RET('|');
+ case '!':
+ if (peek() == '=') {
+ input(); yylval.i = NE; RET(NE);
+ } else if (peek() == '~') {
+ input(); yylval.i = NOTMATCH; RET(MATCHOP);
+ } else
+ RET(NOT);
+ case '~':
+ yylval.i = MATCH;
+ RET(MATCHOP);
+ case '<':
+ if (peek() == '=') {
+ input(); yylval.i = LE; RET(LE);
+ } else {
+ yylval.i = LT; RET(LT);
+ }
+ case '=':
+ if (peek() == '=') {
+ input(); yylval.i = EQ; RET(EQ);
+ } else {
+ yylval.i = ASSIGN; RET(ASGNOP);
+ }
+ case '>':
+ if (peek() == '=') {
+ input(); yylval.i = GE; RET(GE);
+ } else if (peek() == '>') {
+ input(); yylval.i = APPEND; RET(APPEND);
+ } else {
+ yylval.i = GT; RET(GT);
+ }
+ case '+':
+ if (peek() == '+') {
+ input(); yylval.i = INCR; RET(INCR);
+ } else if (peek() == '=') {
+ input(); yylval.i = ADDEQ; RET(ASGNOP);
+ } else
+ RET('+');
+ case '-':
+ if (peek() == '-') {
+ input(); yylval.i = DECR; RET(DECR);
+ } else if (peek() == '=') {
+ input(); yylval.i = SUBEQ; RET(ASGNOP);
+ } else
+ RET('-');
+ case '*':
+ if (peek() == '=') { /* *= */
+ input(); yylval.i = MULTEQ; RET(ASGNOP);
+ } else if (peek() == '*') { /* ** or **= */
+ input(); /* eat 2nd * */
+ if (peek() == '=') {
+ input(); yylval.i = POWEQ; RET(ASGNOP);
+ } else {
+ RET(POWER);
+ }
+ } else
+ RET('*');
+ case '/':
+ RET('/');
+ case '%':
+ if (peek() == '=') {
+ input(); yylval.i = MODEQ; RET(ASGNOP);
+ } else
+ RET('%');
+ case '^':
+ if (peek() == '=') {
+ input(); yylval.i = POWEQ; RET(ASGNOP);
+ } else
+ RET(POWER);
+
+ case '$':
+ /* BUG: awkward, if not wrong */
+ c = gettok(&buf, &bufsize);
+ if (isalpha(c)) {
+ if (strcmp(buf, "NF") == 0) { /* very special */
+ unputstr("(NF)");
+ RET(INDIRECT);
+ }
+ c = peek();
+ if (c == '(' || c == '[' || (infunc && isarg(buf) >= 0)) {
+ unputstr(buf);
+ RET(INDIRECT);
+ }
+ yylval.cp = setsymtab(buf, "", 0.0, STR|NUM, symtab);
+ RET(IVAR);
+ } else if (c == 0) { /* */
+ SYNTAX( "unexpected end of input after $" );
+ RET(';');
+ } else {
+ unputstr(buf);
+ RET(INDIRECT);
+ }
+
+ case '}':
+ if (--bracecnt < 0)
+ SYNTAX( "extra }" );
+ sc = 1;
+ RET(';');
+ case ']':
+ if (--brackcnt < 0)
+ SYNTAX( "extra ]" );
+ RET(']');
+ case ')':
+ if (--parencnt < 0)
+ SYNTAX( "extra )" );
+ RET(')');
+ case '{':
+ bracecnt++;
+ RET('{');
+ case '[':
+ brackcnt++;
+ RET('[');
+ case '(':
+ parencnt++;
+ RET('(');
+
+ case '"':
+ return string(); /* BUG: should be like tran.c ? */
+
+ default:
+ RET(c);
+ }
+ }
+}
+
+int string(void)
+{
+ int c, n;
+ char *s, *bp;
+ static char *buf = 0;
+ static int bufsz = 500;
+
+ if (buf == 0 && (buf = (char *) malloc(bufsz)) == NULL)
+ FATAL("out of space for strings");
+ for (bp = buf; (c = input()) != '"'; ) {
+ if (!adjbuf(&buf, &bufsz, bp-buf+2, 500, &bp, "string"))
+ FATAL("out of space for string %.10s...", buf);
+ switch (c) {
+ case '\n':
+ case '\r':
+ case 0:
+ SYNTAX( "non-terminated string %.10s...", buf );
+ lineno++;
+ if (c == 0) /* hopeless */
+ FATAL( "giving up" );
+ break;
+ case '\\':
+ c = input();
+ switch (c) {
+ case '"': *bp++ = '"'; break;
+ case 'n': *bp++ = '\n'; break;
+ case 't': *bp++ = '\t'; break;
+ case 'f': *bp++ = '\f'; break;
+ case 'r': *bp++ = '\r'; break;
+ case 'b': *bp++ = '\b'; break;
+ case 'v': *bp++ = '\v'; break;
+ case 'a': *bp++ = '\007'; break;
+ case '\\': *bp++ = '\\'; break;
+
+ case '0': case '1': case '2': /* octal: \d \dd \ddd */
+ case '3': case '4': case '5': case '6': case '7':
+ n = c - '0';
+ if ((c = peek()) >= '0' && c < '8') {
+ n = 8 * n + input() - '0';
+ if ((c = peek()) >= '0' && c < '8')
+ n = 8 * n + input() - '0';
+ }
+ *bp++ = n;
+ break;
+
+ case 'x': /* hex \x0-9a-fA-F + */
+ { char xbuf[100], *px;
+ for (px = xbuf; (c = input()) != 0 && px-xbuf < 100-2; ) {
+ if (isdigit(c)
+ || (c >= 'a' && c <= 'f')
+ || (c >= 'A' && c <= 'F'))
+ *px++ = c;
+ else
+ break;
+ }
+ *px = 0;
+ unput(c);
+ sscanf(xbuf, "%x", (unsigned int *) &n);
+ *bp++ = n;
+ break;
+ }
+
+ default:
+ *bp++ = c;
+ break;
+ }
+ break;
+ default:
+ *bp++ = c;
+ break;
+ }
+ }
+ *bp = 0;
+ s = tostring(buf);
+ *bp++ = ' '; *bp++ = 0;
+ yylval.cp = setsymtab(buf, s, 0.0, CON|STR|DONTFREE, symtab);
+ RET(STRING);
+}
+
+
+int binsearch(char *w, Keyword *kp, int n)
+{
+ int cond, low, mid, high;
+
+ low = 0;
+ high = n - 1;
+ while (low <= high) {
+ mid = (low + high) / 2;
+ if ((cond = strcmp(w, kp[mid].word)) < 0)
+ high = mid - 1;
+ else if (cond > 0)
+ low = mid + 1;
+ else
+ return mid;
+ }
+ return -1;
+}
+
+int word(char *w)
+{
+ Keyword *kp;
+ int c, n;
+
+ n = binsearch(w, keywords, sizeof(keywords)/sizeof(keywords[0]));
+/* BUG: this ought to be inside the if; in theory could fault (daniel barrett) */
+ kp = keywords + n;
+ if (n != -1) { /* found in table */
+ yylval.i = kp->sub;
+ switch (kp->type) { /* special handling */
+ case BLTIN:
+ if (kp->sub == FSYSTEM && safe)
+ SYNTAX( "system is unsafe" );
+ RET(kp->type);
+ case FUNC:
+ if (infunc)
+ SYNTAX( "illegal nested function" );
+ RET(kp->type);
+ case RETURN:
+ if (!infunc)
+ SYNTAX( "return not in function" );
+ RET(kp->type);
+ case VARNF:
+ yylval.cp = setsymtab("NF", "", 0.0, NUM, symtab);
+ RET(VARNF);
+ default:
+ RET(kp->type);
+ }
+ }
+ c = peek(); /* look for '(' */
+ if (c != '(' && infunc && (n=isarg(w)) >= 0) {
+ yylval.i = n;
+ RET(ARG);
+ } else {
+ yylval.cp = setsymtab(w, "", 0.0, STR|NUM|DONTFREE, symtab);
+ if (c == '(') {
+ RET(CALL);
+ } else {
+ RET(VAR);
+ }
+ }
+}
+
+void startreg(void) /* next call to yylex will return a regular expression */
+{
+ reg = 1;
+}
+
+int regexpr(void)
+{
+ int c, openclass = 0;
+ static char *buf = 0;
+ static int bufsz = 500;
+ char *bp;
+
+ if (buf == 0 && (buf = (char *) malloc(bufsz)) == NULL)
+ FATAL("out of space for rex expr");
+ bp = buf;
+ for ( ; ((c = input()) != '/' || openclass == 1) && c != 0; ) {
+ if (!adjbuf(&buf, &bufsz, bp-buf+3, 500, &bp, "regexpr"))
+ FATAL("out of space for reg expr %.10s...", buf);
+ if (c == '\n') {
+ SYNTAX( "newline in regular expression %.10s...", buf );
+ unput('\n');
+ break;
+ } else if (c == '\\') {
+ *bp++ = '\\';
+ *bp++ = input();
+ } else {
+ if (c == '[')
+ openclass = 1;
+ else if (c == ']')
+ openclass = 0;
+ *bp++ = c;
+ }
+ }
+ *bp = 0;
+ if (c == 0)
+ SYNTAX("non-terminated regular expression %.10s...", buf);
+ yylval.s = tostring(buf);
+ unput('/');
+ RET(REGEXPR);
+}
+
+/* low-level lexical stuff, sort of inherited from lex */
+
+char ebuf[300];
+char *ep = ebuf;
+char yysbuf[100]; /* pushback buffer */
+char *yysptr = yysbuf;
+FILE *yyin = 0;
+
+int input(void) /* get next lexical input character */
+{
+ int c;
+ extern char *lexprog;
+
+ if (yysptr > yysbuf)
+ c = (uschar)*--yysptr;
+ else if (lexprog != NULL) { /* awk '...' */
+ if ((c = (uschar)*lexprog) != 0)
+ lexprog++;
+ } else /* awk -f ... */
+ c = pgetc();
+ if (c == '\n')
+ lineno++;
+ else if (c == EOF)
+ c = 0;
+ if (ep >= ebuf + sizeof ebuf)
+ ep = ebuf;
+ return *ep++ = c;
+}
+
+void unput(int c) /* put lexical character back on input */
+{
+ if (c == '\n')
+ lineno--;
+ if (yysptr >= yysbuf + sizeof(yysbuf))
+ FATAL("pushed back too much: %.20s...", yysbuf);
+ *yysptr++ = c;
+ if (--ep < ebuf)
+ ep = ebuf + sizeof(ebuf) - 1;
+}
+
+void unputstr(const char *s) /* put a string back on input */
+{
+ int i;
+
+ for (i = strlen(s)-1; i >= 0; i--)
+ unput(s[i]);
+}
diff --git a/awk/lib.c b/awk/lib.c
@@ -0,0 +1,739 @@
+/* $OpenBSD: lib.c,v 1.20 2011/09/28 19:27:18 millert Exp $ */
+/****************************************************************
+Copyright (C) Lucent Technologies 1997
+All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and
+its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that the copyright notice and this
+permission notice and warranty disclaimer appear in supporting
+documentation, and that the name Lucent Technologies or any of
+its entities not be used in advertising or publicity pertaining
+to distribution of the software without specific, written prior
+permission.
+
+LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
+SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+****************************************************************/
+
+#define DEBUG
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include "awk.h"
+#include "util.h"
+#include "ytab.h"
+
+FILE *infile = NULL;
+char *file = "";
+char *record;
+int recsize = RECSIZE;
+char *fields;
+int fieldssize = RECSIZE;
+
+Cell **fldtab; /* pointers to Cells */
+char inputFS[100] = " ";
+
+#define MAXFLD 2
+int nfields = MAXFLD; /* last allocated slot for $i */
+
+int donefld; /* 1 = implies rec broken into fields */
+int donerec; /* 1 = record is valid (no flds have changed) */
+
+int lastfld = 0; /* last used field */
+int argno = 1; /* current input argument number */
+extern Awkfloat *ARGC;
+
+static Cell dollar0 = { OCELL, CFLD, NULL, "", 0.0, REC|STR|DONTFREE };
+static Cell dollar1 = { OCELL, CFLD, NULL, "", 0.0, FLD|STR|DONTFREE };
+
+void recinit(unsigned int n)
+{
+ if ( (record = (char *) malloc(n)) == NULL
+ || (fields = (char *) malloc(n+1)) == NULL
+ || (fldtab = (Cell **) calloc(nfields+1, sizeof(Cell *))) == NULL
+ || (fldtab[0] = (Cell *) malloc(sizeof(Cell))) == NULL )
+ FATAL("out of space for $0 and fields");
+ *fldtab[0] = dollar0;
+ fldtab[0]->sval = record;
+ fldtab[0]->nval = tostring("0");
+ makefields(1, nfields);
+}
+
+void makefields(int n1, int n2) /* create $n1..$n2 inclusive */
+{
+ char temp[50];
+ int i;
+
+ for (i = n1; i <= n2; i++) {
+ fldtab[i] = (Cell *) malloc(sizeof (struct Cell));
+ if (fldtab[i] == NULL)
+ FATAL("out of space in makefields %d", i);
+ *fldtab[i] = dollar1;
+ snprintf(temp, sizeof temp, "%d", i);
+ fldtab[i]->nval = tostring(temp);
+ }
+}
+
+void initgetrec(void)
+{
+ int i;
+ char *p;
+
+ for (i = 1; i < *ARGC; i++) {
+ p = getargv(i); /* find 1st real filename */
+ if (p == NULL || *p == '\0') { /* deleted or zapped */
+ argno++;
+ continue;
+ }
+ if (!isclvar(p)) {
+ setsval(lookup("FILENAME", symtab), p);
+ return;
+ }
+ setclvar(p); /* a commandline assignment before filename */
+ argno++;
+ }
+ infile = stdin; /* no filenames, so use stdin */
+}
+
+static int firsttime = 1;
+
+int getrec(char **pbuf, int *pbufsize, int isrecord) /* get next input record */
+{ /* note: cares whether buf == record */
+ int c;
+ char *buf = *pbuf;
+ uschar saveb0;
+ int bufsize = *pbufsize, savebufsize = bufsize;
+
+ if (firsttime) {
+ firsttime = 0;
+ initgetrec();
+ }
+ dprintf( ("RS=<%s>, FS=<%s>, ARGC=%g, FILENAME=%s\n",
+ *RS, *FS, *ARGC, *FILENAME) );
+ if (isrecord) {
+ donefld = 0;
+ donerec = 1;
+ }
+ saveb0 = buf[0];
+ buf[0] = 0;
+ while (argno < *ARGC || infile == stdin) {
+ dprintf( ("argno=%d, file=|%s|\n", argno, file) );
+ if (infile == NULL) { /* have to open a new file */
+ file = getargv(argno);
+ if (file == NULL || *file == '\0') { /* deleted or zapped */
+ argno++;
+ continue;
+ }
+ if (isclvar(file)) { /* a var=value arg */
+ setclvar(file);
+ argno++;
+ continue;
+ }
+ *FILENAME = file;
+ dprintf( ("opening file %s\n", file) );
+ if (*file == '-' && *(file+1) == '\0')
+ infile = stdin;
+ else if ((infile = fopen(file, "r")) == NULL)
+ FATAL("can't open file %s", file);
+ setfval(fnrloc, 0.0);
+ }
+ c = readrec(&buf, &bufsize, infile);
+ if (c != 0 || buf[0] != '\0') { /* normal record */
+ if (isrecord) {
+ if (freeable(fldtab[0]))
+ xfree(fldtab[0]->sval);
+ fldtab[0]->sval = buf; /* buf == record */
+ fldtab[0]->tval = REC | STR | DONTFREE;
+ if (is_number(fldtab[0]->sval)) {
+ fldtab[0]->fval = atof(fldtab[0]->sval);
+ fldtab[0]->tval |= NUM;
+ }
+ }
+ setfval(nrloc, nrloc->fval+1);
+ setfval(fnrloc, fnrloc->fval+1);
+ *pbuf = buf;
+ *pbufsize = bufsize;
+ return 1;
+ }
+ /* EOF arrived on this file; set up next */
+ if (infile != stdin)
+ fclose(infile);
+ infile = NULL;
+ argno++;
+ }
+ buf[0] = saveb0;
+ *pbuf = buf;
+ *pbufsize = savebufsize;
+ return 0; /* true end of file */
+}
+
+void nextfile(void)
+{
+ if (infile != NULL && infile != stdin)
+ fclose(infile);
+ infile = NULL;
+ argno++;
+}
+
+int readrec(char **pbuf, int *pbufsize, FILE *inf) /* read one record into buf */
+{
+ int sep, c;
+ char *rr, *buf = *pbuf;
+ int bufsize = *pbufsize;
+
+ if (strlen(*FS) >= sizeof(inputFS))
+ FATAL("field separator %.10s... is too long", *FS);
+ /*fflush(stdout); avoids some buffering problem but makes it 25% slower*/
+ strlcpy(inputFS, *FS, sizeof inputFS); /* for subsequent field splitting */
+ if ((sep = **RS) == 0) {
+ sep = '\n';
+ while ((c=getc(inf)) == '\n' && c != EOF) /* skip leading \n's */
+ ;
+ if (c != EOF)
+ ungetc(c, inf);
+ }
+ for (rr = buf; ; ) {
+ for (; (c=getc(inf)) != sep && c != EOF; ) {
+ if (rr-buf+1 > bufsize)
+ if (!adjbuf(&buf, &bufsize, 1+rr-buf, recsize, &rr, "readrec 1"))
+ FATAL("input record `%.30s...' too long", buf);
+ *rr++ = c;
+ }
+ if (**RS == sep || c == EOF)
+ break;
+ if ((c = getc(inf)) == '\n' || c == EOF) /* 2 in a row */
+ break;
+ if (!adjbuf(&buf, &bufsize, 2+rr-buf, recsize, &rr, "readrec 2"))
+ FATAL("input record `%.30s...' too long", buf);
+ *rr++ = '\n';
+ *rr++ = c;
+ }
+ if (!adjbuf(&buf, &bufsize, 1+rr-buf, recsize, &rr, "readrec 3"))
+ FATAL("input record `%.30s...' too long", buf);
+ *rr = 0;
+ dprintf( ("readrec saw <%s>, returns %d\n", buf, c == EOF && rr == buf ? 0 : 1) );
+ *pbuf = buf;
+ *pbufsize = bufsize;
+ return c == EOF && rr == buf ? 0 : 1;
+}
+
+char *getargv(int n) /* get ARGV[n] */
+{
+ Cell *x;
+ char *s, temp[50];
+ extern Array *ARGVtab;
+
+ snprintf(temp, sizeof temp, "%d", n);
+ if (lookup(temp, ARGVtab) == NULL)
+ return NULL;
+ x = setsymtab(temp, "", 0.0, STR, ARGVtab);
+ s = getsval(x);
+ dprintf( ("getargv(%d) returns |%s|\n", n, s) );
+ return s;
+}
+
+void setclvar(char *s) /* set var=value from s */
+{
+ char *p;
+ Cell *q;
+
+ for (p=s; *p != '='; p++)
+ ;
+ *p++ = 0;
+ p = qstring(p, '\0');
+ q = setsymtab(s, p, 0.0, STR, symtab);
+ setsval(q, p);
+ if (is_number(q->sval)) {
+ q->fval = atof(q->sval);
+ q->tval |= NUM;
+ }
+ dprintf( ("command line set %s to |%s|\n", s, p) );
+}
+
+
+void fldbld(void) /* create fields from current record */
+{
+ /* this relies on having fields[] the same length as $0 */
+ /* the fields are all stored in this one array with \0's */
+ /* possibly with a final trailing \0 not associated with any field */
+ char *r, *fr, sep;
+ Cell *p;
+ int i, j, n;
+
+ if (donefld)
+ return;
+ if (!isstr(fldtab[0]))
+ getsval(fldtab[0]);
+ r = fldtab[0]->sval;
+ n = strlen(r);
+ if (n > fieldssize) {
+ xfree(fields);
+ if ((fields = (char *) malloc(n+2)) == NULL) /* possibly 2 final \0s */
+ FATAL("out of space for fields in fldbld %d", n);
+ fieldssize = n;
+ }
+ fr = fields;
+ i = 0; /* number of fields accumulated here */
+ strlcpy(inputFS, *FS, sizeof(inputFS));
+ if (strlen(inputFS) > 1) { /* it's a regular expression */
+ i = refldbld(r, inputFS);
+ } else if ((sep = *inputFS) == ' ') { /* default whitespace */
+ for (i = 0; ; ) {
+ while (*r == ' ' || *r == '\t' || *r == '\n')
+ r++;
+ if (*r == 0)
+ break;
+ i++;
+ if (i > nfields)
+ growfldtab(i);
+ if (freeable(fldtab[i]))
+ xfree(fldtab[i]->sval);
+ fldtab[i]->sval = fr;
+ fldtab[i]->tval = FLD | STR | DONTFREE;
+ do
+ *fr++ = *r++;
+ while (*r != ' ' && *r != '\t' && *r != '\n' && *r != '\0');
+ *fr++ = 0;
+ }
+ *fr = 0;
+ } else if ((sep = *inputFS) == 0) { /* new: FS="" => 1 char/field */
+ for (i = 0; *r != 0; r++) {
+ char buf[2];
+ i++;
+ if (i > nfields)
+ growfldtab(i);
+ if (freeable(fldtab[i]))
+ xfree(fldtab[i]->sval);
+ buf[0] = *r;
+ buf[1] = 0;
+ fldtab[i]->sval = tostring(buf);
+ fldtab[i]->tval = FLD | STR;
+ }
+ *fr = 0;
+ } else if (*r != 0) { /* if 0, it's a null field */
+ /* subtlecase : if length(FS) == 1 && length(RS > 0)
+ * \n is NOT a field separator (cf awk book 61,84).
+ * this variable is tested in the inner while loop.
+ */
+ int rtest = '\n'; /* normal case */
+ if (strlen(*RS) > 0)
+ rtest = '\0';
+ for (;;) {
+ i++;
+ if (i > nfields)
+ growfldtab(i);
+ if (freeable(fldtab[i]))
+ xfree(fldtab[i]->sval);
+ fldtab[i]->sval = fr;
+ fldtab[i]->tval = FLD | STR | DONTFREE;
+ while (*r != sep && *r != rtest && *r != '\0') /* \n is always a separator */
+ *fr++ = *r++;
+ *fr++ = 0;
+ if (*r++ == 0)
+ break;
+ }
+ *fr = 0;
+ }
+ if (i > nfields)
+ FATAL("record `%.30s...' has too many fields; can't happen", r);
+ cleanfld(i+1, lastfld); /* clean out junk from previous record */
+ lastfld = i;
+ donefld = 1;
+ for (j = 1; j <= lastfld; j++) {
+ p = fldtab[j];
+ if(is_number(p->sval)) {
+ p->fval = atof(p->sval);
+ p->tval |= NUM;
+ }
+ }
+ setfval(nfloc, (Awkfloat) lastfld);
+ if (dbg) {
+ for (j = 0; j <= lastfld; j++) {
+ p = fldtab[j];
+ printf("field %d (%s): |%s|\n", j, p->nval, p->sval);
+ }
+ }
+}
+
+void cleanfld(int n1, int n2) /* clean out fields n1 .. n2 inclusive */
+{ /* nvals remain intact */
+ Cell *p;
+ int i;
+
+ for (i = n1; i <= n2; i++) {
+ p = fldtab[i];
+ if (freeable(p))
+ xfree(p->sval);
+ p->sval = "";
+ p->tval = FLD | STR | DONTFREE;
+ }
+}
+
+void newfld(int n) /* add field n after end of existing lastfld */
+{
+ if (n > nfields)
+ growfldtab(n);
+ cleanfld(lastfld+1, n);
+ lastfld = n;
+ setfval(nfloc, (Awkfloat) n);
+}
+
+Cell *fieldadr(int n) /* get nth field */
+{
+ if (n < 0)
+ FATAL("trying to access out of range field %d", n);
+ if (n > nfields) /* fields after NF are empty */
+ growfldtab(n); /* but does not increase NF */
+ return(fldtab[n]);
+}
+
+void growfldtab(int n) /* make new fields up to at least $n */
+{
+ int nf = 2 * nfields;
+ size_t s;
+
+ if (n > nf)
+ nf = n;
+ s = (nf+1) * (sizeof (struct Cell *)); /* freebsd: how much do we need? */
+ if (s / sizeof(struct Cell *) - 1 == nf) /* didn't overflow */
+ fldtab = (Cell **) realloc(fldtab, s);
+ else /* overflow sizeof int */
+ xfree(fldtab); /* make it null */
+ if (fldtab == NULL)
+ FATAL("out of space creating %d fields", nf);
+ makefields(nfields+1, nf);
+ nfields = nf;
+}
+
+int refldbld(const char *rec, const char *fs) /* build fields from reg expr in FS */
+{
+ /* this relies on having fields[] the same length as $0 */
+ /* the fields are all stored in this one array with \0's */
+ char *fr;
+ int i, tempstat, n;
+ fa *pfa;
+
+ n = strlen(rec);
+ if (n > fieldssize) {
+ xfree(fields);
+ if ((fields = (char *) malloc(n+1)) == NULL)
+ FATAL("out of space for fields in refldbld %d", n);
+ fieldssize = n;
+ }
+ fr = fields;
+ *fr = '\0';
+ if (*rec == '\0')
+ return 0;
+ pfa = makedfa(fs, 1);
+ dprintf( ("into refldbld, rec = <%s>, pat = <%s>\n", rec, fs) );
+ tempstat = pfa->initstat;
+ for (i = 1; ; i++) {
+ if (i > nfields)
+ growfldtab(i);
+ if (freeable(fldtab[i]))
+ xfree(fldtab[i]->sval);
+ fldtab[i]->tval = FLD | STR | DONTFREE;
+ fldtab[i]->sval = fr;
+ dprintf( ("refldbld: i=%d\n", i) );
+ if (nematch(pfa, rec)) {
+ pfa->initstat = 2; /* horrible coupling to b.c */
+ dprintf( ("match %s (%d chars)\n", patbeg, patlen) );
+ strncpy(fr, rec, patbeg-rec);
+ fr += patbeg - rec + 1;
+ *(fr-1) = '\0';
+ rec = patbeg + patlen;
+ } else {
+ dprintf( ("no match %s\n", rec) );
+ strlcpy(fr, rec, fields + fieldssize - fr);
+ pfa->initstat = tempstat;
+ break;
+ }
+ }
+ return i;
+}
+
+void recbld(void) /* create $0 from $1..$NF if necessary */
+{
+ int i;
+ char *r, *p;
+
+ if (donerec == 1)
+ return;
+ r = record;
+ for (i = 1; i <= *NF; i++) {
+ p = getsval(fldtab[i]);
+ if (!adjbuf(&record, &recsize, 1+strlen(p)+r-record, recsize, &r, "recbld 1"))
+ FATAL("created $0 `%.30s...' too long", record);
+ while ((*r = *p++) != 0)
+ r++;
+ if (i < *NF) {
+ if (!adjbuf(&record, &recsize, 2+strlen(*OFS)+r-record, recsize, &r, "recbld 2"))
+ FATAL("created $0 `%.30s...' too long", record);
+ for (p = *OFS; (*r = *p++) != 0; )
+ r++;
+ }
+ }
+ if (!adjbuf(&record, &recsize, 2+r-record, recsize, &r, "recbld 3"))
+ FATAL("built giant record `%.30s...'", record);
+ *r = '\0';
+ dprintf( ("in recbld inputFS=%s, fldtab[0]=%p\n", inputFS, (void*)fldtab[0]) );
+
+ if (freeable(fldtab[0]))
+ xfree(fldtab[0]->sval);
+ fldtab[0]->tval = REC | STR | DONTFREE;
+ fldtab[0]->sval = record;
+
+ dprintf( ("in recbld inputFS=%s, fldtab[0]=%p\n", inputFS, (void*)fldtab[0]) );
+ dprintf( ("recbld = |%s|\n", record) );
+ donerec = 1;
+}
+
+int errorflag = 0;
+
+void yyerror(const char *s)
+{
+ SYNTAX("%s", s);
+}
+
+void SYNTAX(const char *fmt, ...)
+{
+ extern char *cmdname, *curfname;
+ static int been_here = 0;
+ va_list varg;
+
+ if (been_here++ > 2)
+ return;
+ fprintf(stderr, "%s: ", cmdname);
+ va_start(varg, fmt);
+ vfprintf(stderr, fmt, varg);
+ va_end(varg);
+ fprintf(stderr, " at source line %d", lineno);
+ if (curfname != NULL)
+ fprintf(stderr, " in function %s", curfname);
+ if (compile_time == 1 && cursource() != NULL)
+ fprintf(stderr, " source file %s", cursource());
+ fprintf(stderr, "\n");
+ errorflag = 2;
+ eprint();
+}
+
+void fpecatch(int sig)
+{
+ extern Node *curnode;
+ char buf[1024];
+
+ snprintf(buf, sizeof buf, "floating point exception\n");
+ write(STDERR_FILENO, buf, strlen(buf));
+
+ if (compile_time != 2 && NR && *NR > 0) {
+ snprintf(buf, sizeof buf, " input record number %d", (int) (*FNR));
+ write(STDERR_FILENO, buf, strlen(buf));
+
+ if (strcmp(*FILENAME, "-") != 0) {
+ snprintf(buf, sizeof buf, ", file %s", *FILENAME);
+ write(STDERR_FILENO, buf, strlen(buf));
+ }
+ write(STDERR_FILENO, "\n", 1);
+ }
+ if (compile_time != 2 && curnode) {
+ snprintf(buf, sizeof buf, " source line number %d", curnode->lineno);
+ write(STDERR_FILENO, buf, strlen(buf));
+ } else if (compile_time != 2 && lineno) {
+ snprintf(buf, sizeof buf, " source line number %d", lineno);
+ write(STDERR_FILENO, buf, strlen(buf));
+ }
+ if (compile_time == 1 && cursource() != NULL) {
+ snprintf(buf, sizeof buf, " source file %s", cursource());
+ write(STDERR_FILENO, buf, strlen(buf));
+ }
+ write(STDERR_FILENO, "\n", 1);
+ if (dbg > 1) /* core dump if serious debugging on */
+ abort();
+ _exit(1);
+}
+
+extern int bracecnt, brackcnt, parencnt;
+
+void bracecheck(void)
+{
+ int c;
+ static int beenhere = 0;
+
+ if (beenhere++)
+ return;
+ while ((c = input()) != EOF && c != '\0')
+ bclass(c);
+ bcheck2(bracecnt, '{', '}');
+ bcheck2(brackcnt, '[', ']');
+ bcheck2(parencnt, '(', ')');
+}
+
+void bcheck2(int n, int c1, int c2)
+{
+ if (n == 1)
+ fprintf(stderr, "\tmissing %c\n", c2);
+ else if (n > 1)
+ fprintf(stderr, "\t%d missing %c's\n", n, c2);
+ else if (n == -1)
+ fprintf(stderr, "\textra %c\n", c2);
+ else if (n < -1)
+ fprintf(stderr, "\t%d extra %c's\n", -n, c2);
+}
+
+void FATAL(const char *fmt, ...)
+{
+ extern char *cmdname;
+ va_list varg;
+
+ fflush(stdout);
+ fprintf(stderr, "%s: ", cmdname);
+ va_start(varg, fmt);
+ vfprintf(stderr, fmt, varg);
+ va_end(varg);
+ error();
+ if (dbg > 1) /* core dump if serious debugging on */
+ abort();
+ exit(2);
+}
+
+void WARNING(const char *fmt, ...)
+{
+ extern char *cmdname;
+ va_list varg;
+
+ fflush(stdout);
+ fprintf(stderr, "%s: ", cmdname);
+ va_start(varg, fmt);
+ vfprintf(stderr, fmt, varg);
+ va_end(varg);
+ error();
+}
+
+void error()
+{
+ extern Node *curnode;
+
+ fprintf(stderr, "\n");
+ if (compile_time != 2 && NR && *NR > 0) {
+ fprintf(stderr, " input record number %d", (int) (*FNR));
+ if (strcmp(*FILENAME, "-") != 0)
+ fprintf(stderr, ", file %s", *FILENAME);
+ fprintf(stderr, "\n");
+ }
+ if (compile_time != 2 && curnode)
+ fprintf(stderr, " source line number %d", curnode->lineno);
+ else if (compile_time != 2 && lineno)
+ fprintf(stderr, " source line number %d", lineno);
+ if (compile_time == 1 && cursource() != NULL)
+ fprintf(stderr, " source file %s", cursource());
+ fprintf(stderr, "\n");
+ eprint();
+}
+
+void eprint(void) /* try to print context around error */
+{
+ char *p, *q;
+ int c;
+ static int been_here = 0;
+ extern char ebuf[], *ep;
+
+ if (compile_time == 2 || compile_time == 0 || been_here++ > 0)
+ return;
+ p = ep - 1;
+ if (p > ebuf && *p == '\n')
+ p--;
+ for ( ; p > ebuf && *p != '\n' && *p != '\0'; p--)
+ ;
+ while (*p == '\n')
+ p++;
+ fprintf(stderr, " context is\n\t");
+ for (q=ep-1; q>=p && *q!=' ' && *q!='\t' && *q!='\n'; q--)
+ ;
+ for ( ; p < q; p++)
+ if (*p)
+ putc(*p, stderr);
+ fprintf(stderr, " >>> ");
+ for ( ; p < ep; p++)
+ if (*p)
+ putc(*p, stderr);
+ fprintf(stderr, " <<< ");
+ if (*ep)
+ while ((c = input()) != '\n' && c != '\0' && c != EOF) {
+ putc(c, stderr);
+ bclass(c);
+ }
+ putc('\n', stderr);
+ ep = ebuf;
+}
+
+void bclass(int c)
+{
+ switch (c) {
+ case '{': bracecnt++; break;
+ case '}': bracecnt--; break;
+ case '[': brackcnt++; break;
+ case ']': brackcnt--; break;
+ case '(': parencnt++; break;
+ case ')': parencnt--; break;
+ }
+}
+
+double errcheck(double x, const char *s)
+{
+
+ if (errno == EDOM) {
+ errno = 0;
+ WARNING("%s argument out of domain", s);
+ x = 1;
+ } else if (errno == ERANGE) {
+ errno = 0;
+ WARNING("%s result out of range", s);
+ x = 1;
+ }
+ return x;
+}
+
+int isclvar(const char *s) /* is s of form var=something ? */
+{
+ const char *os = s;
+
+ if (!isalpha((uschar) *s) && *s != '_')
+ return 0;
+ for ( ; *s; s++)
+ if (!(isalnum((uschar) *s) || *s == '_'))
+ break;
+ return *s == '=' && s > os && *(s+1) != '=';
+}
+
+/* strtod is supposed to be a proper test of what's a valid number */
+/* appears to be broken in gcc on linux: thinks 0x123 is a valid FP number */
+/* wrong: violates 4.10.1.4 of ansi C standard */
+
+#include <math.h>
+int is_number(const char *s)
+{
+ double r;
+ char *ep;
+ errno = 0;
+ r = strtod(s, &ep);
+ if (ep == s || r == HUGE_VAL || errno == ERANGE)
+ return 0;
+ while (*ep == ' ' || *ep == '\t' || *ep == '\n')
+ ep++;
+ if (*ep == '\0')
+ return 1;
+ else
+ return 0;
+}
diff --git a/awk/main.c b/awk/main.c
@@ -0,0 +1,212 @@
+/* $OpenBSD: main.c,v 1.17 2011/09/28 19:27:18 millert Exp $ */
+/****************************************************************
+Copyright (C) Lucent Technologies 1997
+All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and
+its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that the copyright notice and this
+permission notice and warranty disclaimer appear in supporting
+documentation, and that the name Lucent Technologies or any of
+its entities not be used in advertising or publicity pertaining
+to distribution of the software without specific, written prior
+permission.
+
+LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
+SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+****************************************************************/
+
+const char *version = "version 20110810";
+
+#define DEBUG
+#include <stdio.h>
+#include <ctype.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include "awk.h"
+#include "ytab.h"
+
+extern char **environ;
+extern int nfields;
+extern char *__progname;
+
+int dbg = 0;
+Awkfloat srand_seed = 1;
+char *cmdname; /* gets argv[0] for error messages */
+extern FILE *yyin; /* lex input file */
+char *lexprog; /* points to program argument if it exists */
+extern int errorflag; /* non-zero if any syntax errors; set by yyerror */
+int compile_time = 2; /* for error printing: */
+ /* 2 = cmdline, 1 = compile, 0 = running */
+
+#define MAX_PFILE 20 /* max number of -f's */
+
+char *pfile[MAX_PFILE]; /* program filenames from -f's */
+int npfile = 0; /* number of filenames */
+int curpfile = 0; /* current filename */
+
+int safe = 0; /* 1 => "safe" mode */
+
+int main(int argc, char *argv[])
+{
+ const char *fs = NULL;
+
+ setlocale(LC_ALL, "");
+ setlocale(LC_NUMERIC, "C"); /* for parsing cmdline & prog */
+ cmdname = __progname;
+ if (argc == 1) {
+ fprintf(stderr, "usage: %s [-safe] [-V] [-d[n]] [-F fs] "
+ "[-v var=value] [prog | -f progfile]\n\tfile ...\n",
+ cmdname);
+ exit(1);
+ }
+ signal(SIGFPE, fpecatch);
+
+ yyin = NULL;
+ symtab = makesymtab(NSYMTAB);
+ while (argc > 1 && argv[1][0] == '-' && argv[1][1] != '\0') {
+ if (strcmp(argv[1], "--") == 0) { /* explicit end of args */
+ argc--;
+ argv++;
+ break;
+ }
+ switch (argv[1][1]) {
+ case 's':
+ if (strcmp(argv[1], "-safe") == 0)
+ safe = 1;
+ break;
+ case 'f': /* next argument is program filename */
+ if (argv[1][2] != 0) { /* arg is -fsomething */
+ if (npfile >= MAX_PFILE - 1)
+ FATAL("too many -f options");
+ pfile[npfile++] = &argv[1][2];
+ } else { /* arg is -f something */
+ argc--; argv++;
+ if (argc <= 1)
+ FATAL("no program filename");
+ if (npfile >= MAX_PFILE - 1)
+ FATAL("too many -f options");
+ pfile[npfile++] = argv[1];
+ }
+ break;
+ case 'F': /* set field separator */
+ if (argv[1][2] != 0) { /* arg is -Fsomething */
+ if (argv[1][2] == 't' && argv[1][3] == 0) /* wart: t=>\t */
+ fs = "\t";
+ else if (argv[1][2] != 0)
+ fs = &argv[1][2];
+ } else { /* arg is -F something */
+ argc--; argv++;
+ if (argc > 1 && argv[1][0] == 't' && argv[1][1] == 0) /* wart: t=>\t */
+ fs = "\t";
+ else if (argc > 1 && argv[1][0] != 0)
+ fs = &argv[1][0];
+ }
+ if (fs == NULL || *fs == '\0')
+ WARNING("field separator FS is empty");
+ break;
+ case 'v': /* -v a=1 to be done NOW. one -v for each */
+ if (argv[1][2] != 0) { /* arg is -vsomething */
+ if (isclvar(&argv[1][2]))
+ setclvar(&argv[1][2]);
+ else
+ FATAL("invalid -v option argument: %s", &argv[1][2]);
+ } else { /* arg is -v something */
+ argc--; argv++;
+ if (argc <= 1)
+ FATAL("no variable name");
+ if (isclvar(argv[1]))
+ setclvar(argv[1]);
+ else
+ FATAL("invalid -v option argument: %s", argv[1]);
+ }
+ break;
+ case 'd':
+ dbg = atoi(&argv[1][2]);
+ if (dbg == 0)
+ dbg = 1;
+ printf("awk %s\n", version);
+ break;
+ case 'V': /* added for exptools "standard" */
+ printf("awk %s\n", version);
+ exit(0);
+ break;
+ default:
+ WARNING("unknown option %s ignored", argv[1]);
+ break;
+ }
+ argc--;
+ argv++;
+ }
+ /* argv[1] is now the first argument */
+ if (npfile == 0) { /* no -f; first argument is program */
+ if (argc <= 1) {
+ if (dbg)
+ exit(0);
+ FATAL("no program given");
+ }
+ dprintf( ("program = |%s|\n", argv[1]) );
+ lexprog = argv[1];
+ argc--;
+ argv++;
+ }
+ recinit(recsize);
+ syminit();
+ compile_time = 1;
+ argv[0] = cmdname; /* put prog name at front of arglist */
+ dprintf( ("argc=%d, argv[0]=%s\n", argc, argv[0]) );
+ arginit(argc, argv);
+ if (!safe)
+ envinit(environ);
+ yyparse();
+ setlocale(LC_NUMERIC, ""); /* back to whatever it is locally */
+ if (fs)
+ *FS = qstring(fs, '\0');
+ dprintf( ("errorflag=%d\n", errorflag) );
+ if (errorflag == 0) {
+ compile_time = 0;
+ run(winner);
+ } else
+ bracecheck();
+ return(errorflag);
+}
+
+int pgetc(void) /* get 1 character from awk program */
+{
+ int c;
+
+ for (;;) {
+ if (yyin == NULL) {
+ if (curpfile >= npfile)
+ return EOF;
+ if (strcmp(pfile[curpfile], "-") == 0)
+ yyin = stdin;
+ else if ((yyin = fopen(pfile[curpfile], "r")) == NULL)
+ FATAL("can't open file %s", pfile[curpfile]);
+ lineno = 1;
+ }
+ if ((c = getc(yyin)) != EOF)
+ return c;
+ if (yyin != stdin)
+ fclose(yyin);
+ yyin = NULL;
+ curpfile++;
+ }
+}
+
+char *cursource(void) /* current source file name */
+{
+ if (npfile > 0)
+ return pfile[curpfile];
+ else
+ return NULL;
+}
diff --git a/awk/maketab.c b/awk/maketab.c
@@ -0,0 +1,172 @@
+/* $OpenBSD: maketab.c,v 1.11 2010/06/13 17:58:19 millert Exp $ */
+/****************************************************************
+Copyright (C) Lucent Technologies 1997
+All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and
+its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that the copyright notice and this
+permission notice and warranty disclaimer appear in supporting
+documentation, and that the name Lucent Technologies or any of
+its entities not be used in advertising or publicity pertaining
+to distribution of the software without specific, written prior
+permission.
+
+LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
+SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+****************************************************************/
+
+/*
+ * this program makes the table to link function names
+ * and type indices that is used by execute() in run.c.
+ * it finds the indices in ytab.h, produced by yacc.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "awk.h"
+#include "ytab.h"
+
+struct xx
+{ int token;
+ const char *name;
+ const char *pname;
+} proc[] = {
+ { PROGRAM, "program", NULL },
+ { BOR, "boolop", " || " },
+ { AND, "boolop", " && " },
+ { NOT, "boolop", " !" },
+ { NE, "relop", " != " },
+ { EQ, "relop", " == " },
+ { LE, "relop", " <= " },
+ { LT, "relop", " < " },
+ { GE, "relop", " >= " },
+ { GT, "relop", " > " },
+ { ARRAY, "array", NULL },
+ { INDIRECT, "indirect", "$(" },
+ { SUBSTR, "substr", "substr" },
+ { SUB, "sub", "sub" },
+ { GSUB, "gsub", "gsub" },
+ { INDEX, "sindex", "sindex" },
+ { SPRINTF, "awksprintf", "sprintf " },
+ { ADD, "arith", " + " },
+ { MINUS, "arith", " - " },
+ { MULT, "arith", " * " },
+ { DIVIDE, "arith", " / " },
+ { MOD, "arith", " % " },
+ { UMINUS, "arith", " -" },
+ { POWER, "arith", " **" },
+ { PREINCR, "incrdecr", "++" },
+ { POSTINCR, "incrdecr", "++" },
+ { PREDECR, "incrdecr", "--" },
+ { POSTDECR, "incrdecr", "--" },
+ { CAT, "cat", " " },
+ { PASTAT, "pastat", NULL },
+ { PASTAT2, "dopa2", NULL },
+ { MATCH, "matchop", " ~ " },
+ { NOTMATCH, "matchop", " !~ " },
+ { MATCHFCN, "matchop", "matchop" },
+ { INTEST, "intest", "intest" },
+ { PRINTF, "awkprintf", "printf" },
+ { PRINT, "printstat", "print" },
+ { CLOSE, "closefile", "closefile" },
+ { DELETE, "awkdelete", "awkdelete" },
+ { SPLIT, "split", "split" },
+ { ASSIGN, "assign", " = " },
+ { ADDEQ, "assign", " += " },
+ { SUBEQ, "assign", " -= " },
+ { MULTEQ, "assign", " *= " },
+ { DIVEQ, "assign", " /= " },
+ { MODEQ, "assign", " %= " },
+ { POWEQ, "assign", " ^= " },
+ { CONDEXPR, "condexpr", " ?: " },
+ { IF, "ifstat", "if(" },
+ { WHILE, "whilestat", "while(" },
+ { FOR, "forstat", "for(" },
+ { DO, "dostat", "do" },
+ { IN, "instat", "instat" },
+ { NEXT, "jump", "next" },
+ { NEXTFILE, "jump", "nextfile" },
+ { EXIT, "jump", "exit" },
+ { BREAK, "jump", "break" },
+ { CONTINUE, "jump", "continue" },
+ { RETURN, "jump", "ret" },
+ { BLTIN, "bltin", "bltin" },
+ { CALL, "call", "call" },
+ { ARG, "arg", "arg" },
+ { VARNF, "getnf", "NF" },
+ { GETLINE, "awkgetline", "getline" },
+ { 0, "", "" },
+};
+
+#define SIZE (LASTTOKEN - FIRSTTOKEN + 1)
+const char *table[SIZE];
+char *names[SIZE];
+
+int main(int argc, char *argv[])
+{
+ const struct xx *p;
+ int i, n, tok;
+ char c;
+ FILE *fp;
+ char buf[200], name[200], def[200];
+
+ printf("#include <stdio.h>\n");
+ printf("#include \"awk.h\"\n");
+ printf("#include \"ytab.h\"\n\n");
+ for (i = SIZE; --i >= 0; )
+ names[i] = "";
+
+ if ((fp = fopen("ytab.h", "r")) == NULL) {
+ fprintf(stderr, "maketab: can't open ytab.h!\n");
+ exit(1);
+ }
+ printf("static char *printname[%d] = {\n", SIZE);
+ i = 0;
+ while (fgets(buf, sizeof buf, fp) != NULL) {
+ n = sscanf(buf, "%1c %s %s %d", &c, def, name, &tok);
+ if (n != 4 || c != '#' || strcmp(def, "define") != 0)
+ continue; /* not a valid #define */
+ if (tok < FIRSTTOKEN || tok > LASTTOKEN) {
+ /* fprintf(stderr, "maketab: funny token %d %s ignored\n", tok, buf); */
+ continue;
+ }
+ names[tok-FIRSTTOKEN] = (char *) strdup(name);
+ if (names[tok-FIRSTTOKEN] == NULL) {
+ fprintf(stderr, "maketab: out of memory\n");
+ exit(1);
+ }
+ printf("\t(char *) \"%s\",\t/* %d */\n", name, tok);
+ i++;
+ }
+ printf("};\n\n");
+
+ for (p=proc; p->token!=0; p++)
+ table[p->token-FIRSTTOKEN] = p->name;
+ printf("\nCell *(*proctab[%d])(Node **, int) = {\n", SIZE);
+ for (i=0; i<SIZE; i++)
+ if (table[i]==0)
+ printf("\tnullproc,\t/* %s */\n", names[i]);
+ else
+ printf("\t%s,\t/* %s */\n", table[i], names[i]);
+ printf("};\n\n");
+
+ printf("char *tokname(int n)\n"); /* print a tokname() function */
+ printf("{\n");
+ printf(" static char buf[100];\n\n");
+ printf(" if (n < FIRSTTOKEN || n > LASTTOKEN) {\n");
+ printf(" snprintf(buf, sizeof buf, \"token %%d\", n);\n");
+ printf(" return buf;\n");
+ printf(" }\n");
+ printf(" return printname[n-FIRSTTOKEN];\n");
+ printf("}\n");
+ return 0;
+}
diff --git a/awk/parse.c b/awk/parse.c
@@ -0,0 +1,277 @@
+/* $OpenBSD: parse.c,v 1.6 2002/12/19 21:24:28 millert Exp $ */
+/****************************************************************
+Copyright (C) Lucent Technologies 1997
+All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and
+its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that the copyright notice and this
+permission notice and warranty disclaimer appear in supporting
+documentation, and that the name Lucent Technologies or any of
+its entities not be used in advertising or publicity pertaining
+to distribution of the software without specific, written prior
+permission.
+
+LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
+SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+****************************************************************/
+
+#define DEBUG
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "awk.h"
+#include "ytab.h"
+
+Node *nodealloc(int n)
+{
+ Node *x;
+
+ x = (Node *) malloc(sizeof(Node) + (n-1)*sizeof(Node *));
+ if (x == NULL)
+ FATAL("out of space in nodealloc");
+ x->nnext = NULL;
+ x->lineno = lineno;
+ return(x);
+}
+
+Node *exptostat(Node *a)
+{
+ a->ntype = NSTAT;
+ return(a);
+}
+
+Node *node1(int a, Node *b)
+{
+ Node *x;
+
+ x = nodealloc(1);
+ x->nobj = a;
+ x->narg[0]=b;
+ return(x);
+}
+
+Node *node2(int a, Node *b, Node *c)
+{
+ Node *x;
+
+ x = nodealloc(2);
+ x->nobj = a;
+ x->narg[0] = b;
+ x->narg[1] = c;
+ return(x);
+}
+
+Node *node3(int a, Node *b, Node *c, Node *d)
+{
+ Node *x;
+
+ x = nodealloc(3);
+ x->nobj = a;
+ x->narg[0] = b;
+ x->narg[1] = c;
+ x->narg[2] = d;
+ return(x);
+}
+
+Node *node4(int a, Node *b, Node *c, Node *d, Node *e)
+{
+ Node *x;
+
+ x = nodealloc(4);
+ x->nobj = a;
+ x->narg[0] = b;
+ x->narg[1] = c;
+ x->narg[2] = d;
+ x->narg[3] = e;
+ return(x);
+}
+
+Node *stat1(int a, Node *b)
+{
+ Node *x;
+
+ x = node1(a,b);
+ x->ntype = NSTAT;
+ return(x);
+}
+
+Node *stat2(int a, Node *b, Node *c)
+{
+ Node *x;
+
+ x = node2(a,b,c);
+ x->ntype = NSTAT;
+ return(x);
+}
+
+Node *stat3(int a, Node *b, Node *c, Node *d)
+{
+ Node *x;
+
+ x = node3(a,b,c,d);
+ x->ntype = NSTAT;
+ return(x);
+}
+
+Node *stat4(int a, Node *b, Node *c, Node *d, Node *e)
+{
+ Node *x;
+
+ x = node4(a,b,c,d,e);
+ x->ntype = NSTAT;
+ return(x);
+}
+
+Node *op1(int a, Node *b)
+{
+ Node *x;
+
+ x = node1(a,b);
+ x->ntype = NEXPR;
+ return(x);
+}
+
+Node *op2(int a, Node *b, Node *c)
+{
+ Node *x;
+
+ x = node2(a,b,c);
+ x->ntype = NEXPR;
+ return(x);
+}
+
+Node *op3(int a, Node *b, Node *c, Node *d)
+{
+ Node *x;
+
+ x = node3(a,b,c,d);
+ x->ntype = NEXPR;
+ return(x);
+}
+
+Node *op4(int a, Node *b, Node *c, Node *d, Node *e)
+{
+ Node *x;
+
+ x = node4(a,b,c,d,e);
+ x->ntype = NEXPR;
+ return(x);
+}
+
+Node *celltonode(Cell *a, int b)
+{
+ Node *x;
+
+ a->ctype = OCELL;
+ a->csub = b;
+ x = node1(0, (Node *) a);
+ x->ntype = NVALUE;
+ return(x);
+}
+
+Node *rectonode(void) /* make $0 into a Node */
+{
+ extern Cell *literal0;
+ return op1(INDIRECT, celltonode(literal0, CUNK));
+}
+
+Node *makearr(Node *p)
+{
+ Cell *cp;
+
+ if (isvalue(p)) {
+ cp = (Cell *) (p->narg[0]);
+ if (isfcn(cp))
+ SYNTAX( "%s is a function, not an array", cp->nval );
+ else if (!isarr(cp)) {
+ xfree(cp->sval);
+ cp->sval = (char *) makesymtab(NSYMTAB);
+ cp->tval = ARR;
+ }
+ }
+ return p;
+}
+
+#define PA2NUM 50 /* max number of pat,pat patterns allowed */
+int paircnt; /* number of them in use */
+int pairstack[PA2NUM]; /* state of each pat,pat */
+
+Node *pa2stat(Node *a, Node *b, Node *c) /* pat, pat {...} */
+{
+ Node *x;
+
+ x = node4(PASTAT2, a, b, c, itonp(paircnt));
+ if (paircnt++ >= PA2NUM)
+ SYNTAX( "limited to %d pat,pat statements", PA2NUM );
+ x->ntype = NSTAT;
+ return(x);
+}
+
+Node *linkum(Node *a, Node *b)
+{
+ Node *c;
+
+ if (errorflag) /* don't link things that are wrong */
+ return a;
+ if (a == NULL)
+ return(b);
+ else if (b == NULL)
+ return(a);
+ for (c = a; c->nnext != NULL; c = c->nnext)
+ ;
+ c->nnext = b;
+ return(a);
+}
+
+void defn(Cell *v, Node *vl, Node *st) /* turn on FCN bit in definition, */
+{ /* body of function, arglist */
+ Node *p;
+ int n;
+
+ if (isarr(v)) {
+ SYNTAX( "`%s' is an array name and a function name", v->nval );
+ return;
+ }
+ if (isarg(v->nval) != -1) {
+ SYNTAX( "`%s' is both function name and argument name", v->nval );
+ return;
+ }
+
+ v->tval = FCN;
+ v->sval = (char *) st;
+ n = 0; /* count arguments */
+ for (p = vl; p; p = p->nnext)
+ n++;
+ v->fval = n;
+ dprintf( ("defining func %s (%d args)\n", v->nval, n) );
+}
+
+int isarg(const char *s) /* is s in argument list for current function? */
+{ /* return -1 if not, otherwise arg # */
+ extern Node *arglist;
+ Node *p = arglist;
+ int n;
+
+ for (n = 0; p != 0; p = p->nnext, n++)
+ if (strcmp(((Cell *)(p->narg[0]))->nval, s) == 0)
+ return n;
+ return -1;
+}
+
+int ptoi(void *p) /* convert pointer to integer */
+{
+ return (int) (long) p; /* swearing that p fits, of course */
+}
+
+Node *itonp(int i) /* and vice versa */
+{
+ return (Node *) (long) i;
+}
diff --git a/awk/proto.h b/awk/proto.h
@@ -0,0 +1,196 @@
+/* $OpenBSD: proto.h,v 1.9 2011/09/28 19:27:18 millert Exp $ */
+/****************************************************************
+Copyright (C) Lucent Technologies 1997
+All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and
+its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that the copyright notice and this
+permission notice and warranty disclaimer appear in supporting
+documentation, and that the name Lucent Technologies or any of
+its entities not be used in advertising or publicity pertaining
+to distribution of the software without specific, written prior
+permission.
+
+LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
+SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+****************************************************************/
+
+extern int yywrap(void);
+extern void setfname(Cell *);
+extern int constnode(Node *);
+extern char *strnode(Node *);
+extern Node *notnull(Node *);
+extern int yyparse(void);
+
+extern int yylex(void);
+extern void startreg(void);
+extern int input(void);
+extern void unput(int);
+extern void unputstr(const char *);
+extern int yylook(void);
+extern int yyback(int *, int);
+extern int yyinput(void);
+
+extern fa *makedfa(const char *, int);
+extern fa *mkdfa(const char *, int);
+extern int makeinit(fa *, int);
+extern void penter(Node *);
+extern void freetr(Node *);
+extern int hexstr(uschar **);
+extern int quoted(uschar **);
+extern char *cclenter(const char *);
+extern void overflo(const char *);
+extern void cfoll(fa *, Node *);
+extern int first(Node *);
+extern void follow(Node *);
+extern int member(int, const char *);
+extern int match(fa *, const char *);
+extern int pmatch(fa *, const char *);
+extern int nematch(fa *, const char *);
+extern Node *reparse(const char *);
+extern Node *regexp(void);
+extern Node *primary(void);
+extern Node *concat(Node *);
+extern Node *alt(Node *);
+extern Node *unary(Node *);
+extern int relex(void);
+extern int cgoto(fa *, int, int);
+extern void freefa(fa *);
+
+extern int pgetc(void);
+extern char *cursource(void);
+
+extern Node *nodealloc(int);
+extern Node *exptostat(Node *);
+extern Node *node1(int, Node *);
+extern Node *node2(int, Node *, Node *);
+extern Node *node3(int, Node *, Node *, Node *);
+extern Node *node4(int, Node *, Node *, Node *, Node *);
+extern Node *stat3(int, Node *, Node *, Node *);
+extern Node *op2(int, Node *, Node *);
+extern Node *op1(int, Node *);
+extern Node *stat1(int, Node *);
+extern Node *op3(int, Node *, Node *, Node *);
+extern Node *op4(int, Node *, Node *, Node *, Node *);
+extern Node *stat2(int, Node *, Node *);
+extern Node *stat4(int, Node *, Node *, Node *, Node *);
+extern Node *celltonode(Cell *, int);
+extern Node *rectonode(void);
+extern Node *makearr(Node *);
+extern Node *pa2stat(Node *, Node *, Node *);
+extern Node *linkum(Node *, Node *);
+extern void defn(Cell *, Node *, Node *);
+extern int isarg(const char *);
+extern char *tokname(int);
+extern Cell *(*proctab[])(Node **, int);
+extern int ptoi(void *);
+extern Node *itonp(int);
+
+extern void syminit(void);
+extern void arginit(int, char **);
+extern void envinit(char **);
+extern Array *makesymtab(int);
+extern void freesymtab(Cell *);
+extern void freeelem(Cell *, const char *);
+extern Cell *setsymtab(const char *, const char *, double, unsigned int, Array *);
+extern int hash(const char *, int);
+extern void rehash(Array *);
+extern Cell *lookup(const char *, Array *);
+extern double setfval(Cell *, double);
+extern void funnyvar(Cell *, const char *);
+extern char *setsval(Cell *, const char *);
+extern double getfval(Cell *);
+extern char *getsval(Cell *);
+extern char *getpssval(Cell *); /* for print */
+extern char *tostring(const char *);
+extern char *qstring(const char *, int);
+
+extern void recinit(unsigned int);
+extern void initgetrec(void);
+extern void makefields(int, int);
+extern void growfldtab(int n);
+extern int getrec(char **, int *, int);
+extern void nextfile(void);
+extern int readrec(char **buf, int *bufsize, FILE *inf);
+extern char *getargv(int);
+extern void setclvar(char *);
+extern void fldbld(void);
+extern void cleanfld(int, int);
+extern void newfld(int);
+extern int refldbld(const char *, const char *);
+extern void recbld(void);
+extern Cell *fieldadr(int);
+extern void yyerror(const char *);
+extern void fpecatch(int);
+extern void bracecheck(void);
+extern void bcheck2(int, int, int);
+extern void SYNTAX(const char *, ...);
+extern void FATAL(const char *, ...);
+extern void WARNING(const char *, ...);
+extern void error(void);
+extern void eprint(void);
+extern void bclass(int);
+extern double errcheck(double, const char *);
+extern int isclvar(const char *);
+extern int is_number(const char *);
+
+extern int adjbuf(char **pb, int *sz, int min, int q, char **pbp, const char *what);
+extern void run(Node *);
+extern Cell *execute(Node *);
+extern Cell *program(Node **, int);
+extern Cell *call(Node **, int);
+extern Cell *copycell(Cell *);
+extern Cell *arg(Node **, int);
+extern Cell *jump(Node **, int);
+extern Cell *awkgetline(Node **, int);
+extern Cell *getnf(Node **, int);
+extern Cell *array(Node **, int);
+extern Cell *awkdelete(Node **, int);
+extern Cell *intest(Node **, int);
+extern Cell *matchop(Node **, int);
+extern Cell *boolop(Node **, int);
+extern Cell *relop(Node **, int);
+extern void tfree(Cell *);
+extern Cell *gettemp(void);
+extern Cell *field(Node **, int);
+extern Cell *indirect(Node **, int);
+extern Cell *substr(Node **, int);
+extern Cell *sindex(Node **, int);
+extern int format(char **, int *, const char *, Node *);
+extern Cell *awksprintf(Node **, int);
+extern Cell *awkprintf(Node **, int);
+extern Cell *arith(Node **, int);
+extern double ipow(double, int);
+extern Cell *incrdecr(Node **, int);
+extern Cell *assign(Node **, int);
+extern Cell *cat(Node **, int);
+extern Cell *pastat(Node **, int);
+extern Cell *dopa2(Node **, int);
+extern Cell *split(Node **, int);
+extern Cell *condexpr(Node **, int);
+extern Cell *ifstat(Node **, int);
+extern Cell *whilestat(Node **, int);
+extern Cell *dostat(Node **, int);
+extern Cell *forstat(Node **, int);
+extern Cell *instat(Node **, int);
+extern Cell *bltin(Node **, int);
+extern Cell *printstat(Node **, int);
+extern Cell *nullproc(Node **, int);
+extern FILE *redirect(int, Node *);
+extern FILE *openfile(int, const char *);
+extern const char *filename(FILE *);
+extern Cell *closefile(Node **, int);
+extern void closeall(void);
+extern Cell *sub(Node **, int);
+extern Cell *gsub(Node **, int);
+
+extern FILE *popen(const char *, const char *);
+extern int pclose(FILE *);
diff --git a/awk/reallocarray.c b/awk/reallocarray.c
@@ -0,0 +1,38 @@
+/* $OpenBSD: reallocarray.c,v 1.1 2014/05/08 21:43:49 deraadt Exp $ */
+/*
+ * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW (1UL << (sizeof(size_t) * 4))
+
+void *
+reallocarray(void *optr, size_t nmemb, size_t size)
+{
+ if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ nmemb > 0 && SIZE_MAX / nmemb < size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ return realloc(optr, size * nmemb);
+}
diff --git a/awk/run.c b/awk/run.c
@@ -0,0 +1,2027 @@
+/* $OpenBSD: run.c,v 1.35 2014/10/11 03:07:29 doug Exp $ */
+/****************************************************************
+Copyright (C) Lucent Technologies 1997
+All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and
+its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that the copyright notice and this
+permission notice and warranty disclaimer appear in supporting
+documentation, and that the name Lucent Technologies or any of
+its entities not be used in advertising or publicity pertaining
+to distribution of the software without specific, written prior
+permission.
+
+LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
+SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+****************************************************************/
+
+#define DEBUG
+#include <stdio.h>
+#include <ctype.h>
+#include <setjmp.h>
+#include <limits.h>
+#include <math.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include "awk.h"
+#include "util.h"
+#include "ytab.h"
+
+#define tempfree(x) if (istemp(x)) tfree(x); else
+
+/*
+#undef tempfree
+
+void tempfree(Cell *p) {
+ if (p->ctype == OCELL && (p->csub < CUNK || p->csub > CFREE)) {
+ WARNING("bad csub %d in Cell %d %s",
+ p->csub, p->ctype, p->sval);
+ }
+ if (istemp(p))
+ tfree(p);
+}
+*/
+
+/* do we really need these? */
+/* #ifdef _NFILE */
+/* #ifndef FOPEN_MAX */
+/* #define FOPEN_MAX _NFILE */
+/* #endif */
+/* #endif */
+/* */
+/* #ifndef FOPEN_MAX */
+/* #define FOPEN_MAX 40 */ /* max number of open files */
+/* #endif */
+/* */
+/* #ifndef RAND_MAX */
+/* #define RAND_MAX 32767 */ /* all that ansi guarantees */
+/* #endif */
+
+jmp_buf env;
+int use_srandom;
+extern int pairstack[];
+extern Awkfloat srand_seed;
+
+Node *winner = NULL; /* root of parse tree */
+Cell *tmps; /* free temporary cells for execution */
+
+static Cell truecell ={ OBOOL, BTRUE, 0, 0, 1.0, NUM };
+Cell *True = &truecell;
+static Cell falsecell ={ OBOOL, BFALSE, 0, 0, 0.0, NUM };
+Cell *False = &falsecell;
+static Cell breakcell ={ OJUMP, JBREAK, 0, 0, 0.0, NUM };
+Cell *jbreak = &breakcell;
+static Cell contcell ={ OJUMP, JCONT, 0, 0, 0.0, NUM };
+Cell *jcont = &contcell;
+static Cell nextcell ={ OJUMP, JNEXT, 0, 0, 0.0, NUM };
+Cell *jnext = &nextcell;
+static Cell nextfilecell ={ OJUMP, JNEXTFILE, 0, 0, 0.0, NUM };
+Cell *jnextfile = &nextfilecell;
+static Cell exitcell ={ OJUMP, JEXIT, 0, 0, 0.0, NUM };
+Cell *jexit = &exitcell;
+static Cell retcell ={ OJUMP, JRET, 0, 0, 0.0, NUM };
+Cell *jret = &retcell;
+static Cell tempcell ={ OCELL, CTEMP, 0, "", 0.0, NUM|STR|DONTFREE };
+
+Node *curnode = NULL; /* the node being executed, for debugging */
+
+void stdinit(void);
+void flush_all(void);
+
+/* buffer memory management */
+int adjbuf(char **pbuf, int *psiz, int minlen, int quantum, char **pbptr,
+ const char *whatrtn)
+/* pbuf: address of pointer to buffer being managed
+ * psiz: address of buffer size variable
+ * minlen: minimum length of buffer needed
+ * quantum: buffer size quantum
+ * pbptr: address of movable pointer into buffer, or 0 if none
+ * whatrtn: name of the calling routine if failure should cause fatal error
+ *
+ * return 0 for realloc failure, !=0 for success
+ */
+{
+ if (minlen > *psiz) {
+ char *tbuf;
+ int rminlen = quantum ? minlen % quantum : 0;
+ int boff = pbptr ? *pbptr - *pbuf : 0;
+ /* round up to next multiple of quantum */
+ if (rminlen)
+ minlen += quantum - rminlen;
+ tbuf = (char *) realloc(*pbuf, minlen);
+ dprintf( ("adjbuf %s: %d %d (pbuf=%p, tbuf=%p)\n", whatrtn, *psiz, minlen, *pbuf, tbuf) );
+ if (tbuf == NULL) {
+ if (whatrtn)
+ FATAL("out of memory in %s", whatrtn);
+ return 0;
+ }
+ *pbuf = tbuf;
+ *psiz = minlen;
+ if (pbptr)
+ *pbptr = tbuf + boff;
+ }
+ return 1;
+}
+
+void run(Node *a) /* execution of parse tree starts here */
+{
+ stdinit();
+ execute(a);
+ closeall();
+}
+
+Cell *execute(Node *u) /* execute a node of the parse tree */
+{
+ Cell *(*proc)(Node **, int);
+ Cell *x;
+ Node *a;
+
+ if (u == NULL)
+ return(True);
+ for (a = u; ; a = a->nnext) {
+ curnode = a;
+ if (isvalue(a)) {
+ x = (Cell *) (a->narg[0]);
+ if (isfld(x) && !donefld)
+ fldbld();
+ else if (isrec(x) && !donerec)
+ recbld();
+ return(x);
+ }
+ if (notlegal(a->nobj)) /* probably a Cell* but too risky to print */
+ FATAL("illegal statement");
+ proc = proctab[a->nobj-FIRSTTOKEN];
+ x = (*proc)(a->narg, a->nobj);
+ if (isfld(x) && !donefld)
+ fldbld();
+ else if (isrec(x) && !donerec)
+ recbld();
+ if (isexpr(a))
+ return(x);
+ if (isjump(x))
+ return(x);
+ if (a->nnext == NULL)
+ return(x);
+ tempfree(x);
+ }
+}
+
+
+Cell *program(Node **a, int n) /* execute an awk program */
+{ /* a[0] = BEGIN, a[1] = body, a[2] = END */
+ Cell *x;
+
+ if (setjmp(env) != 0)
+ goto ex;
+ if (a[0]) { /* BEGIN */
+ x = execute(a[0]);
+ if (isexit(x))
+ return(True);
+ if (isjump(x))
+ FATAL("illegal break, continue, next or nextfile from BEGIN");
+ tempfree(x);
+ }
+ if (a[1] || a[2])
+ while (getrec(&record, &recsize, 1) > 0) {
+ x = execute(a[1]);
+ if (isexit(x))
+ break;
+ tempfree(x);
+ }
+ ex:
+ if (setjmp(env) != 0) /* handles exit within END */
+ goto ex1;
+ if (a[2]) { /* END */
+ x = execute(a[2]);
+ if (isbreak(x) || isnext(x) || iscont(x))
+ FATAL("illegal break, continue, next or nextfile from END");
+ tempfree(x);
+ }
+ ex1:
+ return(True);
+}
+
+struct Frame { /* stack frame for awk function calls */
+ int nargs; /* number of arguments in this call */
+ Cell *fcncell; /* pointer to Cell for function */
+ Cell **args; /* pointer to array of arguments after execute */
+ Cell *retval; /* return value */
+};
+
+#define NARGS 50 /* max args in a call */
+
+struct Frame *frame = NULL; /* base of stack frames; dynamically allocated */
+int nframe = 0; /* number of frames allocated */
+struct Frame *fp = NULL; /* frame pointer. bottom level unused */
+
+Cell *call(Node **a, int n) /* function call. very kludgy and fragile */
+{
+ static Cell newcopycell = { OCELL, CCOPY, 0, "", 0.0, NUM|STR|DONTFREE };
+ int i, ncall, ndef;
+ int freed = 0; /* handles potential double freeing when fcn & param share a tempcell */
+ Node *x;
+ Cell *args[NARGS], *oargs[NARGS]; /* BUG: fixed size arrays */
+ Cell *y, *z, *fcn;
+ char *s;
+
+ fcn = execute(a[0]); /* the function itself */
+ s = fcn->nval;
+ if (!isfcn(fcn))
+ FATAL("calling undefined function %s", s);
+ if (frame == NULL) {
+ fp = frame = (struct Frame *) calloc(nframe += 100, sizeof(struct Frame));
+ if (frame == NULL)
+ FATAL("out of space for stack frames calling %s", s);
+ }
+ for (ncall = 0, x = a[1]; x != NULL; x = x->nnext) /* args in call */
+ ncall++;
+ ndef = (int) fcn->fval; /* args in defn */
+ dprintf( ("calling %s, %d args (%d in defn), fp=%d\n", s, ncall, ndef, (int) (fp-frame)) );
+ if (ncall > ndef)
+ WARNING("function %s called with %d args, uses only %d",
+ s, ncall, ndef);
+ if (ncall + ndef > NARGS)
+ FATAL("function %s has %d arguments, limit %d", s, ncall+ndef, NARGS);
+ for (i = 0, x = a[1]; x != NULL; i++, x = x->nnext) { /* get call args */
+ dprintf( ("evaluate args[%d], fp=%d:\n", i, (int) (fp-frame)) );
+ y = execute(x);
+ oargs[i] = y;
+ dprintf( ("args[%d]: %s %f <%s>, t=%o\n",
+ i, NN(y->nval), y->fval, isarr(y) ? "(array)" : NN(y->sval), y->tval) );
+ if (isfcn(y))
+ FATAL("can't use function %s as argument in %s", y->nval, s);
+ if (isarr(y))
+ args[i] = y; /* arrays by ref */
+ else
+ args[i] = copycell(y);
+ tempfree(y);
+ }
+ for ( ; i < ndef; i++) { /* add null args for ones not provided */
+ args[i] = gettemp();
+ *args[i] = newcopycell;
+ }
+ fp++; /* now ok to up frame */
+ if (fp >= frame + nframe) {
+ int dfp = fp - frame; /* old index */
+ frame = (struct Frame *)
+ realloc((char *) frame, (nframe += 100) * sizeof(struct Frame));
+ if (frame == NULL)
+ FATAL("out of space for stack frames in %s", s);
+ fp = frame + dfp;
+ }
+ fp->fcncell = fcn;
+ fp->args = args;
+ fp->nargs = ndef; /* number defined with (excess are locals) */
+ fp->retval = gettemp();
+
+ dprintf( ("start exec of %s, fp=%d\n", s, (int) (fp-frame)) );
+ y = execute((Node *)(fcn->sval)); /* execute body */
+ dprintf( ("finished exec of %s, fp=%d\n", s, (int) (fp-frame)) );
+
+ for (i = 0; i < ndef; i++) {
+ Cell *t = fp->args[i];
+ if (isarr(t)) {
+ if (t->csub == CCOPY) {
+ if (i >= ncall) {
+ freesymtab(t);
+ t->csub = CTEMP;
+ tempfree(t);
+ } else {
+ oargs[i]->tval = t->tval;
+ oargs[i]->tval &= ~(STR|NUM|DONTFREE);
+ oargs[i]->sval = t->sval;
+ tempfree(t);
+ }
+ }
+ } else if (t != y) { /* kludge to prevent freeing twice */
+ t->csub = CTEMP;
+ tempfree(t);
+ } else if (t == y && t->csub == CCOPY) {
+ t->csub = CTEMP;
+ tempfree(t);
+ freed = 1;
+ }
+ }
+ tempfree(fcn);
+ if (isexit(y) || isnext(y))
+ return y;
+ if (freed == 0) {
+ tempfree(y); /* don't free twice! */
+ }
+ z = fp->retval; /* return value */
+ dprintf( ("%s returns %g |%s| %o\n", s, getfval(z), getsval(z), z->tval) );
+ fp--;
+ return(z);
+}
+
+Cell *copycell(Cell *x) /* make a copy of a cell in a temp */
+{
+ Cell *y;
+
+ y = gettemp();
+ y->csub = CCOPY; /* prevents freeing until call is over */
+ y->nval = x->nval; /* BUG? */
+ if (isstr(x))
+ y->sval = tostring(x->sval);
+ y->fval = x->fval;
+ y->tval = x->tval & ~(CON|FLD|REC|DONTFREE); /* copy is not constant or field */
+ /* is DONTFREE right? */
+ return y;
+}
+
+Cell *arg(Node **a, int n) /* nth argument of a function */
+{
+
+ n = ptoi(a[0]); /* argument number, counting from 0 */
+ dprintf( ("arg(%d), fp->nargs=%d\n", n, fp->nargs) );
+ if (n+1 > fp->nargs)
+ FATAL("argument #%d of function %s was not supplied",
+ n+1, fp->fcncell->nval);
+ return fp->args[n];
+}
+
+Cell *jump(Node **a, int n) /* break, continue, next, nextfile, return */
+{
+ Cell *y;
+
+ switch (n) {
+ case EXIT:
+ if (a[0] != NULL) {
+ y = execute(a[0]);
+ errorflag = (int) getfval(y);
+ tempfree(y);
+ }
+ longjmp(env, 1);
+ case RETURN:
+ if (a[0] != NULL) {
+ y = execute(a[0]);
+ if ((y->tval & (STR|NUM)) == (STR|NUM)) {
+ setsval(fp->retval, getsval(y));
+ fp->retval->fval = getfval(y);
+ fp->retval->tval |= NUM;
+ }
+ else if (y->tval & STR)
+ setsval(fp->retval, getsval(y));
+ else if (y->tval & NUM)
+ setfval(fp->retval, getfval(y));
+ else /* can't happen */
+ FATAL("bad type variable %d", y->tval);
+ tempfree(y);
+ }
+ return(jret);
+ case NEXT:
+ return(jnext);
+ case NEXTFILE:
+ nextfile();
+ return(jnextfile);
+ case BREAK:
+ return(jbreak);
+ case CONTINUE:
+ return(jcont);
+ default: /* can't happen */
+ FATAL("illegal jump type %d", n);
+ }
+ return 0; /* not reached */
+}
+
+Cell *awkgetline(Node **a, int n) /* get next line from specific input */
+{ /* a[0] is variable, a[1] is operator, a[2] is filename */
+ Cell *r, *x;
+ extern Cell **fldtab;
+ FILE *fp;
+ char *buf;
+ int bufsize = recsize;
+ int mode;
+
+ if ((buf = (char *) malloc(bufsize)) == NULL)
+ FATAL("out of memory in getline");
+
+ fflush(stdout); /* in case someone is waiting for a prompt */
+ r = gettemp();
+ if (a[1] != NULL) { /* getline < file */
+ x = execute(a[2]); /* filename */
+ mode = ptoi(a[1]);
+ if (mode == '|') /* input pipe */
+ mode = LE; /* arbitrary flag */
+ fp = openfile(mode, getsval(x));
+ tempfree(x);
+ if (fp == NULL)
+ n = -1;
+ else
+ n = readrec(&buf, &bufsize, fp);
+ if (n <= 0) {
+ ;
+ } else if (a[0] != NULL) { /* getline var <file */
+ x = execute(a[0]);
+ setsval(x, buf);
+ tempfree(x);
+ } else { /* getline <file */
+ setsval(fldtab[0], buf);
+ if (is_number(fldtab[0]->sval)) {
+ fldtab[0]->fval = atof(fldtab[0]->sval);
+ fldtab[0]->tval |= NUM;
+ }
+ }
+ } else { /* bare getline; use current input */
+ if (a[0] == NULL) /* getline */
+ n = getrec(&record, &recsize, 1);
+ else { /* getline var */
+ n = getrec(&buf, &bufsize, 0);
+ x = execute(a[0]);
+ setsval(x, buf);
+ tempfree(x);
+ }
+ }
+ setfval(r, (Awkfloat) n);
+ free(buf);
+ return r;
+}
+
+Cell *getnf(Node **a, int n) /* get NF */
+{
+ if (donefld == 0)
+ fldbld();
+ return (Cell *) a[0];
+}
+
+Cell *array(Node **a, int n) /* a[0] is symtab, a[1] is list of subscripts */
+{
+ Cell *x, *y, *z;
+ char *s;
+ Node *np;
+ char *buf;
+ int bufsz = recsize;
+ int nsub = strlen(*SUBSEP);
+
+ if ((buf = (char *) malloc(bufsz)) == NULL)
+ FATAL("out of memory in array");
+
+ x = execute(a[0]); /* Cell* for symbol table */
+ buf[0] = 0;
+ for (np = a[1]; np; np = np->nnext) {
+ y = execute(np); /* subscript */
+ s = getsval(y);
+ if (!adjbuf(&buf, &bufsz, strlen(buf)+strlen(s)+nsub+1, recsize, 0, "array"))
+ FATAL("out of memory for %s[%s...]", x->nval, buf);
+ strlcat(buf, s, bufsz);
+ if (np->nnext)
+ strlcat(buf, *SUBSEP, bufsz);
+ tempfree(y);
+ }
+ if (!isarr(x)) {
+ dprintf( ("making %s into an array\n", NN(x->nval)) );
+ if (freeable(x))
+ xfree(x->sval);
+ x->tval &= ~(STR|NUM|DONTFREE);
+ x->tval |= ARR;
+ x->sval = (char *) makesymtab(NSYMTAB);
+ }
+ z = setsymtab(buf, "", 0.0, STR|NUM, (Array *) x->sval);
+ z->ctype = OCELL;
+ z->csub = CVAR;
+ tempfree(x);
+ free(buf);
+ return(z);
+}
+
+Cell *awkdelete(Node **a, int n) /* a[0] is symtab, a[1] is list of subscripts */
+{
+ Cell *x, *y;
+ Node *np;
+ char *s;
+ int nsub = strlen(*SUBSEP);
+
+ x = execute(a[0]); /* Cell* for symbol table */
+ if (!isarr(x))
+ return True;
+ if (a[1] == 0) { /* delete the elements, not the table */
+ freesymtab(x);
+ x->tval &= ~STR;
+ x->tval |= ARR;
+ x->sval = (char *) makesymtab(NSYMTAB);
+ } else {
+ int bufsz = recsize;
+ char *buf;
+ if ((buf = (char *) malloc(bufsz)) == NULL)
+ FATAL("out of memory in adelete");
+ buf[0] = 0;
+ for (np = a[1]; np; np = np->nnext) {
+ y = execute(np); /* subscript */
+ s = getsval(y);
+ if (!adjbuf(&buf, &bufsz, strlen(buf)+strlen(s)+nsub+1, recsize, 0, "awkdelete"))
+ FATAL("out of memory deleting %s[%s...]", x->nval, buf);
+ strlcat(buf, s, bufsz);
+ if (np->nnext)
+ strlcat(buf, *SUBSEP, bufsz);
+ tempfree(y);
+ }
+ freeelem(x, buf);
+ free(buf);
+ }
+ tempfree(x);
+ return True;
+}
+
+Cell *intest(Node **a, int n) /* a[0] is index (list), a[1] is symtab */
+{
+ Cell *x, *ap, *k;
+ Node *p;
+ char *buf;
+ char *s;
+ int bufsz = recsize;
+ int nsub = strlen(*SUBSEP);
+
+ ap = execute(a[1]); /* array name */
+ if (!isarr(ap)) {
+ dprintf( ("making %s into an array\n", ap->nval) );
+ if (freeable(ap))
+ xfree(ap->sval);
+ ap->tval &= ~(STR|NUM|DONTFREE);
+ ap->tval |= ARR;
+ ap->sval = (char *) makesymtab(NSYMTAB);
+ }
+ if ((buf = (char *) malloc(bufsz)) == NULL) {
+ FATAL("out of memory in intest");
+ }
+ buf[0] = 0;
+ for (p = a[0]; p; p = p->nnext) {
+ x = execute(p); /* expr */
+ s = getsval(x);
+ if (!adjbuf(&buf, &bufsz, strlen(buf)+strlen(s)+nsub+1, recsize, 0, "intest"))
+ FATAL("out of memory deleting %s[%s...]", x->nval, buf);
+ strlcat(buf, s, bufsz);
+ tempfree(x);
+ if (p->nnext)
+ strlcat(buf, *SUBSEP, bufsz);
+ }
+ k = lookup(buf, (Array *) ap->sval);
+ tempfree(ap);
+ free(buf);
+ if (k == NULL)
+ return(False);
+ else
+ return(True);
+}
+
+
+Cell *matchop(Node **a, int n) /* ~ and match() */
+{
+ Cell *x, *y;
+ char *s, *t;
+ int i;
+ fa *pfa;
+ int (*mf)(fa *, const char *) = match, mode = 0;
+
+ if (n == MATCHFCN) {
+ mf = pmatch;
+ mode = 1;
+ }
+ x = execute(a[1]); /* a[1] = target text */
+ s = getsval(x);
+ if (a[0] == 0) /* a[1] == 0: already-compiled reg expr */
+ i = (*mf)((fa *) a[2], s);
+ else {
+ y = execute(a[2]); /* a[2] = regular expr */
+ t = getsval(y);
+ pfa = makedfa(t, mode);
+ i = (*mf)(pfa, s);
+ tempfree(y);
+ }
+ tempfree(x);
+ if (n == MATCHFCN) {
+ int start = patbeg - s + 1;
+ if (patlen < 0)
+ start = 0;
+ setfval(rstartloc, (Awkfloat) start);
+ setfval(rlengthloc, (Awkfloat) patlen);
+ x = gettemp();
+ x->tval = NUM;
+ x->fval = start;
+ return x;
+ } else if ((n == MATCH && i == 1) || (n == NOTMATCH && i == 0))
+ return(True);
+ else
+ return(False);
+}
+
+
+Cell *boolop(Node **a, int n) /* a[0] || a[1], a[0] && a[1], !a[0] */
+{
+ Cell *x, *y;
+ int i;
+
+ x = execute(a[0]);
+ i = istrue(x);
+ tempfree(x);
+ switch (n) {
+ case BOR:
+ if (i) return(True);
+ y = execute(a[1]);
+ i = istrue(y);
+ tempfree(y);
+ if (i) return(True);
+ else return(False);
+ case AND:
+ if ( !i ) return(False);
+ y = execute(a[1]);
+ i = istrue(y);
+ tempfree(y);
+ if (i) return(True);
+ else return(False);
+ case NOT:
+ if (i) return(False);
+ else return(True);
+ default: /* can't happen */
+ FATAL("unknown boolean operator %d", n);
+ }
+ return 0; /*NOTREACHED*/
+}
+
+Cell *relop(Node **a, int n) /* a[0 < a[1], etc. */
+{
+ int i;
+ Cell *x, *y;
+ Awkfloat j;
+
+ x = execute(a[0]);
+ y = execute(a[1]);
+ if (x->tval&NUM && y->tval&NUM) {
+ j = x->fval - y->fval;
+ i = j<0? -1: (j>0? 1: 0);
+ } else {
+ i = strcmp(getsval(x), getsval(y));
+ }
+ tempfree(x);
+ tempfree(y);
+ switch (n) {
+ case LT: if (i<0) return(True);
+ else return(False);
+ case LE: if (i<=0) return(True);
+ else return(False);
+ case NE: if (i!=0) return(True);
+ else return(False);
+ case EQ: if (i == 0) return(True);
+ else return(False);
+ case GE: if (i>=0) return(True);
+ else return(False);
+ case GT: if (i>0) return(True);
+ else return(False);
+ default: /* can't happen */
+ FATAL("unknown relational operator %d", n);
+ }
+ return 0; /*NOTREACHED*/
+}
+
+void tfree(Cell *a) /* free a tempcell */
+{
+ if (freeable(a)) {
+ dprintf( ("freeing %s %s %o\n", NN(a->nval), NN(a->sval), a->tval) );
+ xfree(a->sval);
+ }
+ if (a == tmps)
+ FATAL("tempcell list is curdled");
+ a->cnext = tmps;
+ tmps = a;
+}
+
+Cell *gettemp(void) /* get a tempcell */
+{ int i;
+ Cell *x;
+
+ if (!tmps) {
+ tmps = (Cell *) calloc(100, sizeof(Cell));
+ if (!tmps)
+ FATAL("out of space for temporaries");
+ for(i = 1; i < 100; i++)
+ tmps[i-1].cnext = &tmps[i];
+ tmps[i-1].cnext = 0;
+ }
+ x = tmps;
+ tmps = x->cnext;
+ *x = tempcell;
+ return(x);
+}
+
+Cell *indirect(Node **a, int n) /* $( a[0] ) */
+{
+ Awkfloat val;
+ Cell *x;
+ int m;
+ char *s;
+
+ x = execute(a[0]);
+ val = getfval(x); /* freebsd: defend against super large field numbers */
+ if ((Awkfloat)INT_MAX < val)
+ FATAL("trying to access out of range field %s", x->nval);
+ m = (int) val;
+ if (m == 0 && !is_number(s = getsval(x))) /* suspicion! */
+ FATAL("illegal field $(%s), name \"%s\"", s, x->nval);
+ /* BUG: can x->nval ever be null??? */
+ tempfree(x);
+ x = fieldadr(m);
+ x->ctype = OCELL; /* BUG? why are these needed? */
+ x->csub = CFLD;
+ return(x);
+}
+
+Cell *substr(Node **a, int nnn) /* substr(a[0], a[1], a[2]) */
+{
+ int k, m, n;
+ char *s;
+ int temp;
+ Cell *x, *y, *z = 0;
+
+ x = execute(a[0]);
+ y = execute(a[1]);
+ if (a[2] != 0)
+ z = execute(a[2]);
+ s = getsval(x);
+ k = strlen(s) + 1;
+ if (k <= 1) {
+ tempfree(x);
+ tempfree(y);
+ if (a[2] != 0) {
+ tempfree(z);
+ }
+ x = gettemp();
+ setsval(x, "");
+ return(x);
+ }
+ m = (int) getfval(y);
+ if (m <= 0)
+ m = 1;
+ else if (m > k)
+ m = k;
+ tempfree(y);
+ if (a[2] != 0) {
+ n = (int) getfval(z);
+ tempfree(z);
+ } else
+ n = k - 1;
+ if (n < 0)
+ n = 0;
+ else if (n > k - m)
+ n = k - m;
+ dprintf( ("substr: m=%d, n=%d, s=%s\n", m, n, s) );
+ y = gettemp();
+ temp = s[n+m-1]; /* with thanks to John Linderman */
+ s[n+m-1] = '\0';
+ setsval(y, s + m - 1);
+ s[n+m-1] = temp;
+ tempfree(x);
+ return(y);
+}
+
+Cell *sindex(Node **a, int nnn) /* index(a[0], a[1]) */
+{
+ Cell *x, *y, *z;
+ char *s1, *s2, *p1, *p2, *q;
+ Awkfloat v = 0.0;
+
+ x = execute(a[0]);
+ s1 = getsval(x);
+ y = execute(a[1]);
+ s2 = getsval(y);
+
+ z = gettemp();
+ for (p1 = s1; *p1 != '\0'; p1++) {
+ for (q=p1, p2=s2; *p2 != '\0' && *q == *p2; q++, p2++)
+ ;
+ if (*p2 == '\0') {
+ v = (Awkfloat) (p1 - s1 + 1); /* origin 1 */
+ break;
+ }
+ }
+ tempfree(x);
+ tempfree(y);
+ setfval(z, v);
+ return(z);
+}
+
+#define MAXNUMSIZE 50
+
+int format(char **pbuf, int *pbufsize, const char *s, Node *a) /* printf-like conversions */
+{
+ char *fmt;
+ char *p, *t;
+ const char *os;
+ Cell *x;
+ int flag = 0, n;
+ int fmtwd; /* format width */
+ int fmtsz = recsize;
+ char *buf = *pbuf;
+ int bufsize = *pbufsize;
+
+ os = s;
+ p = buf;
+ if ((fmt = (char *) malloc(fmtsz)) == NULL)
+ FATAL("out of memory in format()");
+ while (*s) {
+ adjbuf(&buf, &bufsize, MAXNUMSIZE+1+p-buf, recsize, &p, "format1");
+ if (*s != '%') {
+ *p++ = *s++;
+ continue;
+ }
+ if (*(s+1) == '%') {
+ *p++ = '%';
+ s += 2;
+ continue;
+ }
+ /* have to be real careful in case this is a huge number, eg, %100000d */
+ fmtwd = atoi(s+1);
+ if (fmtwd < 0)
+ fmtwd = -fmtwd;
+ adjbuf(&buf, &bufsize, fmtwd+1+p-buf, recsize, &p, "format2");
+ for (t = fmt; (*t++ = *s) != '\0'; s++) {
+ if (!adjbuf(&fmt, &fmtsz, MAXNUMSIZE+1+t-fmt, recsize, &t, "format3"))
+ FATAL("format item %.30s... ran format() out of memory", os);
+ if (isalpha((uschar)*s) && *s != 'l' && *s != 'h' && *s != 'L')
+ break; /* the ansi panoply */
+ if (*s == '*') {
+ if (a == NULL)
+ FATAL("not enough args in printf(%s)", os);
+ x = execute(a);
+ a = a->nnext;
+ snprintf(t-1, fmt + fmtsz - (t-1), "%d", fmtwd=(int) getfval(x));
+ if (fmtwd < 0)
+ fmtwd = -fmtwd;
+ adjbuf(&buf, &bufsize, fmtwd+1+p-buf, recsize, &p, "format");
+ t = fmt + strlen(fmt);
+ tempfree(x);
+ }
+ }
+ *t = '\0';
+ if (fmtwd < 0)
+ fmtwd = -fmtwd;
+ adjbuf(&buf, &bufsize, fmtwd+1+p-buf, recsize, &p, "format4");
+
+ switch (*s) {
+ case 'f': case 'e': case 'g': case 'E': case 'G':
+ flag = 'f';
+ break;
+ case 'd': case 'i':
+ flag = 'd';
+ if(*(s-1) == 'l') break;
+ *(t-1) = 'l';
+ *t = 'd';
+ *++t = '\0';
+ break;
+ case 'o': case 'x': case 'X': case 'u':
+ flag = *(s-1) == 'l' ? 'd' : 'u';
+ break;
+ case 's':
+ flag = 's';
+ break;
+ case 'c':
+ flag = 'c';
+ break;
+ default:
+ WARNING("weird printf conversion %s", fmt);
+ flag = '?';
+ break;
+ }
+ if (a == NULL)
+ FATAL("not enough args in printf(%s)", os);
+ x = execute(a);
+ a = a->nnext;
+ n = MAXNUMSIZE;
+ if (fmtwd > n)
+ n = fmtwd;
+ adjbuf(&buf, &bufsize, 1+n+p-buf, recsize, &p, "format5");
+ switch (flag) {
+ case '?': /* unknown, so dump it too */
+ snprintf(p, buf + bufsize - p, "%s", fmt);
+ t = getsval(x);
+ n = strlen(t);
+ if (fmtwd > n)
+ n = fmtwd;
+ adjbuf(&buf, &bufsize, 1+strlen(p)+n+p-buf, recsize, &p, "format6");
+ p += strlen(p);
+ snprintf(p, buf + bufsize - p, "%s", t);
+ break;
+ case 'f': snprintf(p, buf + bufsize - p, fmt, getfval(x)); break;
+ case 'd': snprintf(p, buf + bufsize - p, fmt, (long) getfval(x)); break;
+ case 'u': snprintf(p, buf + bufsize - p, fmt, (int) getfval(x)); break;
+ case 's':
+ t = getsval(x);
+ n = strlen(t);
+ if (fmtwd > n)
+ n = fmtwd;
+ if (!adjbuf(&buf, &bufsize, 1+n+p-buf, recsize, &p, "format7"))
+ FATAL("huge string/format (%d chars) in printf %.30s... ran format() out of memory", n, t);
+ snprintf(p, buf + bufsize - p, fmt, t);
+ break;
+ case 'c':
+ if (isnum(x)) {
+ if (getfval(x))
+ snprintf(p, buf + bufsize - p, fmt, (int) getfval(x));
+ else {
+ *p++ = '\0'; /* explicit null byte */
+ *p = '\0'; /* next output will start here */
+ }
+ } else
+ snprintf(p, buf + bufsize - p, fmt, getsval(x)[0]);
+ break;
+ default:
+ FATAL("can't happen: bad conversion %c in format()", flag);
+ }
+ tempfree(x);
+ p += strlen(p);
+ s++;
+ }
+ *p = '\0';
+ free(fmt);
+ for ( ; a; a = a->nnext) /* evaluate any remaining args */
+ execute(a);
+ *pbuf = buf;
+ *pbufsize = bufsize;
+ return p - buf;
+}
+
+Cell *awksprintf(Node **a, int n) /* sprintf(a[0]) */
+{
+ Cell *x;
+ Node *y;
+ char *buf;
+ int bufsz=3*recsize;
+
+ if ((buf = (char *) malloc(bufsz)) == NULL)
+ FATAL("out of memory in awksprintf");
+ y = a[0]->nnext;
+ x = execute(a[0]);
+ if (format(&buf, &bufsz, getsval(x), y) == -1)
+ FATAL("sprintf string %.30s... too long. can't happen.", buf);
+ tempfree(x);
+ x = gettemp();
+ x->sval = buf;
+ x->tval = STR;
+ return(x);
+}
+
+Cell *awkprintf(Node **a, int n) /* printf */
+{ /* a[0] is list of args, starting with format string */
+ /* a[1] is redirection operator, a[2] is redirection file */
+ FILE *fp;
+ Cell *x;
+ Node *y;
+ char *buf;
+ int len;
+ int bufsz=3*recsize;
+
+ if ((buf = (char *) malloc(bufsz)) == NULL)
+ FATAL("out of memory in awkprintf");
+ y = a[0]->nnext;
+ x = execute(a[0]);
+ if ((len = format(&buf, &bufsz, getsval(x), y)) == -1)
+ FATAL("printf string %.30s... too long. can't happen.", buf);
+ tempfree(x);
+ if (a[1] == NULL) {
+ /* fputs(buf, stdout); */
+ fwrite(buf, len, 1, stdout);
+ if (ferror(stdout))
+ FATAL("write error on stdout");
+ } else {
+ fp = redirect(ptoi(a[1]), a[2]);
+ /* fputs(buf, fp); */
+ fwrite(buf, len, 1, fp);
+ fflush(fp);
+ if (ferror(fp))
+ FATAL("write error on %s", filename(fp));
+ }
+ free(buf);
+ return(True);
+}
+
+Cell *arith(Node **a, int n) /* a[0] + a[1], etc. also -a[0] */
+{
+ Awkfloat i, j = 0;
+ double v;
+ Cell *x, *y, *z;
+
+ x = execute(a[0]);
+ i = getfval(x);
+ tempfree(x);
+ if (n != UMINUS) {
+ y = execute(a[1]);
+ j = getfval(y);
+ tempfree(y);
+ }
+ z = gettemp();
+ switch (n) {
+ case ADD:
+ i += j;
+ break;
+ case MINUS:
+ i -= j;
+ break;
+ case MULT:
+ i *= j;
+ break;
+ case DIVIDE:
+ if (j == 0)
+ FATAL("division by zero");
+ i /= j;
+ break;
+ case MOD:
+ if (j == 0)
+ FATAL("division by zero in mod");
+ modf(i/j, &v);
+ i = i - j * v;
+ break;
+ case UMINUS:
+ i = -i;
+ break;
+ case POWER:
+ if (j >= 0 && modf(j, &v) == 0.0) /* pos integer exponent */
+ i = ipow(i, (int) j);
+ else
+ i = errcheck(pow(i, j), "pow");
+ break;
+ default: /* can't happen */
+ FATAL("illegal arithmetic operator %d", n);
+ }
+ setfval(z, i);
+ return(z);
+}
+
+double ipow(double x, int n) /* x**n. ought to be done by pow, but isn't always */
+{
+ double v;
+
+ if (n <= 0)
+ return 1;
+ v = ipow(x, n/2);
+ if (n % 2 == 0)
+ return v * v;
+ else
+ return x * v * v;
+}
+
+Cell *incrdecr(Node **a, int n) /* a[0]++, etc. */
+{
+ Cell *x, *z;
+ int k;
+ Awkfloat xf;
+
+ x = execute(a[0]);
+ xf = getfval(x);
+ k = (n == PREINCR || n == POSTINCR) ? 1 : -1;
+ if (n == PREINCR || n == PREDECR) {
+ setfval(x, xf + k);
+ return(x);
+ }
+ z = gettemp();
+ setfval(z, xf);
+ setfval(x, xf + k);
+ tempfree(x);
+ return(z);
+}
+
+Cell *assign(Node **a, int n) /* a[0] = a[1], a[0] += a[1], etc. */
+{ /* this is subtle; don't muck with it. */
+ Cell *x, *y;
+ Awkfloat xf, yf;
+ double v;
+
+ y = execute(a[1]);
+ x = execute(a[0]);
+ if (n == ASSIGN) { /* ordinary assignment */
+ if (x == y && !(x->tval & (FLD|REC))) /* self-assignment: */
+ ; /* leave alone unless it's a field */
+ else if ((y->tval & (STR|NUM)) == (STR|NUM)) {
+ setsval(x, getsval(y));
+ x->fval = getfval(y);
+ x->tval |= NUM;
+ }
+ else if (isstr(y))
+ setsval(x, getsval(y));
+ else if (isnum(y))
+ setfval(x, getfval(y));
+ else
+ funnyvar(y, "read value of");
+ tempfree(y);
+ return(x);
+ }
+ xf = getfval(x);
+ yf = getfval(y);
+ switch (n) {
+ case ADDEQ:
+ xf += yf;
+ break;
+ case SUBEQ:
+ xf -= yf;
+ break;
+ case MULTEQ:
+ xf *= yf;
+ break;
+ case DIVEQ:
+ if (yf == 0)
+ FATAL("division by zero in /=");
+ xf /= yf;
+ break;
+ case MODEQ:
+ if (yf == 0)
+ FATAL("division by zero in %%=");
+ modf(xf/yf, &v);
+ xf = xf - yf * v;
+ break;
+ case POWEQ:
+ if (yf >= 0 && modf(yf, &v) == 0.0) /* pos integer exponent */
+ xf = ipow(xf, (int) yf);
+ else
+ xf = errcheck(pow(xf, yf), "pow");
+ break;
+ default:
+ FATAL("illegal assignment operator %d", n);
+ break;
+ }
+ tempfree(y);
+ setfval(x, xf);
+ return(x);
+}
+
+Cell *cat(Node **a, int q) /* a[0] cat a[1] */
+{
+ Cell *x, *y, *z;
+ int n1, n2;
+ char *s;
+ size_t len;
+
+ x = execute(a[0]);
+ y = execute(a[1]);
+ getsval(x);
+ getsval(y);
+ n1 = strlen(x->sval);
+ n2 = strlen(y->sval);
+ len = n1 + n2 + 1;
+ s = (char *) malloc(len);
+ if (s == NULL)
+ FATAL("out of space concatenating %.15s... and %.15s...",
+ x->sval, y->sval);
+ strlcpy(s, x->sval, len);
+ strlcpy(s+n1, y->sval, len - n1);
+ tempfree(x);
+ tempfree(y);
+ z = gettemp();
+ z->sval = s;
+ z->tval = STR;
+ return(z);
+}
+
+Cell *pastat(Node **a, int n) /* a[0] { a[1] } */
+{
+ Cell *x;
+
+ if (a[0] == 0)
+ x = execute(a[1]);
+ else {
+ x = execute(a[0]);
+ if (istrue(x)) {
+ tempfree(x);
+ x = execute(a[1]);
+ }
+ }
+ return x;
+}
+
+Cell *dopa2(Node **a, int n) /* a[0], a[1] { a[2] } */
+{
+ Cell *x;
+ int pair;
+
+ pair = ptoi(a[3]);
+ if (pairstack[pair] == 0) {
+ x = execute(a[0]);
+ if (istrue(x))
+ pairstack[pair] = 1;
+ tempfree(x);
+ }
+ if (pairstack[pair] == 1) {
+ x = execute(a[1]);
+ if (istrue(x))
+ pairstack[pair] = 0;
+ tempfree(x);
+ x = execute(a[2]);
+ return(x);
+ }
+ return(False);
+}
+
+Cell *split(Node **a, int nnn) /* split(a[0], a[1], a[2]); a[3] is type */
+{
+ Cell *x = 0, *y, *ap;
+ char *s;
+ int sep;
+ char *t, temp, num[50], *fs = 0;
+ int n, tempstat, arg3type;
+
+ y = execute(a[0]); /* source string */
+ s = getsval(y);
+ arg3type = ptoi(a[3]);
+ if (a[2] == 0) /* fs string */
+ fs = *FS;
+ else if (arg3type == STRING) { /* split(str,arr,"string") */
+ x = execute(a[2]);
+ fs = getsval(x);
+ } else if (arg3type == REGEXPR)
+ fs = "(regexpr)"; /* split(str,arr,/regexpr/) */
+ else
+ FATAL("illegal type of split");
+ sep = *fs;
+ ap = execute(a[1]); /* array name */
+ freesymtab(ap);
+ dprintf( ("split: s=|%s|, a=%s, sep=|%s|\n", s, NN(ap->nval), fs) );
+ ap->tval &= ~STR;
+ ap->tval |= ARR;
+ ap->sval = (char *) makesymtab(NSYMTAB);
+
+ n = 0;
+ if (arg3type == REGEXPR && strlen((char*)((fa*)a[2])->restr) == 0) {
+ /* split(s, a, //); have to arrange that it looks like empty sep */
+ arg3type = 0;
+ fs = "";
+ sep = 0;
+ }
+ if (*s != '\0' && (strlen(fs) > 1 || arg3type == REGEXPR)) { /* reg expr */
+ fa *pfa;
+ if (arg3type == REGEXPR) { /* it's ready already */
+ pfa = (fa *) a[2];
+ } else {
+ pfa = makedfa(fs, 1);
+ }
+ if (nematch(pfa,s)) {
+ tempstat = pfa->initstat;
+ pfa->initstat = 2;
+ do {
+ n++;
+ snprintf(num, sizeof num, "%d", n);
+ temp = *patbeg;
+ *patbeg = '\0';
+ if (is_number(s))
+ setsymtab(num, s, atof(s), STR|NUM, (Array *) ap->sval);
+ else
+ setsymtab(num, s, 0.0, STR, (Array *) ap->sval);
+ *patbeg = temp;
+ s = patbeg + patlen;
+ if (*(patbeg+patlen-1) == 0 || *s == 0) {
+ n++;
+ snprintf(num, sizeof num, "%d", n);
+ setsymtab(num, "", 0.0, STR, (Array *) ap->sval);
+ pfa->initstat = tempstat;
+ goto spdone;
+ }
+ } while (nematch(pfa,s));
+ pfa->initstat = tempstat; /* bwk: has to be here to reset */
+ /* cf gsub and refldbld */
+ }
+ n++;
+ snprintf(num, sizeof num, "%d", n);
+ if (is_number(s))
+ setsymtab(num, s, atof(s), STR|NUM, (Array *) ap->sval);
+ else
+ setsymtab(num, s, 0.0, STR, (Array *) ap->sval);
+ spdone:
+ pfa = NULL;
+ } else if (sep == ' ') {
+ for (n = 0; ; ) {
+ while (*s == ' ' || *s == '\t' || *s == '\n')
+ s++;
+ if (*s == 0)
+ break;
+ n++;
+ t = s;
+ do
+ s++;
+ while (*s!=' ' && *s!='\t' && *s!='\n' && *s!='\0');
+ temp = *s;
+ *s = '\0';
+ snprintf(num, sizeof num, "%d", n);
+ if (is_number(t))
+ setsymtab(num, t, atof(t), STR|NUM, (Array *) ap->sval);
+ else
+ setsymtab(num, t, 0.0, STR, (Array *) ap->sval);
+ *s = temp;
+ if (*s != 0)
+ s++;
+ }
+ } else if (sep == 0) { /* new: split(s, a, "") => 1 char/elem */
+ for (n = 0; *s != 0; s++) {
+ char buf[2];
+ n++;
+ snprintf(num, sizeof num, "%d", n);
+ buf[0] = *s;
+ buf[1] = 0;
+ if (isdigit((uschar)buf[0]))
+ setsymtab(num, buf, atof(buf), STR|NUM, (Array *) ap->sval);
+ else
+ setsymtab(num, buf, 0.0, STR, (Array *) ap->sval);
+ }
+ } else if (*s != 0) {
+ for (;;) {
+ n++;
+ t = s;
+ while (*s != sep && *s != '\n' && *s != '\0')
+ s++;
+ temp = *s;
+ *s = '\0';
+ snprintf(num, sizeof num, "%d", n);
+ if (is_number(t))
+ setsymtab(num, t, atof(t), STR|NUM, (Array *) ap->sval);
+ else
+ setsymtab(num, t, 0.0, STR, (Array *) ap->sval);
+ *s = temp;
+ if (*s++ == 0)
+ break;
+ }
+ }
+ tempfree(ap);
+ tempfree(y);
+ if (a[2] != 0 && arg3type == STRING) {
+ tempfree(x);
+ }
+ x = gettemp();
+ x->tval = NUM;
+ x->fval = n;
+ return(x);
+}
+
+Cell *condexpr(Node **a, int n) /* a[0] ? a[1] : a[2] */
+{
+ Cell *x;
+
+ x = execute(a[0]);
+ if (istrue(x)) {
+ tempfree(x);
+ x = execute(a[1]);
+ } else {
+ tempfree(x);
+ x = execute(a[2]);
+ }
+ return(x);
+}
+
+Cell *ifstat(Node **a, int n) /* if (a[0]) a[1]; else a[2] */
+{
+ Cell *x;
+
+ x = execute(a[0]);
+ if (istrue(x)) {
+ tempfree(x);
+ x = execute(a[1]);
+ } else if (a[2] != 0) {
+ tempfree(x);
+ x = execute(a[2]);
+ }
+ return(x);
+}
+
+Cell *whilestat(Node **a, int n) /* while (a[0]) a[1] */
+{
+ Cell *x;
+
+ for (;;) {
+ x = execute(a[0]);
+ if (!istrue(x))
+ return(x);
+ tempfree(x);
+ x = execute(a[1]);
+ if (isbreak(x)) {
+ x = True;
+ return(x);
+ }
+ if (isnext(x) || isexit(x) || isret(x))
+ return(x);
+ tempfree(x);
+ }
+}
+
+Cell *dostat(Node **a, int n) /* do a[0]; while(a[1]) */
+{
+ Cell *x;
+
+ for (;;) {
+ x = execute(a[0]);
+ if (isbreak(x))
+ return True;
+ if (isnext(x) || isexit(x) || isret(x))
+ return(x);
+ tempfree(x);
+ x = execute(a[1]);
+ if (!istrue(x))
+ return(x);
+ tempfree(x);
+ }
+}
+
+Cell *forstat(Node **a, int n) /* for (a[0]; a[1]; a[2]) a[3] */
+{
+ Cell *x;
+
+ x = execute(a[0]);
+ tempfree(x);
+ for (;;) {
+ if (a[1]!=0) {
+ x = execute(a[1]);
+ if (!istrue(x)) return(x);
+ else tempfree(x);
+ }
+ x = execute(a[3]);
+ if (isbreak(x)) /* turn off break */
+ return True;
+ if (isnext(x) || isexit(x) || isret(x))
+ return(x);
+ tempfree(x);
+ x = execute(a[2]);
+ tempfree(x);
+ }
+}
+
+Cell *instat(Node **a, int n) /* for (a[0] in a[1]) a[2] */
+{
+ Cell *x, *vp, *arrayp, *cp, *ncp;
+ Array *tp;
+ int i;
+
+ vp = execute(a[0]);
+ arrayp = execute(a[1]);
+ if (!isarr(arrayp)) {
+ return True;
+ }
+ tp = (Array *) arrayp->sval;
+ tempfree(arrayp);
+ for (i = 0; i < tp->size; i++) { /* this routine knows too much */
+ for (cp = tp->tab[i]; cp != NULL; cp = ncp) {
+ setsval(vp, cp->nval);
+ ncp = cp->cnext;
+ x = execute(a[2]);
+ if (isbreak(x)) {
+ tempfree(vp);
+ return True;
+ }
+ if (isnext(x) || isexit(x) || isret(x)) {
+ tempfree(vp);
+ return(x);
+ }
+ tempfree(x);
+ }
+ }
+ return True;
+}
+
+Cell *bltin(Node **a, int n) /* builtin functions. a[0] is type, a[1] is arg list */
+{
+ Cell *x, *y;
+ Awkfloat u;
+ int t;
+ Awkfloat tmp;
+ char *p, *buf;
+ Node *nextarg;
+ FILE *fp;
+
+ t = ptoi(a[0]);
+ x = execute(a[1]);
+ nextarg = a[1]->nnext;
+ switch (t) {
+ case FLENGTH:
+ if (isarr(x))
+ u = ((Array *) x->sval)->nelem; /* GROT. should be function*/
+ else
+ u = strlen(getsval(x));
+ break;
+ case FLOG:
+ u = errcheck(log(getfval(x)), "log"); break;
+ case FINT:
+ modf(getfval(x), &u); break;
+ case FEXP:
+ u = errcheck(exp(getfval(x)), "exp"); break;
+ case FSQRT:
+ u = errcheck(sqrt(getfval(x)), "sqrt"); break;
+ case FSIN:
+ u = sin(getfval(x)); break;
+ case FCOS:
+ u = cos(getfval(x)); break;
+ case FATAN:
+ if (nextarg == 0) {
+ WARNING("atan2 requires two arguments; returning 1.0");
+ u = 1.0;
+ } else {
+ y = execute(a[1]->nnext);
+ u = atan2(getfval(x), getfval(y));
+ tempfree(y);
+ nextarg = nextarg->nnext;
+ }
+ break;
+ case FCOMPL:
+ u = ~((int)getfval(x));
+ break;
+ case FAND:
+ if (nextarg == 0) {
+ WARNING("and requires two arguments; returning 0");
+ u = 0;
+ break;
+ }
+ y = execute(a[1]->nnext);
+ u = ((int)getfval(x)) & ((int)getfval(y));
+ tempfree(y);
+ nextarg = nextarg->nnext;
+ break;
+ case FFOR:
+ if (nextarg == 0) {
+ WARNING("or requires two arguments; returning 0");
+ u = 0;
+ break;
+ }
+ y = execute(a[1]->nnext);
+ u = ((int)getfval(x)) | ((int)getfval(y));
+ tempfree(y);
+ nextarg = nextarg->nnext;
+ break;
+ case FXOR:
+ if (nextarg == 0) {
+ WARNING("or requires two arguments; returning 0");
+ u = 0;
+ break;
+ }
+ y = execute(a[1]->nnext);
+ u = ((int)getfval(x)) ^ ((int)getfval(y));
+ tempfree(y);
+ nextarg = nextarg->nnext;
+ break;
+ case FLSHIFT:
+ if (nextarg == 0) {
+ WARNING("or requires two arguments; returning 0");
+ u = 0;
+ break;
+ }
+ y = execute(a[1]->nnext);
+ u = ((int)getfval(x)) << ((int)getfval(y));
+ tempfree(y);
+ nextarg = nextarg->nnext;
+ break;
+ case FRSHIFT:
+ if (nextarg == 0) {
+ WARNING("or requires two arguments; returning 0");
+ u = 0;
+ break;
+ }
+ y = execute(a[1]->nnext);
+ u = ((int)getfval(x)) >> ((int)getfval(y));
+ tempfree(y);
+ nextarg = nextarg->nnext;
+ break;
+ case FSYSTEM:
+ fflush(stdout); /* in case something is buffered already */
+ u = (Awkfloat) system(getsval(x)) / 256; /* 256 is unix-dep */
+ break;
+ case FRAND:
+ if (use_srandom)
+ u = (Awkfloat) (random() % RAND_MAX) / RAND_MAX;
+ else
+ u = (Awkfloat)arc4random() / 0xffffffff;
+ break;
+ case FSRAND:
+ if (isrec(x)) /* no argument provided, want arc4random() */
+ use_srandom = 0;
+ else {
+ use_srandom = 1;
+ u = getfval(x);
+ tmp = u;
+ srandom((unsigned int) u);
+ u = srand_seed;
+ srand_seed = tmp;
+ }
+ break;
+ case FTOUPPER:
+ case FTOLOWER:
+ buf = tostring(getsval(x));
+ if (t == FTOUPPER) {
+ for (p = buf; *p; p++)
+ if (islower((uschar) *p))
+ *p = toupper((uschar)*p);
+ } else {
+ for (p = buf; *p; p++)
+ if (isupper((uschar) *p))
+ *p = tolower((uschar)*p);
+ }
+ tempfree(x);
+ x = gettemp();
+ setsval(x, buf);
+ free(buf);
+ return x;
+ case FFLUSH:
+ if (isrec(x) || strlen(getsval(x)) == 0) {
+ flush_all(); /* fflush() or fflush("") -> all */
+ u = 0;
+ } else if ((fp = openfile(FFLUSH, getsval(x))) == NULL)
+ u = EOF;
+ else
+ u = fflush(fp);
+ break;
+ default: /* can't happen */
+ FATAL("illegal function type %d", t);
+ break;
+ }
+ tempfree(x);
+ x = gettemp();
+ setfval(x, u);
+ if (nextarg != 0) {
+ WARNING("warning: function has too many arguments");
+ for ( ; nextarg; nextarg = nextarg->nnext)
+ execute(nextarg);
+ }
+ return(x);
+}
+
+Cell *printstat(Node **a, int n) /* print a[0] */
+{
+ Node *x;
+ Cell *y;
+ FILE *fp;
+
+ if (a[1] == 0) /* a[1] is redirection operator, a[2] is file */
+ fp = stdout;
+ else
+ fp = redirect(ptoi(a[1]), a[2]);
+ for (x = a[0]; x != NULL; x = x->nnext) {
+ y = execute(x);
+ fputs(getpssval(y), fp);
+ tempfree(y);
+ if (x->nnext == NULL)
+ fputs(*ORS, fp);
+ else
+ fputs(*OFS, fp);
+ }
+ if (a[1] != 0)
+ fflush(fp);
+ if (ferror(fp))
+ FATAL("write error on %s", filename(fp));
+ return(True);
+}
+
+Cell *nullproc(Node **a, int n)
+{
+ n = n;
+ a = a;
+ return 0;
+}
+
+
+FILE *redirect(int a, Node *b) /* set up all i/o redirections */
+{
+ FILE *fp;
+ Cell *x;
+ char *fname;
+
+ x = execute(b);
+ fname = getsval(x);
+ fp = openfile(a, fname);
+ if (fp == NULL)
+ FATAL("can't open file %s", fname);
+ tempfree(x);
+ return fp;
+}
+
+struct files {
+ FILE *fp;
+ const char *fname;
+ int mode; /* '|', 'a', 'w' => LE/LT, GT */
+} *files;
+
+int nfiles;
+
+void stdinit(void) /* in case stdin, etc., are not constants */
+{
+ nfiles = FOPEN_MAX;
+ files = calloc(nfiles, sizeof(*files));
+ if (files == NULL)
+ FATAL("can't allocate file memory for %u files", nfiles);
+ files[0].fp = stdin;
+ files[0].fname = "/dev/stdin";
+ files[0].mode = LT;
+ files[1].fp = stdout;
+ files[1].fname = "/dev/stdout";
+ files[1].mode = GT;
+ files[2].fp = stderr;
+ files[2].fname = "/dev/stderr";
+ files[2].mode = GT;
+}
+
+FILE *openfile(int a, const char *us)
+{
+ const char *s = us;
+ int i, m;
+ FILE *fp = 0;
+
+ if (*s == '\0')
+ FATAL("null file name in print or getline");
+ for (i=0; i < nfiles; i++)
+ if (files[i].fname && strcmp(s, files[i].fname) == 0) {
+ if (a == files[i].mode || (a==APPEND && files[i].mode==GT))
+ return files[i].fp;
+ if (a == FFLUSH)
+ return files[i].fp;
+ }
+ if (a == FFLUSH) /* didn't find it, so don't create it! */
+ return NULL;
+
+ for (i=0; i < nfiles; i++)
+ if (files[i].fp == 0)
+ break;
+ if (i >= nfiles) {
+ struct files *nf;
+ int nnf = nfiles + FOPEN_MAX;
+ nf = reallocarray(files, nnf, sizeof(*nf));
+ if (nf == NULL)
+ FATAL("cannot grow files for %s and %d files", s, nnf);
+ memset(&nf[nfiles], 0, FOPEN_MAX * sizeof(*nf));
+ nfiles = nnf;
+ files = nf;
+ }
+ fflush(stdout); /* force a semblance of order */
+ m = a;
+ if (a == GT) {
+ fp = fopen(s, "w");
+ } else if (a == APPEND) {
+ fp = fopen(s, "a");
+ m = GT; /* so can mix > and >> */
+ } else if (a == '|') { /* output pipe */
+ fp = popen(s, "w");
+ } else if (a == LE) { /* input pipe */
+ fp = popen(s, "r");
+ } else if (a == LT) { /* getline <file */
+ fp = strcmp(s, "-") == 0 ? stdin : fopen(s, "r"); /* "-" is stdin */
+ } else /* can't happen */
+ FATAL("illegal redirection %d", a);
+ if (fp != NULL) {
+ files[i].fname = tostring(s);
+ files[i].fp = fp;
+ files[i].mode = m;
+ }
+ return fp;
+}
+
+const char *filename(FILE *fp)
+{
+ int i;
+
+ for (i = 0; i < nfiles; i++)
+ if (fp == files[i].fp)
+ return files[i].fname;
+ return "???";
+}
+
+Cell *closefile(Node **a, int n)
+{
+ Cell *x;
+ int i, stat;
+
+ n = n;
+ x = execute(a[0]);
+ getsval(x);
+ stat = -1;
+ for (i = 0; i < nfiles; i++) {
+ if (files[i].fname && strcmp(x->sval, files[i].fname) == 0) {
+ if (ferror(files[i].fp))
+ WARNING( "i/o error occurred on %s", files[i].fname );
+ if (files[i].mode == '|' || files[i].mode == LE)
+ stat = pclose(files[i].fp);
+ else
+ stat = fclose(files[i].fp);
+ if (stat == EOF)
+ WARNING( "i/o error occurred closing %s", files[i].fname );
+ if (i > 2) /* don't do /dev/std... */
+ xfree(files[i].fname);
+ files[i].fname = NULL; /* watch out for ref thru this */
+ files[i].fp = NULL;
+ }
+ }
+ tempfree(x);
+ x = gettemp();
+ setfval(x, (Awkfloat) stat);
+ return(x);
+}
+
+void closeall(void)
+{
+ int i, stat;
+
+ for (i = 0; i < FOPEN_MAX; i++) {
+ if (files[i].fp) {
+ if (ferror(files[i].fp))
+ WARNING( "i/o error occurred on %s", files[i].fname );
+ if (files[i].mode == '|' || files[i].mode == LE)
+ stat = pclose(files[i].fp);
+ else
+ stat = fclose(files[i].fp);
+ if (stat == EOF)
+ WARNING( "i/o error occurred while closing %s", files[i].fname );
+ }
+ }
+}
+
+void flush_all(void)
+{
+ int i;
+
+ for (i = 0; i < nfiles; i++)
+ if (files[i].fp)
+ fflush(files[i].fp);
+}
+
+void backsub(char **pb_ptr, char **sptr_ptr);
+
+Cell *sub(Node **a, int nnn) /* substitute command */
+{
+ char *sptr, *pb, *q;
+ Cell *x, *y, *result;
+ char *t, *buf;
+ fa *pfa;
+ int bufsz = recsize;
+
+ if ((buf = (char *) malloc(bufsz)) == NULL)
+ FATAL("out of memory in sub");
+ x = execute(a[3]); /* target string */
+ t = getsval(x);
+ if (a[0] == 0) /* 0 => a[1] is already-compiled regexpr */
+ pfa = (fa *) a[1]; /* regular expression */
+ else {
+ y = execute(a[1]);
+ pfa = makedfa(getsval(y), 1);
+ tempfree(y);
+ }
+ y = execute(a[2]); /* replacement string */
+ result = False;
+ if (pmatch(pfa, t)) {
+ sptr = t;
+ adjbuf(&buf, &bufsz, 1+patbeg-sptr, recsize, 0, "sub");
+ pb = buf;
+ while (sptr < patbeg)
+ *pb++ = *sptr++;
+ sptr = getsval(y);
+ while (*sptr != 0) {
+ adjbuf(&buf, &bufsz, 5+pb-buf, recsize, &pb, "sub");
+ if (*sptr == '\\') {
+ backsub(&pb, &sptr);
+ } else if (*sptr == '&') {
+ sptr++;
+ adjbuf(&buf, &bufsz, 1+patlen+pb-buf, recsize, &pb, "sub");
+ for (q = patbeg; q < patbeg+patlen; )
+ *pb++ = *q++;
+ } else
+ *pb++ = *sptr++;
+ }
+ *pb = '\0';
+ if (pb > buf + bufsz)
+ FATAL("sub result1 %.30s too big; can't happen", buf);
+ sptr = patbeg + patlen;
+ if ((patlen == 0 && *patbeg) || (patlen && *(sptr-1))) {
+ adjbuf(&buf, &bufsz, 1+strlen(sptr)+pb-buf, 0, &pb, "sub");
+ while ((*pb++ = *sptr++) != 0)
+ ;
+ }
+ if (pb > buf + bufsz)
+ FATAL("sub result2 %.30s too big; can't happen", buf);
+ setsval(x, buf); /* BUG: should be able to avoid copy */
+ result = True;
+ }
+ tempfree(x);
+ tempfree(y);
+ free(buf);
+ return result;
+}
+
+Cell *gsub(Node **a, int nnn) /* global substitute */
+{
+ Cell *x, *y;
+ char *rptr, *sptr, *t, *pb, *q;
+ char *buf;
+ fa *pfa;
+ int mflag, tempstat, num;
+ int bufsz = recsize;
+
+ if ((buf = (char *) malloc(bufsz)) == NULL)
+ FATAL("out of memory in gsub");
+ mflag = 0; /* if mflag == 0, can replace empty string */
+ num = 0;
+ x = execute(a[3]); /* target string */
+ t = getsval(x);
+ if (a[0] == 0) /* 0 => a[1] is already-compiled regexpr */
+ pfa = (fa *) a[1]; /* regular expression */
+ else {
+ y = execute(a[1]);
+ pfa = makedfa(getsval(y), 1);
+ tempfree(y);
+ }
+ y = execute(a[2]); /* replacement string */
+ if (pmatch(pfa, t)) {
+ tempstat = pfa->initstat;
+ pfa->initstat = 2;
+ pb = buf;
+ rptr = getsval(y);
+ do {
+ if (patlen == 0 && *patbeg != 0) { /* matched empty string */
+ if (mflag == 0) { /* can replace empty */
+ num++;
+ sptr = rptr;
+ while (*sptr != 0) {
+ adjbuf(&buf, &bufsz, 5+pb-buf, recsize, &pb, "gsub");
+ if (*sptr == '\\') {
+ backsub(&pb, &sptr);
+ } else if (*sptr == '&') {
+ sptr++;
+ adjbuf(&buf, &bufsz, 1+patlen+pb-buf, recsize, &pb, "gsub");
+ for (q = patbeg; q < patbeg+patlen; )
+ *pb++ = *q++;
+ } else
+ *pb++ = *sptr++;
+ }
+ }
+ if (*t == 0) /* at end */
+ goto done;
+ adjbuf(&buf, &bufsz, 2+pb-buf, recsize, &pb, "gsub");
+ *pb++ = *t++;
+ if (pb > buf + bufsz) /* BUG: not sure of this test */
+ FATAL("gsub result0 %.30s too big; can't happen", buf);
+ mflag = 0;
+ }
+ else { /* matched nonempty string */
+ num++;
+ sptr = t;
+ adjbuf(&buf, &bufsz, 1+(patbeg-sptr)+pb-buf, recsize, &pb, "gsub");
+ while (sptr < patbeg)
+ *pb++ = *sptr++;
+ sptr = rptr;
+ while (*sptr != 0) {
+ adjbuf(&buf, &bufsz, 5+pb-buf, recsize, &pb, "gsub");
+ if (*sptr == '\\') {
+ backsub(&pb, &sptr);
+ } else if (*sptr == '&') {
+ sptr++;
+ adjbuf(&buf, &bufsz, 1+patlen+pb-buf, recsize, &pb, "gsub");
+ for (q = patbeg; q < patbeg+patlen; )
+ *pb++ = *q++;
+ } else
+ *pb++ = *sptr++;
+ }
+ t = patbeg + patlen;
+ if (patlen == 0 || *t == 0 || *(t-1) == 0)
+ goto done;
+ if (pb > buf + bufsz)
+ FATAL("gsub result1 %.30s too big; can't happen", buf);
+ mflag = 1;
+ }
+ } while (pmatch(pfa,t));
+ sptr = t;
+ adjbuf(&buf, &bufsz, 1+strlen(sptr)+pb-buf, 0, &pb, "gsub");
+ while ((*pb++ = *sptr++) != 0)
+ ;
+ done: if (pb < buf + bufsz)
+ *pb = '\0';
+ else if (*(pb-1) != '\0')
+ FATAL("gsub result2 %.30s truncated; can't happen", buf);
+ setsval(x, buf); /* BUG: should be able to avoid copy + free */
+ pfa->initstat = tempstat;
+ }
+ tempfree(x);
+ tempfree(y);
+ x = gettemp();
+ x->tval = NUM;
+ x->fval = num;
+ free(buf);
+ return(x);
+}
+
+void backsub(char **pb_ptr, char **sptr_ptr) /* handle \\& variations */
+{ /* sptr[0] == '\\' */
+ char *pb = *pb_ptr, *sptr = *sptr_ptr;
+
+ if (sptr[1] == '\\') {
+ if (sptr[2] == '\\' && sptr[3] == '&') { /* \\\& -> \& */
+ *pb++ = '\\';
+ *pb++ = '&';
+ sptr += 4;
+ } else if (sptr[2] == '&') { /* \\& -> \ + matched */
+ *pb++ = '\\';
+ sptr += 2;
+ } else { /* \\x -> \\x */
+ *pb++ = *sptr++;
+ *pb++ = *sptr++;
+ }
+ } else if (sptr[1] == '&') { /* literal & */
+ sptr++;
+ *pb++ = *sptr++;
+ } else /* literal \ */
+ *pb++ = *sptr++;
+
+ *pb_ptr = pb;
+ *sptr_ptr = sptr;
+}
diff --git a/awk/strlcat.c b/awk/strlcat.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+#include <sys/types.h>
+
+#include "util.h"
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+ return(dlen + (s - src)); /* count does not include NUL */
+}
diff --git a/awk/strlcpy.c b/awk/strlcpy.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+#include <sys/types.h>
+
+#include "util.h"
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0')
+ break;
+ }
+ }
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+ return(s - src - 1); /* count does not include NUL */
+}
diff --git a/awk/tran.c b/awk/tran.c
@@ -0,0 +1,458 @@
+/* $OpenBSD: tran.c,v 1.15 2011/09/28 19:27:18 millert Exp $ */
+/****************************************************************
+Copyright (C) Lucent Technologies 1997
+All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and
+its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that the copyright notice and this
+permission notice and warranty disclaimer appear in supporting
+documentation, and that the name Lucent Technologies or any of
+its entities not be used in advertising or publicity pertaining
+to distribution of the software without specific, written prior
+permission.
+
+LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
+SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+****************************************************************/
+
+#define DEBUG
+#include <stdio.h>
+#include <math.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include "awk.h"
+#include "ytab.h"
+
+#define FULLTAB 2 /* rehash when table gets this x full */
+#define GROWTAB 4 /* grow table by this factor */
+
+Array *symtab; /* main symbol table */
+
+char **FS; /* initial field sep */
+char **RS; /* initial record sep */
+char **OFS; /* output field sep */
+char **ORS; /* output record sep */
+char **OFMT; /* output format for numbers */
+char **CONVFMT; /* format for conversions in getsval */
+Awkfloat *NF; /* number of fields in current record */
+Awkfloat *NR; /* number of current record */
+Awkfloat *FNR; /* number of current record in current file */
+char **FILENAME; /* current filename argument */
+Awkfloat *ARGC; /* number of arguments from command line */
+char **SUBSEP; /* subscript separator for a[i,j,k]; default \034 */
+Awkfloat *RSTART; /* start of re matched with ~; origin 1 (!) */
+Awkfloat *RLENGTH; /* length of same */
+
+Cell *fsloc; /* FS */
+Cell *nrloc; /* NR */
+Cell *nfloc; /* NF */
+Cell *fnrloc; /* FNR */
+Array *ARGVtab; /* symbol table containing ARGV[...] */
+Array *ENVtab; /* symbol table containing ENVIRON[...] */
+Cell *rstartloc; /* RSTART */
+Cell *rlengthloc; /* RLENGTH */
+Cell *symtabloc; /* SYMTAB */
+
+Cell *nullloc; /* a guaranteed empty cell */
+Node *nullnode; /* zero&null, converted into a node for comparisons */
+Cell *literal0;
+
+extern Cell **fldtab;
+
+void syminit(void) /* initialize symbol table with builtin vars */
+{
+ literal0 = setsymtab("0", "0", 0.0, NUM|STR|CON|DONTFREE, symtab);
+ /* this is used for if(x)... tests: */
+ nullloc = setsymtab("$zero&null", "", 0.0, NUM|STR|CON|DONTFREE, symtab);
+ nullnode = celltonode(nullloc, CCON);
+
+ fsloc = setsymtab("FS", " ", 0.0, STR|DONTFREE, symtab);
+ FS = &fsloc->sval;
+ RS = &setsymtab("RS", "\n", 0.0, STR|DONTFREE, symtab)->sval;
+ OFS = &setsymtab("OFS", " ", 0.0, STR|DONTFREE, symtab)->sval;
+ ORS = &setsymtab("ORS", "\n", 0.0, STR|DONTFREE, symtab)->sval;
+ OFMT = &setsymtab("OFMT", "%.6g", 0.0, STR|DONTFREE, symtab)->sval;
+ CONVFMT = &setsymtab("CONVFMT", "%.6g", 0.0, STR|DONTFREE, symtab)->sval;
+ FILENAME = &setsymtab("FILENAME", "", 0.0, STR|DONTFREE, symtab)->sval;
+ nfloc = setsymtab("NF", "", 0.0, NUM, symtab);
+ NF = &nfloc->fval;
+ nrloc = setsymtab("NR", "", 0.0, NUM, symtab);
+ NR = &nrloc->fval;
+ fnrloc = setsymtab("FNR", "", 0.0, NUM, symtab);
+ FNR = &fnrloc->fval;
+ SUBSEP = &setsymtab("SUBSEP", "\034", 0.0, STR|DONTFREE, symtab)->sval;
+ rstartloc = setsymtab("RSTART", "", 0.0, NUM, symtab);
+ RSTART = &rstartloc->fval;
+ rlengthloc = setsymtab("RLENGTH", "", 0.0, NUM, symtab);
+ RLENGTH = &rlengthloc->fval;
+ symtabloc = setsymtab("SYMTAB", "", 0.0, ARR, symtab);
+ symtabloc->sval = (char *) symtab;
+}
+
+void arginit(int ac, char **av) /* set up ARGV and ARGC */
+{
+ Cell *cp;
+ int i;
+ char temp[50];
+
+ ARGC = &setsymtab("ARGC", "", (Awkfloat) ac, NUM, symtab)->fval;
+ cp = setsymtab("ARGV", "", 0.0, ARR, symtab);
+ ARGVtab = makesymtab(NSYMTAB); /* could be (int) ARGC as well */
+ cp->sval = (char *) ARGVtab;
+ for (i = 0; i < ac; i++) {
+ snprintf(temp, sizeof temp, "%d", i);
+ if (is_number(*av))
+ setsymtab(temp, *av, atof(*av), STR|NUM, ARGVtab);
+ else
+ setsymtab(temp, *av, 0.0, STR, ARGVtab);
+ av++;
+ }
+}
+
+void envinit(char **envp) /* set up ENVIRON variable */
+{
+ Cell *cp;
+ char *p;
+
+ cp = setsymtab("ENVIRON", "", 0.0, ARR, symtab);
+ ENVtab = makesymtab(NSYMTAB);
+ cp->sval = (char *) ENVtab;
+ for ( ; *envp; envp++) {
+ if ((p = strchr(*envp, '=')) == NULL)
+ continue;
+ if( p == *envp ) /* no left hand side name in env string */
+ continue;
+ *p++ = 0; /* split into two strings at = */
+ if (is_number(p))
+ setsymtab(*envp, p, atof(p), STR|NUM, ENVtab);
+ else
+ setsymtab(*envp, p, 0.0, STR, ENVtab);
+ p[-1] = '='; /* restore in case env is passed down to a shell */
+ }
+}
+
+Array *makesymtab(int n) /* make a new symbol table */
+{
+ Array *ap;
+ Cell **tp;
+
+ ap = (Array *) malloc(sizeof(Array));
+ tp = (Cell **) calloc(n, sizeof(Cell *));
+ if (ap == NULL || tp == NULL)
+ FATAL("out of space in makesymtab");
+ ap->nelem = 0;
+ ap->size = n;
+ ap->tab = tp;
+ return(ap);
+}
+
+void freesymtab(Cell *ap) /* free a symbol table */
+{
+ Cell *cp, *temp;
+ Array *tp;
+ int i;
+
+ if (!isarr(ap))
+ return;
+ tp = (Array *) ap->sval;
+ if (tp == NULL)
+ return;
+ for (i = 0; i < tp->size; i++) {
+ for (cp = tp->tab[i]; cp != NULL; cp = temp) {
+ xfree(cp->nval);
+ if (freeable(cp))
+ xfree(cp->sval);
+ temp = cp->cnext; /* avoids freeing then using */
+ free(cp);
+ tp->nelem--;
+ }
+ tp->tab[i] = 0;
+ }
+ if (tp->nelem != 0)
+ WARNING("can't happen: inconsistent element count freeing %s", ap->nval);
+ free(tp->tab);
+ free(tp);
+}
+
+void freeelem(Cell *ap, const char *s) /* free elem s from ap (i.e., ap["s"] */
+{
+ Array *tp;
+ Cell *p, *prev = NULL;
+ int h;
+
+ tp = (Array *) ap->sval;
+ h = hash(s, tp->size);
+ for (p = tp->tab[h]; p != NULL; prev = p, p = p->cnext)
+ if (strcmp(s, p->nval) == 0) {
+ if (prev == NULL) /* 1st one */
+ tp->tab[h] = p->cnext;
+ else /* middle somewhere */
+ prev->cnext = p->cnext;
+ if (freeable(p))
+ xfree(p->sval);
+ free(p->nval);
+ free(p);
+ tp->nelem--;
+ return;
+ }
+}
+
+Cell *setsymtab(const char *n, const char *s, Awkfloat f, unsigned t, Array *tp)
+{
+ int h;
+ Cell *p;
+
+ if (n != NULL && (p = lookup(n, tp)) != NULL) {
+ dprintf( ("setsymtab found %p: n=%s s=\"%s\" f=%g t=%o\n",
+ (void*)p, NN(p->nval), NN(p->sval), p->fval, p->tval) );
+ return(p);
+ }
+ p = (Cell *) malloc(sizeof(Cell));
+ if (p == NULL)
+ FATAL("out of space for symbol table at %s", n);
+ p->nval = tostring(n);
+ p->sval = s ? tostring(s) : tostring("");
+ p->fval = f;
+ p->tval = t;
+ p->csub = CUNK;
+ p->ctype = OCELL;
+ tp->nelem++;
+ if (tp->nelem > FULLTAB * tp->size)
+ rehash(tp);
+ h = hash(n, tp->size);
+ p->cnext = tp->tab[h];
+ tp->tab[h] = p;
+ dprintf( ("setsymtab set %p: n=%s s=\"%s\" f=%g t=%o\n",
+ (void*)p, p->nval, p->sval, p->fval, p->tval) );
+ return(p);
+}
+
+int hash(const char *s, int n) /* form hash value for string s */
+{
+ unsigned hashval;
+
+ for (hashval = 0; *s != '\0'; s++)
+ hashval = (*s + 31 * hashval);
+ return hashval % n;
+}
+
+void rehash(Array *tp) /* rehash items in small table into big one */
+{
+ int i, nh, nsz;
+ Cell *cp, *op, **np;
+
+ nsz = GROWTAB * tp->size;
+ np = (Cell **) calloc(nsz, sizeof(Cell *));
+ if (np == NULL) /* can't do it, but can keep running. */
+ return; /* someone else will run out later. */
+ for (i = 0; i < tp->size; i++) {
+ for (cp = tp->tab[i]; cp; cp = op) {
+ op = cp->cnext;
+ nh = hash(cp->nval, nsz);
+ cp->cnext = np[nh];
+ np[nh] = cp;
+ }
+ }
+ free(tp->tab);
+ tp->tab = np;
+ tp->size = nsz;
+}
+
+Cell *lookup(const char *s, Array *tp) /* look for s in tp */
+{
+ Cell *p;
+ int h;
+
+ h = hash(s, tp->size);
+ for (p = tp->tab[h]; p != NULL; p = p->cnext)
+ if (strcmp(s, p->nval) == 0)
+ return(p); /* found it */
+ return(NULL); /* not found */
+}
+
+Awkfloat setfval(Cell *vp, Awkfloat f) /* set float val of a Cell */
+{
+ int fldno;
+
+ if ((vp->tval & (NUM | STR)) == 0)
+ funnyvar(vp, "assign to");
+ if (isfld(vp)) {
+ donerec = 0; /* mark $0 invalid */
+ fldno = atoi(vp->nval);
+ if (fldno > *NF)
+ newfld(fldno);
+ dprintf( ("setting field %d to %g\n", fldno, f) );
+ } else if (isrec(vp)) {
+ donefld = 0; /* mark $1... invalid */
+ donerec = 1;
+ }
+ if (freeable(vp))
+ xfree(vp->sval); /* free any previous string */
+ vp->tval &= ~STR; /* mark string invalid */
+ vp->tval |= NUM; /* mark number ok */
+ dprintf( ("setfval %p: %s = %g, t=%o\n", (void*)vp, NN(vp->nval), f, vp->tval) );
+ return vp->fval = f;
+}
+
+void funnyvar(Cell *vp, const char *rw)
+{
+ if (isarr(vp))
+ FATAL("can't %s %s; it's an array name.", rw, vp->nval);
+ if (vp->tval & FCN)
+ FATAL("can't %s %s; it's a function.", rw, vp->nval);
+ WARNING("funny variable %p: n=%s s=\"%s\" f=%g t=%o",
+ vp, vp->nval, vp->sval, vp->fval, vp->tval);
+}
+
+char *setsval(Cell *vp, const char *s) /* set string val of a Cell */
+{
+ char *t;
+ int fldno;
+
+ dprintf( ("starting setsval %p: %s = \"%s\", t=%o, r,f=%d,%d\n",
+ (void*)vp, NN(vp->nval), s, vp->tval, donerec, donefld) );
+ if ((vp->tval & (NUM | STR)) == 0)
+ funnyvar(vp, "assign to");
+ if (isfld(vp)) {
+ donerec = 0; /* mark $0 invalid */
+ fldno = atoi(vp->nval);
+ if (fldno > *NF)
+ newfld(fldno);
+ dprintf( ("setting field %d to %s (%p)\n", fldno, s, s) );
+ } else if (isrec(vp)) {
+ donefld = 0; /* mark $1... invalid */
+ donerec = 1;
+ }
+ t = tostring(s); /* in case it's self-assign */
+ if (freeable(vp))
+ xfree(vp->sval);
+ vp->tval &= ~NUM;
+ vp->tval |= STR;
+ vp->tval &= ~DONTFREE;
+ dprintf( ("setsval %p: %s = \"%s (%p) \", t=%o r,f=%d,%d\n",
+ (void*)vp, NN(vp->nval), t,t, vp->tval, donerec, donefld) );
+ return(vp->sval = t);
+}
+
+Awkfloat getfval(Cell *vp) /* get float val of a Cell */
+{
+ if ((vp->tval & (NUM | STR)) == 0)
+ funnyvar(vp, "read value of");
+ if (isfld(vp) && donefld == 0)
+ fldbld();
+ else if (isrec(vp) && donerec == 0)
+ recbld();
+ if (!isnum(vp)) { /* not a number */
+ vp->fval = atof(vp->sval); /* best guess */
+ if (is_number(vp->sval) && !(vp->tval&CON))
+ vp->tval |= NUM; /* make NUM only sparingly */
+ }
+ dprintf( ("getfval %p: %s = %g, t=%o\n",
+ (void*)vp, NN(vp->nval), vp->fval, vp->tval) );
+ return(vp->fval);
+}
+
+static char *get_str_val(Cell *vp, char **fmt) /* get string val of a Cell */
+{
+ int n;
+ double dtemp;
+
+ if ((vp->tval & (NUM | STR)) == 0)
+ funnyvar(vp, "read value of");
+ if (isfld(vp) && donefld == 0)
+ fldbld();
+ else if (isrec(vp) && donerec == 0)
+ recbld();
+ if (isstr(vp) == 0) {
+ if (freeable(vp))
+ xfree(vp->sval);
+ if (modf(vp->fval, &dtemp) == 0) /* it's integral */
+ n = asprintf(&vp->sval, "%.30g", vp->fval);
+ else
+ n = asprintf(&vp->sval, *fmt, vp->fval);
+ if (n == -1)
+ FATAL("out of space in get_str_val");
+ vp->tval &= ~DONTFREE;
+ vp->tval |= STR;
+ }
+ dprintf( ("getsval %p: %s = \"%s (%p)\", t=%o\n",
+ (void*)vp, NN(vp->nval), vp->sval, vp->sval, vp->tval) );
+ return(vp->sval);
+}
+
+char *getsval(Cell *vp) /* get string val of a Cell */
+{
+ return get_str_val(vp, CONVFMT);
+}
+
+char *getpssval(Cell *vp) /* get string val of a Cell for print */
+{
+ return get_str_val(vp, OFMT);
+}
+
+
+char *tostring(const char *s) /* make a copy of string s */
+{
+ char *p;
+
+ p = strdup(s);
+ if (p == NULL)
+ FATAL("out of space in tostring on %s", s);
+ return p;
+}
+
+char *qstring(const char *is, int delim) /* collect string up to next delim */
+{
+ const char *os = is;
+ int c, n;
+ uschar *s = (uschar *) is;
+ uschar *buf, *bp;
+
+ if ((buf = (uschar *) malloc(strlen(is)+3)) == NULL)
+ FATAL( "out of space in qstring(%s)", s);
+ for (bp = buf; (c = *s) != delim; s++) {
+ if (c == '\n')
+ SYNTAX( "newline in string %.20s...", os );
+ else if (c != '\\')
+ *bp++ = c;
+ else { /* \something */
+ c = *++s;
+ if (c == 0) { /* \ at end */
+ *bp++ = '\\';
+ break; /* for loop */
+ }
+ switch (c) {
+ case '\\': *bp++ = '\\'; break;
+ case 'n': *bp++ = '\n'; break;
+ case 't': *bp++ = '\t'; break;
+ case 'b': *bp++ = '\b'; break;
+ case 'f': *bp++ = '\f'; break;
+ case 'r': *bp++ = '\r'; break;
+ default:
+ if (!isdigit(c)) {
+ *bp++ = c;
+ break;
+ }
+ n = c - '0';
+ if (isdigit(s[1])) {
+ n = 8 * n + *++s - '0';
+ if (isdigit(s[1]))
+ n = 8 * n + *++s - '0';
+ }
+ *bp++ = n;
+ break;
+ }
+ }
+ }
+ *bp++ = 0;
+ return (char *) buf;
+}
diff --git a/awk/util.h b/awk/util.h
@@ -0,0 +1,10 @@
+#include <sys/stat.h>
+
+#include <stdint.h>
+
+uint32_t arc4random(void);
+
+void *reallocarray(void *, size_t, size_t);
+
+size_t strlcat(char *, const char *, size_t);
+size_t strlcpy(char *, const char *, size_t);
diff --git a/sbase/LICENSE b/sbase/LICENSE
@@ -0,0 +1,57 @@
+MIT/X Consortium License
+
+© 2011 Connor Lane Smith <cls@lubutu.com>
+© 2011-2015 Dimitris Papastamos <sin@2f30.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+Authors/contributors include:
+
+© 2011 Kamil Cholewiński <harry666t@gmail.com>
+© 2011 Rob Pilling <robpilling@gmail.com>
+© 2011 Hiltjo Posthuma <hiltjo@codemadness.org>
+© 2011 pancake <pancake@youterm.com>
+© 2011 Random832 <random832@fastmail.us>
+© 2012 William Haddon <william@haddonthethird.net>
+© 2012 Kurt H. Maier <khm@intma.in>
+© 2012 Christoph Lohmann <20h@r-36.net>
+© 2012 David Galos <galosd83@students.rowan.edu>
+© 2012 Robert Ransom <rransom.8774@gmail.com>
+© 2013 Jakob Kramer <jakob.kramer@gmx.de>
+© 2013 Anselm R Garbe <anselm@garbe.us>
+© 2013 Truls Becken <truls.becken@gmail.com>
+© 2013 dsp <dsp@2f30.org>
+© 2013 Markus Teich <markus.teich@stusta.mhn.de>
+© 2013 Jesse Ogle <jesse.p.ogle@gmail.com>
+© 2013 Lorenzo Cogotti <miciamail@hotmail.it>
+© 2013 Federico G. Benavento <benavento@gmail.com>
+© 2013 Roberto E. Vargas Caballero <k0ga@shike2.com>
+© 2013 Christian Hesse <mail@eworm.de>
+© 2013 Markus Wichmann <nullplan@gmx.net>
+© 2014 Silvan Jegen <s.jegen@gmail.com>
+© 2014 Laslo Hunhold <dev@frign.de>
+© 2014 Daniel Bainton <dpb@driftaway.org>
+© 2014 Tuukka Kataja <stuge@xor.fi>
+© 2014 Jeffrey Picard <jeff@jeffreypicard.com>
+© 2014 Evan Gates <evan.gates@gmail.com>
+© 2014 Michael Forney <mforney@mforney.org>
+© 2014 Ari Malinen <ari.malinen@gmail.com>
+© 2014 Brandon Mulcahy <brandon@jangler.info>
+© 2014 Adria Garriga <rhaps0dy@installgentoo.com>
+© 2014 Greg Reagle <greg.reagle@umbc.edu>
diff --git a/sbase/Makefile b/sbase/Makefile
@@ -0,0 +1,205 @@
+include config.mk
+
+.SUFFIXES:
+.SUFFIXES: .o .c
+
+HDR =\
+ arg.h\
+ compat.h\
+ crypt.h\
+ fs.h\
+ md5.h\
+ queue.h\
+ runetypebody.h\
+ sha1.h\
+ sha256.h\
+ sha512.h\
+ text.h\
+ utf.h\
+ util.h
+
+LIBUTF = libutf.a
+LIBUTFSRC =\
+ libutf/chartorunearr.c\
+ libutf/readrune.c\
+ libutf/rune.c\
+ libutf/runetype.c\
+ libutf/utf.c\
+ libutf/writerune.c
+
+LIBUTIL = libutil.a
+LIBUTILSRC =\
+ libutil/agetcwd.c\
+ libutil/apathmax.c\
+ libutil/concat.c\
+ libutil/cp.c\
+ libutil/crypt.c\
+ libutil/ealloc.c\
+ libutil/enmasse.c\
+ libutil/eprintf.c\
+ libutil/eregcomp.c\
+ libutil/estrtod.c\
+ libutil/estrtol.c\
+ libutil/fnck.c\
+ libutil/getlines.c\
+ libutil/human.c\
+ libutil/md5.c\
+ libutil/mode.c\
+ libutil/putword.c\
+ libutil/recurse.c\
+ libutil/rm.c\
+ libutil/sha1.c\
+ libutil/sha256.c\
+ libutil/sha512.c\
+ libutil/strcasestr.c\
+ libutil/strlcat.c\
+ libutil/strlcpy.c
+
+LIB = $(LIBUTF) $(LIBUTIL)
+
+BIN =\
+ basename\
+ cal\
+ cat\
+ chgrp\
+ chmod\
+ chown\
+ chroot\
+ cksum\
+ cmp\
+ cols\
+ comm\
+ cp\
+ cron\
+ cut\
+ date\
+ dirname\
+ du\
+ echo\
+ env\
+ expand\
+ expr\
+ false\
+ fold\
+ grep\
+ head\
+ hostname\
+ kill\
+ link\
+ ln\
+ logger\
+ logname\
+ ls\
+ md5sum\
+ mkdir\
+ mkfifo\
+ mktemp\
+ mv\
+ nice\
+ nl\
+ nohup\
+ paste\
+ printenv\
+ printf\
+ pwd\
+ readlink\
+ renice\
+ rm\
+ rmdir\
+ seq\
+ setsid\
+ sha1sum\
+ sha256sum\
+ sha512sum\
+ sleep\
+ sort\
+ split\
+ sponge\
+ strings\
+ sync\
+ tail\
+ tar\
+ tee\
+ test\
+ touch\
+ tr\
+ true\
+ tty\
+ uname\
+ unexpand\
+ uniq\
+ unlink\
+ uudecode\
+ uuencode\
+ wc\
+ xargs\
+ yes
+
+LIBUTFOBJ = $(LIBUTFSRC:.c=.o)
+LIBUTILOBJ = $(LIBUTILSRC:.c=.o)
+OBJ = $(BIN:=.o) $(LIBUTFOBJ) $(LIBUTILOBJ)
+SRC = $(BIN:=.c)
+MAN = $(BIN:=.1)
+
+all: $(BIN)
+
+$(BIN): $(LIB) $(@:=.o)
+
+$(OBJ): $(HDR) config.mk
+
+.o:
+ $(LD) $(LDFLAGS) -o $@ $< $(LIB)
+
+.c.o:
+ $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -c $<
+
+$(LIBUTF): $(LIBUTFOBJ)
+ $(AR) -r -c $@ $?
+ $(RANLIB) $@
+
+$(LIBUTIL): $(LIBUTILOBJ)
+ $(AR) -r -c $@ $?
+ $(RANLIB) $@
+
+install: all
+ mkdir -p $(DESTDIR)$(PREFIX)/bin
+ cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin
+ cd $(DESTDIR)$(PREFIX)/bin && chmod 755 $(BIN)
+ mkdir -p $(DESTDIR)$(MANPREFIX)/man1
+ for m in $(MAN); do sed "s/VERSION/$(VERSION)/g" < "$$m" > $(DESTDIR)$(MANPREFIX)/man1/"$$m"; done
+ cd $(DESTDIR)$(MANPREFIX)/man1 && chmod 644 $(MAN)
+
+uninstall:
+ cd $(DESTDIR)$(PREFIX)/bin && rm -f $(BIN)
+ cd $(DESTDIR)$(MANPREFIX)/man1 && rm -f $(MAN)
+
+dist: clean
+ mkdir -p sbase-$(VERSION)
+ cp -r LICENSE Makefile README TODO config.mk $(SRC) $(MAN) libutf libutil $(HDR) sbase-$(VERSION)
+ tar -cf sbase-$(VERSION).tar sbase-$(VERSION)
+ gzip sbase-$(VERSION).tar
+ rm -rf sbase-$(VERSION)
+
+sbase-box: $(LIB) $(SRC)
+ mkdir -p build
+ cp $(HDR) build
+ for f in $(SRC); do sed "s/^main(/`basename $$f .c`_&/" < $$f > build/$$f; done
+ echo '#include <libgen.h>' > build/$@.c
+ echo '#include <stdio.h>' >> build/$@.c
+ echo '#include <stdlib.h>' >> build/$@.c
+ echo '#include <string.h>' >> build/$@.c
+ echo '#include "util.h"' >> build/$@.c
+ for f in $(SRC); do echo "int `basename $$f .c`_main(int, char **);" >> build/$@.c; done
+ echo 'int main(int argc, char *argv[]) { char *s = basename(argv[0]); if(!strcmp(s,"sbase-box")) { argc--; argv++; s = basename(argv[0]); } if(0) ;' >> build/$@.c
+ for f in $(SRC); do echo "else if(!strcmp(s, \"`basename $$f .c`\")) return `basename $$f .c`_main(argc, argv);" >> build/$@.c; done
+ echo 'else {' >> build/$@.c
+ for f in $(SRC); do echo "printf(\"`basename $$f .c`\"); putchar(' ');" >> build/$@.c; done
+ echo "putchar(0xa); }; return 0; }" >> build/$@.c
+ $(LD) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ build/*.c $(LIB)
+ rm -r build
+
+clean:
+ rm -f $(BIN) $(OBJ) $(LIB) sbase-box sbase-$(VERSION).tar.gz
+
+.PHONY:
+ all install uninstall dist sbase-box clean
diff --git a/sbase/README b/sbase/README
@@ -0,0 +1,120 @@
+sbase - suckless unix tools
+===========================
+
+sbase is a collection of unix tools that are inherently portable
+across UNIX and UNIX-like systems.
+
+The following tools are implemented ('*' == finished, '#' == UTF-8 support,
+'=' == implicit UTF-8 support):
+
+ UTILITY POSIX 2008 COMPLIANT MISSING OPTIONS
+ ------- -------------------- ---------------
+=* basename yes none
+=* cal yes none
+=* cat yes none
+= chgrp no -h, -H, -L, -P
+=* chmod yes none
+= chown no -h, -H, -L, -P
+= chroot non-posix none
+=* cksum yes none
+ * cmp yes none
+ * cols non-posix none
+=* comm yes none
+= cp no -H, -i, -L
+=* cron non-posix none
+ * cut yes none
+= date yes none
+= dirname yes none
+= du no -H, -L, -x
+= echo yes none
+= env yes none
+# expand yes none
+ expr yes none
+=* false yes none
+ fold yes none
+ * grep yes none
+ head yes none
+= hostname non-posix none
+=* kill yes none
+= link yes none
+= ln yes none
+=* logger yes none
+= logname yes none
+= ls no -C, -R, -q, -u
+ md5sum non-posix none
+= mkdir yes none
+= mkfifo yes none
+= mktemp non-posix none
+= mv no -i
+= nice yes none
+= nl no -d, -f, -h, -l, -n, -p, -v, -w
+= nohup yes none
+ paste yes none
+= printenv non-posix none
+ printf stolen stolen
+=* pwd yes none
+= readlink non-posix none
+= renice yes none
+= rm no -i
+= rmdir no -p
+= sleep yes none
+= setsid non-posix none
+ sort no -m, -o, -d, -f, -i
+ split yes none
+= sponge non-posix none
+ strings no -a, -n, -t
+= sync non-posix none
+= tail no -c, -f
+= tar non-posix none
+= tee no -i
+ test yes none
+= touch no -r
+#* tr yes none
+=* true yes none
+= tty yes none
+= uudecode no -o
+= uuencode no -m
+= uname yes none
+# unexpand yes none
+= uniq no -f, -s
+= unlink yes none
+ seq non-posix none
+= sha1sum non-posix none
+= sha256sum non-posix none
+= sha512sum non-posix none
+ wc yes none
+= xargs no -I, -L, -p, -s, -t, -x
+= yes yes none
+
+The complement of sbase is ubase[1] which is Linux-specific and
+provides all the non-portable tools. Together they are intended to
+form a base system similar to busybox but much smaller and suckless.
+
+Building
+--------
+
+You need GNU make to build sbase on OpenBSD.
+
+To build sbase, simply type make. You may have to fiddle with
+config.mk depending on your system.
+
+You can also build sbase-box, which generates a single binary
+containing all the required tools. You can then symlink the
+individual tools to sbase-box.
+
+Ideally you will want to statically link sbase. If you are on Linux
+we recommend using musl-libc[2].
+
+Portability
+-----------
+
+sbase has been compiled on a variety of different operating systems,
+including Linux, *BSD, OSX, Haiku, Solaris, SCO OpenServer and others.
+
+Various combinations of operating systems and architectures have also
+been built.
+
+You can build sbase with gcc, clang, tcc, nwcc and pcc.
+
+[1] http://git.suckless.org/ubase/
+[2] http://www.musl-libc.org/
diff --git a/sbase/TODO b/sbase/TODO
@@ -0,0 +1,42 @@
+The following list of commands is taken from the toybox roadmap[0] and
+has been stripped down accordingly. Commands that belong to ubase[1]
+are not listed here as well as commands that fall outside the scope of
+sbase such as vi and sh are also not listed here.
+
+at
+awk
+bc
+diff
+ed
+file
+find
+getconf
+install
+join
+make
+od
+patch
+pathchk
+sed
+stty
+tabs
+tput
+
+The following program(s) have been imported from OpenBSD and need
+replacing or cleaning up:
+
+printf
+
+If you are looking for some work to do on sbase, another option is to
+pick a utility of interest and check the POSIX specification to see if
+there are any features missing.
+
+We also need to add support for UTF-8. We have imported libutf from
+suckless.org. As a first step we should rework existing tools that use
+wide characters to use the libutf library.
+
+Update manpages to use mdoc(7). Look at grep.1 or at the OpenBSD
+manpages for more information.
+
+[0] http://landley.net/toybox/roadmap.html
+[1] http://git.suckless.org/ubase/
diff --git a/sbase/arg.h b/sbase/arg.h
@@ -0,0 +1,63 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef ARG_H__
+#define ARG_H__
+
+extern char *argv0;
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
+ argv[0] && argv[0][1]\
+ && argv[0][0] == '-';\
+ argc--, argv++) {\
+ char argc_;\
+ char **argv_;\
+ int brk_;\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ for (brk_ = 0, argv[0]++, argv_ = argv;\
+ argv[0][0] && !brk_;\
+ argv[0]++) {\
+ if (argv_ != argv)\
+ break;\
+ argc_ = argv[0][0];\
+ switch (argc_)
+
+/* Handles obsolete -NUM syntax */
+#define ARGNUM case '0':\
+ case '1':\
+ case '2':\
+ case '3':\
+ case '4':\
+ case '5':\
+ case '6':\
+ case '7':\
+ case '8':\
+ case '9'
+
+#define ARGEND }\
+ }
+
+#define ARGC() argc_
+
+#define ARGNUMF(base) (brk_ = 1, estrtol(argv[0], (base)))
+
+#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ ((x), abort(), (char *)0) :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ (char *)0 :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#endif
diff --git a/sbase/basename.1 b/sbase/basename.1
@@ -0,0 +1,26 @@
+.Dd November 21, 2014
+.Dt BASENAME 1 sbase\-VERSION
+.Os
+.Sh NAME
+.Nm basename
+.Nd strip leading path component
+.Sh SYNOPSIS
+.Nm basename
+.Ar string
+.Op Ar suffix
+.Sh DESCRIPTION
+.Nm
+prints the
+.Ar string
+with any leading path components, and the
+.Ar suffix ,
+removed.
+.Sh SEE ALSO
+.Xr dirname 1 ,
+.Xr basename 3
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
diff --git a/sbase/basename.c b/sbase/basename.c
@@ -0,0 +1,41 @@
+/* See LICENSE file for copyright and license details. */
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+static void usage(void);
+
+void
+usage(void)
+{
+ eprintf("usage: %s name [suffix]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *p;
+ size_t off;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ p = strlen(argv[0]) ? basename(argv[0]) : ".";
+ if (argc == 2 && *p != '/') {
+ if (strlen(argv[1]) < strlen(p)) {
+ off = strlen(p) - strlen(argv[1]);
+ if (strcmp(&p[off], argv[1]) == 0)
+ p[off] = '\0';
+ }
+ }
+ puts(p);
+ return 0;
+}
diff --git a/sbase/cal.1 b/sbase/cal.1
@@ -0,0 +1,67 @@
+.Dd January 18, 2015
+.Dt CAL 1 sbase\-VERSION
+.Sh NAME
+.Nm cal
+.Nd show calendar
+.Sh SYNOPSIS
+.Nm cal
+.Op Fl 1 | Fl 3 | Fl y | Fl n Ar nmonths
+.Op Fl s | Fl m | Fl f Ar firstday
+.Op Fl c Ar columns
+.Oo Oo Ar month Oc Ar year Oc
+.Sh DESCRIPTION
+Print
+.Ar nmonths
+calendars side by side beginning with
+.Ar month
+and
+.Ar year .
+Each row of calendars contains at most
+.Ar columns
+calendars. The date formatting is obtained using
+.Xr localtime 3 .
+.Pp
+If
+.Ar year
+is given without
+.Ar month ,
+print the whole year, unless overridden by other options.
+.Pp
+The Julian calendar is used until Sep 2, 1752. The Gregorian calendar is used
+starting the next day on Sep 14, 1752.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl 1
+Output current month. This is the default.
+.It Fl 3
+Output previous, current and next month.
+.It Fl n Ar nmonths
+Output in total
+.Ar nmonths
+starting from the current month.
+.It Fl y Ar year
+Output an entire
+.Ar year .
+.It Fl s
+Output Sunday as first day of week.
+.It Fl m
+Output Monday as first day of week.
+.It Fl f Ar firstday
+Output
+.Ar firstday
+(0 is Sunday, 6 is Saturday) as first day of week.
+.It Fl c Ar columns
+Set number of calendars in a row. The default is 3.
+.El
+.Sh SEE ALSO
+.Xr localtime 3
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
+.Pp
+The flags
+.Op Fl 13ynsmfc
+are an extension to that specification.
diff --git a/sbase/cal.c b/sbase/cal.c
@@ -0,0 +1,216 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "util.h"
+
+enum { JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };
+enum caltype { JULIAN, GREGORIAN };
+enum { TRANS_YEAR = 1752, TRANS_MONTH = SEP, TRANS_DAY = 2 };
+
+static int
+isleap(int year, enum caltype cal)
+{
+ if (cal == GREGORIAN) {
+ if (year % 400 == 0)
+ return 1;
+ if (year % 100 == 0)
+ return 0;
+ return (year % 4 == 0);
+ }
+ else { /* cal == Julian */
+ return (year % 4 == 0);
+ }
+}
+
+static int
+monthlength(int year, int month, enum caltype cal)
+{
+ int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+ return (month == FEB && isleap(year,cal)) ? 29 : mdays[month];
+}
+
+/* From http://www.tondering.dk/claus/cal/chrweek.php#calcdow */
+static int
+dayofweek(int year, int month, int dom, enum caltype cal)
+{
+ int m, y, a;
+
+ month += 1; /* in this formula, 1 <= month <= 12 */
+ a = (14 - month) / 12;
+ y = year - a;
+ m = month + 12 * a - 2;
+
+ if (cal == GREGORIAN)
+ return (dom + y + y / 4 - y / 100 + y / 400 + (31 * m) / 12) % 7;
+ else /* cal == Julian */
+ return (5 + dom + y + y / 4 + (31 * m) / 12) % 7;
+}
+
+static void
+printgrid(int year, int month, int fday, int line)
+{
+ enum caltype cal;
+ int trans; /* are we in the transition from Julian to Gregorian? */
+ int offset, dom, d = 0;
+
+ if (year < TRANS_YEAR || (year == TRANS_YEAR && month <= TRANS_MONTH))
+ cal = JULIAN;
+ else
+ cal = GREGORIAN;
+ trans = (year == TRANS_YEAR && month == TRANS_MONTH);
+ offset = dayofweek(year, month, 1, cal) - fday;
+ if (offset < 0)
+ offset += 7;
+ if (line == 1) {
+ for ( ; d < offset; ++d)
+ printf(" ");
+ dom = 1;
+ } else {
+ dom = 8 - offset + (line - 2) * 7;
+ if (trans && !(line == 2 && fday == 3))
+ dom += 11;
+ }
+ for ( ; d < 7 && dom <= monthlength(year, month, cal); ++d, ++dom) {
+ printf("%2d ", dom);
+ if (trans && dom==TRANS_DAY)
+ dom += 11;
+ }
+ for ( ; d < 7; ++d)
+ printf(" ");
+}
+
+static void
+drawcal(int year, int month, int ncols, int nmons, int fday)
+{
+ char *smon[] = {" January", " February", " March", " April",
+ " May", " June", " July", " August",
+ "September", " October", " November", " December" };
+ char *days[] = { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", };
+ int m, n, col, cur_year, cur_month, line, dow;
+
+ for (m = 0; m < nmons; ) {
+ n = m;
+ for (col = 0; m < nmons && col < ncols; ++col, ++m) {
+ cur_year = year + m / 12;
+ cur_month = month + m % 12;
+ if (cur_month > 11) {
+ cur_month -= 12;
+ cur_year += 1;
+ }
+ printf(" %s %d ", smon[cur_month], cur_year);
+ printf(" ");
+ }
+ printf("\n");
+ for (col = 0, m = n; m < nmons && col < ncols; ++col, ++m) {
+ for (dow = fday; dow < (fday + 7); ++dow)
+ printf("%s ", days[dow % 7]);
+ printf(" ");
+ }
+ printf("\n");
+ for (line = 1; line <= 6; ++line) {
+ for (col = 0, m = n; m < nmons && col < ncols; ++col, ++m) {
+ cur_year = year + m / 12;
+ cur_month = month + m % 12;
+ if (cur_month > 11) {
+ cur_month -= 12;
+ cur_year += 1;
+ }
+ printgrid(cur_year, cur_month, fday, line);
+ printf(" ");
+ }
+ printf("\n");
+ }
+ }
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-1 | -3 | -y | -n nmonths] "
+ "[-s | -m | -f firstday] [-c columns] [[month] year]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int year, month, ncols, nmons, fday;
+ struct tm *ltime;
+ time_t now;
+
+ now = time(NULL);
+ ltime = localtime(&now);
+ year = ltime->tm_year + 1900;
+ month = ltime->tm_mon + 1;
+ fday = 0;
+
+ ncols = 3;
+ nmons = 0;
+
+ ARGBEGIN {
+ case '1':
+ nmons = 1;
+ break;
+ case '3':
+ nmons = 3;
+ month -= 1;
+ if (month == 0) {
+ month = 12;
+ year--;
+ }
+ break;
+ case 'c':
+ ncols = estrtol(EARGF(usage()), 0);
+ break;
+ case 'f':
+ fday = estrtol(EARGF(usage()), 0);
+ break;
+ case 'm': /* Monday */
+ fday = 1;
+ break;
+ case 'n':
+ nmons = estrtol(EARGF(usage()), 0);
+ break;
+ case 's': /* Sunday */
+ fday = 0;
+ break;
+ case 'y':
+ month = 1;
+ nmons = 12;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (nmons == 0) {
+ if (argc == 1) {
+ month = 1;
+ nmons = 12;
+ } else {
+ nmons = 1;
+ }
+ }
+
+ switch (argc) {
+ case 2:
+ month = estrtol(argv[0], 0);
+ argv++;
+ case 1:
+ year = estrtol(argv[0], 0);
+ break;
+ case 0:
+ break;
+ default:
+ usage();
+ }
+
+ if (ncols < 0 || month < 1 || month > 12 || nmons < 1 || fday < 0 || fday > 6) {
+ usage();
+ }
+
+ drawcal(year, month - 1, ncols, nmons, fday);
+
+ return 0;
+}
diff --git a/sbase/cat.1 b/sbase/cat.1
@@ -0,0 +1,29 @@
+.Dd January 16, 2015
+.Dt CAT 1 sbase\-VERSION
+.Sh NAME
+.Nm cat
+.Nd concatenate files
+.Sh SYNOPSIS
+.Nm cat
+.Op Fl u
+.Op Ar file ...
+.Sh DESCRIPTION
+.Nm
+reads each
+.Ar file
+in sequence and writes it to stdout. If no
+.Ar file
+is given,
+.Nm
+reads from stdin.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl u
+Unbuffered output
+.El
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
diff --git a/sbase/cat.c b/sbase/cat.c
@@ -0,0 +1,45 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "text.h"
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-u] [file...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+ int ret = 0;
+
+ ARGBEGIN {
+ case 'u':
+ setbuf(stdout, NULL);
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0) {
+ concat(stdin, "<stdin>", stdout, "<stdout>");
+ } else {
+ for (; argc; argc--, argv++) {
+ if (argv[0][0] == '-' && !argv[0][1])
+ argv[0] = "/dev/fd/0";
+ if (!(fp = fopen(argv[0], "r"))) {
+ weprintf("fopen %s:", argv[0]);
+ ret = 1;
+ continue;
+ }
+ concat(fp, argv[0], stdout, "<stdout>");
+ fclose(fp);
+ }
+ }
+ return ret;
+}
diff --git a/sbase/chgrp.1 b/sbase/chgrp.1
@@ -0,0 +1,22 @@
+.TH CHGRP 1 sbase\-VERSION
+.SH NAME
+chgrp \- change the file group ownership
+.SH SYNOPSIS
+.B chgrp
+.RB [ \-R ]
+.I groupname
+.I file...
+.SH DESCRIPTION
+.B chgrp
+sets the group id of the files specified by
+.IR file
+to the gid of the group named
+.IR group.
+If the
+.IR R
+flag is specified, this process is recursively applied to
+everything in
+.IR file.
+
+.SH SEE ALSO
+.IR chown (1) chown (2) chmod (1) chmod (2) getgrnam (3)
diff --git a/sbase/chgrp.c b/sbase/chgrp.c
@@ -0,0 +1,70 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <grp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static int gid;
+static int status;
+static int rflag;
+static struct stat st;
+
+static void
+usage(void)
+{
+ eprintf("usage: chgrp [-R] groupname file...\n");
+}
+
+static void
+chgrp(const char *path)
+{
+ if (chown(path, st.st_uid, gid) < 0) {
+ weprintf("chown %s:", path);
+ status = 1;
+ }
+ if (rflag)
+ recurse(path, chgrp);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct group *gr;
+
+ ARGBEGIN {
+ case 'R':
+ rflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 2)
+ usage();
+
+ errno = 0;
+ gr = getgrnam(argv[0]);
+ if (!gr) {
+ if (errno)
+ eprintf("getgrnam %s:", argv[0]);
+ else
+ eprintf("getgrnam %s: no such group\n", argv[0]);
+ }
+ gid = gr->gr_gid;
+
+ while (*++argv) {
+ if (stat(*argv, &st) < 0) {
+ weprintf("stat %s:", *argv);
+ status = 1;
+ continue;
+ }
+ chgrp(*argv);
+ }
+ return status;
+}
diff --git a/sbase/chmod.1 b/sbase/chmod.1
@@ -0,0 +1,66 @@
+.Dd January 17, 2015
+.Dt CHMOD 1 sbase\-VERSION
+.Sh NAME
+.Nm chmod
+.Nd change file mode
+.Sh SYNOPSIS
+.Nm chmod
+.Op Fl R
+.Ar mode
+.Op Ar file ...
+.Sh DESCRIPTION
+.Nm
+changes the file mode of the given files.
+.Pp
+If
+.Ar mode
+is
+.Em octal
+"[sog]e"
+.Bl -tag -width Ds
+.It s
+.Xr sticky 1 => s += 1
+.Pp
+.Xr setgid 2 => s += 2
+.Pp
+.Xr setuid 4 => s += 4
+.It o|g|e
+owner | group | everyone
+.Pp
+.Xr execute 1 => o|g|e += 1
+.Pp
+.Xr write 2 => o|g|e += 2
+.Pp
+.Xr read 4 => o|g|e += 4
+.El
+.Pp
+Leading zeroes may be omitted.
+.Pp
+If
+.Ar mode
+is
+.Em symbolic
+"[ugoa]*[+-=][rwxst]*"
+.Bl -tag -width Ds
+.It u|g|o|a
+owner | group | other (non-group) | everyone
+.It +|-|=
+add | remove | set
+.It r|w|x|s|t
+read | write | execute | setuid and setgid | sticky
+.El
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl R
+Change modes recursively
+.El
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
+.Pp
+The
+.Op Fl R
+flag is an extension to that specification.
diff --git a/sbase/chmod.c b/sbase/chmod.c
@@ -0,0 +1,74 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static int rflag = 0;
+static char *modestr = "";
+static mode_t mask = 0;
+static int ret = 0;
+
+void
+chmodr(const char *path)
+{
+ struct stat st;
+ mode_t m;
+
+ if (stat(path, &st) < 0) {
+ weprintf("stat %s:", path);
+ ret = 1;
+ return;
+ }
+
+ m = parsemode(modestr, st.st_mode, mask);
+ if (chmod(path, m) < 0) {
+ weprintf("chmod %s:", path);
+ ret = 1;
+ }
+ if (rflag)
+ recurse(path, chmodr);
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-R] mode [file ...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ size_t i;
+
+ argv0 = argv[0];
+ for (i = 1; i < argc && argv[i][0] == '-'; i++) {
+ switch (argv[i][1]) {
+ case 'R':
+ rflag = 1;
+ break;
+ case 'r': case 'w': case 'x': case 's': case 't':
+ /*
+ * -[rwxst] are valid modes so do not interpret
+ * them as options - in any case we are done if
+ * we hit this case
+ */
+ goto done;
+ default:
+ usage();
+ }
+ }
+done:
+ mask = getumask();
+ modestr = argv[i];
+
+ if (argc - i - 1 < 1)
+ usage();
+
+ for (++i; i < argc; i++)
+ chmodr(argv[i]);
+
+ return ret;
+}
diff --git a/sbase/chown.1 b/sbase/chown.1
@@ -0,0 +1,20 @@
+.TH CHOWN 1 sbase\-VERSION
+.SH NAME
+chown \- change file ownership
+.SH SYNOPSIS
+.B chown
+.RB [ \-Rr ]
+.RI [ owner ][: group ]
+.RI [ file ...]
+.SH DESCRIPTION
+.B chown
+changes the user or group ownership for the given files.
+.SH OPTIONS
+.TP
+.B \-R
+equivalent to -r.
+.TP
+.B \-r
+change directory ownership recursively.
+.SH SEE ALSO
+.IR chown (2)
diff --git a/sbase/chown.c b/sbase/chown.c
@@ -0,0 +1,90 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void chownpwgr(const char *);
+
+static int rflag = 0;
+static uid_t uid = -1;
+static gid_t gid = -1;
+static int ret = 0;
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-Rr] [owner][:[group]] file...\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *owner, *group, *end;
+ struct passwd *pw;
+ struct group *gr;
+
+ ARGBEGIN {
+ case 'R':
+ case 'r':
+ rflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0)
+ usage();
+
+ owner = argv[0];
+ argv++;
+ argc--;
+ if ((group = strchr(owner, ':')))
+ *group++ = '\0';
+
+ if (owner && *owner) {
+ errno = 0;
+ pw = getpwnam(owner);
+ if (pw) {
+ uid = pw->pw_uid;
+ } else {
+ if (errno != 0)
+ eprintf("getpwnam %s:", owner);
+ uid = strtoul(owner, &end, 10);
+ if (*end != '\0')
+ eprintf("getpwnam %s: no such user\n", owner);
+ }
+ }
+ if (group && *group) {
+ errno = 0;
+ gr = getgrnam(group);
+ if (gr) {
+ gid = gr->gr_gid;
+ } else {
+ if (errno != 0)
+ eprintf("getgrnam %s:", group);
+ gid = strtoul(group, &end, 10);
+ if (*end != '\0')
+ eprintf("getgrnam %s: no such group\n", group);
+ }
+ }
+ for (; argc > 0; argc--, argv++)
+ chownpwgr(argv[0]);
+
+ return ret;
+}
+
+void
+chownpwgr(const char *path)
+{
+ if (chown(path, uid, gid) < 0) {
+ weprintf("chown %s:", path);
+ ret = 1;
+ }
+ if (rflag)
+ recurse(path, chownpwgr);
+}
diff --git a/sbase/chroot.1 b/sbase/chroot.1
@@ -0,0 +1,26 @@
+.TH CHROOT 1 sbase\-VERSION
+.SH NAME
+chroot \- invoke a command with a different root directory
+.SH SYNOPSIS
+.B chroot
+.IR dir
+.RI [ command
+.RI [ arg ...]]
+
+.SH DESCRIPTION
+.B chroot
+runs
+.IR command
+after changing the root directory to
+.IR dir
+with the
+.B chroot
+system call, and changing the working directory to the new root.
+
+If
+.IR command
+is not specified, an interactive shell is started in the new root.
+
+.SH SEE ALSO
+.IR chroot (2)
+.IR chdir (2)
diff --git a/sbase/chroot.c b/sbase/chroot.c
@@ -0,0 +1,50 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: chroot dir [command [arg...]]\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *shell[] = { "/bin/sh", "-i", NULL }, *aux, *p;
+ int savederrno;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ if ((aux = getenv("SHELL")))
+ shell[0] = aux;
+
+ if (chroot(argv[0]) < 0)
+ eprintf("chroot %s:", argv[0]);
+
+ if (chdir("/") < 0)
+ eprintf("chdir:");
+
+ if (argc == 1) {
+ p = *shell;
+ execvp(*shell, shell);
+ } else {
+ p = argv[1];
+ execvp(argv[1], argv+1);
+ }
+
+ savederrno = errno;
+ weprintf("execvp %s:", p);
+ _exit(savederrno == ENOENT ? 127 : 126);
+ /* unreachable */
+ return 0;
+}
diff --git a/sbase/cksum.1 b/sbase/cksum.1
@@ -0,0 +1,27 @@
+.Dd January 17, 2015
+.Dt CKSUM 1 sbase\-VERSION
+.Sh NAME
+.Nm cksum
+.Nd compute file checksum
+.Sh SYNOPSIS
+.Nm cksum
+.Op Ar file ...
+.Sh DESCRIPTION
+.Nm
+calculates a cyclic redundancy check (CRC) of
+.Ar file
+according to
+.St -iso8802-3
+and writes it, the file size in bytes and path to stdout.
+.Pp
+If no
+.Ar file
+is given,
+.Nm
+reads from stdin.
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
diff --git a/sbase/cksum.c b/sbase/cksum.c
@@ -0,0 +1,118 @@
+/* See LICENSE file for copyright and license details. */
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+static const unsigned long crctab[] = { 0x00000000,
+0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
+0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6,
+0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
+0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac,
+0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f,
+0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a,
+0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
+0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58,
+0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033,
+0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe,
+0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
+0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4,
+0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
+0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5,
+0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
+0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07,
+0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c,
+0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1,
+0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
+0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b,
+0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698,
+0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d,
+0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
+0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f,
+0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
+0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80,
+0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
+0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a,
+0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629,
+0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c,
+0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
+0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e,
+0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65,
+0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8,
+0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
+0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2,
+0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
+0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74,
+0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
+0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21,
+0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a,
+0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087,
+0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
+0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d,
+0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce,
+0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb,
+0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
+0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09,
+0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
+0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf,
+0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
+};
+
+static void
+cksum(FILE *fp, const char *s)
+{
+ unsigned char buf[BUFSIZ];
+ uint32_t ck = 0;
+ size_t len = 0;
+ size_t i, n;
+
+ while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
+ for (i = 0; i < n; i++)
+ ck = (ck << 8) ^ crctab[(ck >> 24) ^ buf[i]];
+ len += n;
+ }
+ if (ferror(fp))
+ eprintf("%s: read error:", s ? s : "<stdin>");
+
+ for (i = len; i > 0; i >>= 8)
+ ck = (ck << 8) ^ crctab[(ck >> 24) ^ (i & 0xFF)];
+
+ printf("%"PRIu32" %lu", ~ck, (unsigned long)len);
+ if (s)
+ printf(" %s", s);
+ putchar('\n');
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [files ...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0)
+ cksum(stdin, NULL);
+ else {
+ for (; argc > 0; argc--, argv++) {
+ if (!(fp = fopen(argv[0], "r"))) {
+ weprintf("fopen %s:", argv[0]);
+ continue;
+ }
+ cksum(fp, argv[0]);
+ fclose(fp);
+ }
+ }
+ return 0;
+}
diff --git a/sbase/cmp.1 b/sbase/cmp.1
@@ -0,0 +1,42 @@
+.Dd November 21, 2014
+.Dt CMP 1 sbase\-VERSION
+.Os
+.Sh NAME
+.Nm cmp
+.Nd compare two files
+.Sh SYNOPSIS
+.Nm cmp
+.Op Fl l | Fl s
+.Ar file1 file2
+.Sh DESCRIPTION
+.Nm
+compares two files byte by byte. If the files differ,
+.Nm
+prints the byte and
+line number at which the difference occurred.
+.Pp
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl l
+Print the byte number, and the differing bytes (in octal), for each difference.
+.It Fl s
+Print nothing, only returns status.
+.El
+.Sh EXIT STATUS
+.Bl -tag -width Ds
+.It 0
+The files are identical.
+.It 1
+The files are different.
+.It > 1
+An error occured.
+.El
+.Sh SEE ALSO
+.Xr comm 1 ,
+.Xr diff 1
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
diff --git a/sbase/cmp.c b/sbase/cmp.c
@@ -0,0 +1,86 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+enum { Same = 0, Diff = 1, Error = 2 };
+
+static void
+usage(void)
+{
+ enprintf(Error, "usage: %s [-l | -s] file1 file2\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int lflag = 0;
+ int sflag = 0;
+ int same = 1;
+ int b[2], i;
+ long line = 1, n = 1;
+ FILE *fp[2];
+
+ ARGBEGIN {
+ case 'l':
+ lflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc != 2 || (lflag && sflag))
+ usage();
+
+ if (argv[0][0] == '-' && !argv[0][1])
+ argv[0] = "/dev/fd/0";
+ fp[0] = fopen(argv[0], "r");
+ if (!fp[0]) {
+ if (!sflag)
+ weprintf("fopen %s:", argv[0]);
+ exit(Error);
+ }
+
+ if (argv[1][0] == '-' && !argv[1][1])
+ argv[1] = "/dev/fd/0";
+ fp[1] = fopen(argv[1], "r");
+ if (!fp[1]) {
+ if (!sflag)
+ weprintf("fopen %s:", argv[1]);
+ exit(Error);
+ }
+
+ for (n = 1; ; n++) {
+ b[0] = getc(fp[0]);
+ b[1] = getc(fp[1]);
+ if (b[0] == EOF && b[1] == EOF)
+ break;
+ if (b[0] == '\n' && b[1] == '\n')
+ line++;
+ if (b[0] == b[1])
+ continue;
+ for (i = 0; i < 2; i++) {
+ if (b[i] == EOF) {
+ if (!sflag)
+ fprintf(stderr, "cmp: EOF on %s\n",
+ !argv[i] ? "<stdin>" : argv[i]);
+ exit(Diff);
+ }
+ }
+ if (!lflag) {
+ if (!sflag)
+ printf("%s %s differ: char %ld, line %ld\n",
+ argv[0], !argv[1] ? "<stdin>" : argv[1], n, line);
+ exit(Diff);
+ } else {
+ printf("%ld %o %o\n", n, b[0], b[1]);
+ same = 0;
+ }
+ }
+ return same ? Same : Diff;
+}
diff --git a/sbase/cols.1 b/sbase/cols.1
@@ -0,0 +1,41 @@
+.Dd December 8, 2014
+.Dt COLS 1 sbase\-VERSION
+.Os
+.Sh NAME
+.Nm cols
+.Nd columnize output
+.Sh SYNOPSIS
+.Nm cols
+.Op Fl c Ar chars
+.Op Ar file ...
+.Sh DESCRIPTION
+.Nm
+reads each file in sequence and writes them to stdout,
+in as many vertical columns as will fit in
+.Ar chars
+character columns.
+If no file is given, cols reads from stdin.
+.Sh OPTIONS
+.Bl -tag -width xxxxxxxx
+.It Fl c Ar chars
+Specifies the maximum number of character columns to use
+(unless the input contains lines longer than
+.Ar chars
+characters). By default cols tries to figure out the width
+of the output device, if that fails it defaults to 65
+chars.
+.El
+.Sh BUGS
+This implementation of
+.Nm
+assumes that each UTF-8 code point occupies one character cell,
+and thus mishandles TAB characters (among others).
+.Pp
+.Nm
+currently mangles files which contain embedded NULs.
+.Sh HISTORY
+.Nm
+is similar to the mc(1) command on Plan 9. It was renamed to
+.Nm
+to avoid the name collision with the popular file manager
+Midnight Commander.
diff --git a/sbase/cols.c b/sbase/cols.c
@@ -0,0 +1,94 @@
+/* See LICENSE file for copyright and license details. */
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "text.h"
+#include "utf.h"
+#include "util.h"
+
+static long chars = 65;
+static int cflag;
+static struct linebuf b = EMPTY_LINEBUF;
+
+static long n_columns;
+static long n_rows;
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-c chars] [file ...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ long i, l, col;
+ size_t len, bytes;
+ int maxlen = 0;
+ struct winsize w;
+ FILE *fp;
+
+ ARGBEGIN {
+ case 'c':
+ cflag = 1;
+ chars = estrtol(EARGF(usage()), 0);
+ if (chars < 3)
+ eprintf("%d: too few character columns");
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (cflag == 0) {
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
+ if (w.ws_col != 0)
+ chars = w.ws_col;
+ }
+
+ if (argc == 0) {
+ getlines(stdin, &b);
+ } else for (; argc > 0; argc--, argv++) {
+ if (!(fp = fopen(argv[0], "r")))
+ eprintf("fopen %s:", argv[0]);
+ getlines(fp, &b);
+ fclose(fp);
+ }
+
+ for (l = 0; l < b.nlines; ++l) {
+ len = utflen(b.lines[l]);
+ bytes = strlen(b.lines[l]);
+ if (len > 0 && b.lines[l][bytes-1] == '\n') {
+ b.lines[l][bytes-1] = '\0';
+ --len;
+ }
+ if (len > maxlen)
+ maxlen = len;
+ if (maxlen > (chars - 1) / 2)
+ break;
+ }
+
+ n_columns = (chars + 1) / (maxlen + 1);
+ if (n_columns <= 1) {
+ for (l = 0; l < b.nlines; ++l) {
+ fputs(b.lines[l], stdout);
+ }
+ return 0;
+ }
+
+ n_rows = (b.nlines + (n_columns - 1)) / n_columns;
+ for (i = 0; i < n_rows; ++i) {
+ for (l = i, col = 1; l < b.nlines; l += n_rows, ++col) {
+ len = utflen(b.lines[l]);
+ fputs(b.lines[l], stdout);
+ if (col < n_columns)
+ printf("%*s", maxlen + 1 - (int)len, "");
+ }
+ fputs("\n", stdout);
+ }
+
+ return 0;
+}
diff --git a/sbase/comm.1 b/sbase/comm.1
@@ -0,0 +1,43 @@
+.Dd January 18, 2015
+.Dt COMM 1 sbase\-VERSION
+.Sh NAME
+.Nm comm
+.Nd select or reject lines common to two files
+.Sh SYNOPSIS
+.Nm comm
+.Op Fl 123
+.Ar file1
+.Ar file2
+.Sh DESCRIPTION
+.Nm
+reads
+.Ar file1
+and
+.Ar file2,
+which should both be sorted lexically, and writes three text columns
+to stdout:
+.Bl -tag -width Ds
+.It 1
+Lines only in
+.Ar file1 .
+.It 2
+Lines only in
+.Ar file2 .
+.It 3
+Common lines.
+.El
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl 1 | Fl 2 | Fl 3
+Suppress column 1 | 2 | 3
+.El
+.Sh SEE ALSO
+.Xr cmp 1 ,
+.Xr sort 1 ,
+.Xr uniq 1
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
diff --git a/sbase/comm.c b/sbase/comm.c
@@ -0,0 +1,107 @@
+/* See LICENSE file for copyright and license details. */
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+#define CLAMP(x, l, h) MIN(h, MAX(l, x))
+
+static int show = 0x07;
+
+static void
+printline(int pos, char *line)
+{
+ int i;
+
+ if (!(show & (0x1 << pos)))
+ return;
+
+ for (i = 0; i < pos; i++) {
+ if (show & (0x1 << i))
+ putchar('\t');
+ }
+ printf("%s", line);
+}
+
+static char *
+nextline(char *buf, int n, FILE *f, char *name)
+{
+ buf = fgets(buf, n, f);
+ if (!buf && !feof(f))
+ eprintf("%s: read error:", name);
+ if (buf && !strchr(buf, '\n'))
+ eprintf("%s: line too long\n", name);
+ return buf;
+}
+
+static void
+finish(int pos, FILE *f, char *name)
+{
+ char buf[LINE_MAX + 1];
+
+ while (nextline(buf, sizeof(buf), f, name))
+ printline(pos, buf);
+ exit(1);
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-123] file1 file2\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i, diff = 0;
+ FILE *fp[2];
+ char lines[2][LINE_MAX + 1];
+
+ ARGBEGIN {
+ case '1':
+ case '2':
+ case '3':
+ show &= 0x07 ^ (1 << (ARGC() - '1'));
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc != 2)
+ usage();
+
+ for (i = 0; i < LEN(fp); i++) {
+ if (argv[i][0] == '-' && !argv[i][1])
+ argv[i] = "/dev/fd/0";
+ if (!(fp[i] = fopen(argv[i], "r")))
+ eprintf("fopen %s:", argv[i]);
+ }
+
+ for (;;) {
+ if (diff <= 0) {
+ lines[0][0] = '\0';
+ if (!nextline(lines[0], sizeof(lines[0]),
+ fp[0], argv[0])) {
+ if (lines[1][0] != '\0')
+ printline(1, lines[1]);
+ finish(1, fp[1], argv[1]);
+ }
+ }
+ if (diff >= 0) {
+ lines[1][0] = '\0';
+ if (!nextline(lines[1], sizeof(lines[1]),
+ fp[1], argv[1])) {
+ if (lines[0][0] != '\0')
+ printline(0, lines[0]);
+ finish(0, fp[0], argv[0]);
+ }
+ }
+ diff = strcmp(lines[0], lines[1]);
+ diff = CLAMP(diff, -1, 1);
+ printline((2-diff) % 3, lines[MAX(0, diff)]);
+ }
+
+ return 0;
+}
diff --git a/sbase/compat.h b/sbase/compat.h
@@ -0,0 +1,6 @@
+/* See LICENSE file for copyright and license details. */
+#include <limits.h>
+
+#ifndef HOST_NAME_MAX
+#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
+#endif
diff --git a/sbase/config.mk b/sbase/config.mk
@@ -0,0 +1,15 @@
+# sbase version
+VERSION = 0.0
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = $(PREFIX)/share/man
+
+CC = cc
+LD = $(CC)
+AR = ar
+RANLIB = ranlib
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700
+CFLAGS = -std=c99 -Wall -pedantic
+LDFLAGS = -s
diff --git a/sbase/cp.1 b/sbase/cp.1
@@ -0,0 +1,42 @@
+.TH CP 1 sbase\-VERSION
+.SH NAME
+cp \- copy files and directories
+.SH SYNOPSIS
+.B cp
+.RB [ \-Rr ]
+.I file
+.RI [ name ]
+.P
+.B cp
+.RB [ \-aPpRrv ]
+.RI [ file ...]
+.RI [ directory ]
+.SH DESCRIPTION
+.B cp
+copies a given file, naming it the given name. If multiple files are listed
+they will be copied into the given directory.
+.SH OPTIONS
+.TP
+.B \-a
+preserve mode, timestamp, links and permissions.
+Implies \-d, \-p, \-r.
+.TP
+.B \-P
+don't dereference symbolic links.
+.TP
+.B \-p
+preserve mode, timestamp and permissions.
+.TP
+.B \-f
+if an existing destination file cannot be opened, remove it and try again.
+.TP
+.B \-R
+equivalent to -r.
+.TP
+.B \-r
+copies directories recursively. If this flag is not specified, directories are
+not copied.
+.TP
+.B \-v
+print names of source and destination per file to stdout. In the format:
+"source \-> destination".
diff --git a/sbase/cp.c b/sbase/cp.c
@@ -0,0 +1,51 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "fs.h"
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-adfpRrv] source... dest\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct stat st;
+
+ ARGBEGIN {
+ case 'a':
+ /* implies -dpr */
+ cp_aflag = cp_Pflag = cp_pflag = cp_rflag = 1;
+ break;
+ case 'P':
+ cp_Pflag = 1;
+ break;
+ case 'p':
+ cp_pflag = 1;
+ break;
+ case 'f':
+ cp_fflag = 1;
+ break;
+ case 'R':
+ case 'r':
+ cp_rflag = 1;
+ break;
+ case 'v':
+ cp_vflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 2)
+ usage();
+
+ if (argc > 2 && !(stat(argv[argc-1], &st) == 0 && S_ISDIR(st.st_mode)))
+ eprintf("%s: not a directory\n", argv[argc-1]);
+ enmasse(argc, argv, cp);
+ return cp_status;
+}
diff --git a/sbase/cron.1 b/sbase/cron.1
@@ -0,0 +1,24 @@
+.Dd December 16, 2014
+.Dt CRON 1 sbase\-VERSION
+.Os
+.Sh NAME
+.Nm cron
+.Nd clock daemon
+.Sh SYNOPSIS
+.Nm cron
+.Op Fl f Ar file
+.Op Fl n
+.Sh DESCRIPTION
+.Nm
+schedules commands to be run at specified dates and times.
+.Pp
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl f Ar file
+Use the specified
+.Ar file
+instead of the default
+.Ar /etc/crontab .
+.It Fl n
+Do not daemonize.
+.El
diff --git a/sbase/cron.c b/sbase/cron.c
@@ -0,0 +1,494 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "queue.h"
+#include "util.h"
+
+struct field {
+ /* [low, high] */
+ long low;
+ long high;
+ /* for every `div' units */
+ long div;
+};
+
+struct ctabentry {
+ struct field min;
+ struct field hour;
+ struct field mday;
+ struct field mon;
+ struct field wday;
+ char *cmd;
+ TAILQ_ENTRY(ctabentry) entry;
+};
+
+struct jobentry {
+ char *cmd;
+ pid_t pid;
+ TAILQ_ENTRY(jobentry) entry;
+};
+
+char *argv0;
+static sig_atomic_t chldreap;
+static sig_atomic_t reload;
+static sig_atomic_t quit;
+static TAILQ_HEAD(, ctabentry) ctabhead = TAILQ_HEAD_INITIALIZER(ctabhead);
+static TAILQ_HEAD(, jobentry) jobhead = TAILQ_HEAD_INITIALIZER(jobhead);
+static char *config = "/etc/crontab";
+static char *pidfile = "/var/run/crond.pid";
+static int nflag;
+
+static void
+loginfo(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ if (nflag == 0)
+ vsyslog(LOG_INFO, fmt, ap);
+ else
+ vfprintf(stdout, fmt, ap);
+ fflush(stdout);
+ va_end(ap);
+}
+
+static void
+logwarn(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ if (nflag == 0)
+ vsyslog(LOG_WARNING, fmt, ap);
+ else
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+static void
+logerr(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ if (nflag == 0)
+ vsyslog(LOG_ERR, fmt, ap);
+ else
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+static void
+runjob(char *cmd)
+{
+ struct jobentry *je;
+ time_t t;
+ pid_t pid;
+
+ t = time(NULL);
+
+ /* If command is already running, skip it */
+ TAILQ_FOREACH(je, &jobhead, entry) {
+ if (strcmp(je->cmd, cmd) == 0) {
+ loginfo("already running %s pid: %d at %s",
+ je->cmd, je->pid, ctime(&t));
+ return;
+ }
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ logerr("error: failed to fork job: %s time: %s",
+ cmd, ctime(&t));
+ return;
+ } else if (pid == 0) {
+ setsid();
+ loginfo("run: %s pid: %d at %s",
+ cmd, getpid(), ctime(&t));
+ execl("/bin/sh", "/bin/sh", "-c", cmd, (char *)NULL);
+ logerr("error: failed to execute job: %s time: %s",
+ cmd, ctime(&t));
+ _exit(1);
+ } else {
+ je = emalloc(sizeof(*je));
+ je->cmd = estrdup(cmd);
+ je->pid = pid;
+ TAILQ_INSERT_TAIL(&jobhead, je, entry);
+ }
+}
+
+static void
+waitjob(void)
+{
+ struct jobentry *je, *tmp;
+ int status;
+ time_t t;
+ pid_t pid;
+
+ t = time(NULL);
+
+ while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
+ je = NULL;
+ TAILQ_FOREACH(tmp, &jobhead, entry) {
+ if (tmp->pid == pid) {
+ je = tmp;
+ break;
+ }
+ }
+ if (je) {
+ TAILQ_REMOVE(&jobhead, je, entry);
+ free(je->cmd);
+ free(je);
+ }
+ if (WIFEXITED(status) == 1)
+ loginfo("complete: pid: %d returned: %d time: %s",
+ pid, WEXITSTATUS(status), ctime(&t));
+ else if (WIFSIGNALED(status) == 1)
+ loginfo("complete: pid: %d terminated by signal: %s time: %s",
+ pid, strsignal(WTERMSIG(status)), ctime(&t));
+ else if (WIFSTOPPED(status) == 1)
+ loginfo("complete: pid: %d stopped by signal: %s time: %s",
+ pid, strsignal(WSTOPSIG(status)), ctime(&t));
+ }
+}
+
+static int
+isleap(int year)
+{
+ if (year % 400 == 0)
+ return 1;
+ if (year % 100 == 0)
+ return 0;
+ return (year % 4 == 0);
+}
+
+static int
+daysinmon(int mon, int year)
+{
+ int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ if (year < 1900)
+ year += 1900;
+ if (isleap(year))
+ days[1] = 29;
+ return days[mon];
+}
+
+static int
+matchentry(struct ctabentry *cte, struct tm *tm)
+{
+ struct {
+ struct field *f;
+ int tm;
+ int len;
+ } matchtbl[] = {
+ { .f = &cte->min, .tm = tm->tm_min, .len = 60 },
+ { .f = &cte->hour, .tm = tm->tm_hour, .len = 24 },
+ { .f = &cte->mday, .tm = tm->tm_mday, .len = daysinmon(tm->tm_mon, tm->tm_year) },
+ { .f = &cte->mon, .tm = tm->tm_mon, .len = 12 },
+ { .f = &cte->wday, .tm = tm->tm_wday, .len = 7 },
+ };
+ size_t i;
+
+ for (i = 0; i < LEN(matchtbl); i++) {
+ if (matchtbl[i].f->high == -1) {
+ if (matchtbl[i].f->low == -1) {
+ continue;
+ } else if (matchtbl[i].f->div > 0) {
+ if (matchtbl[i].tm > 0) {
+ if (matchtbl[i].tm % matchtbl[i].f->div == 0)
+ continue;
+ } else {
+ if (matchtbl[i].len % matchtbl[i].f->div == 0)
+ continue;
+ }
+ } else if (matchtbl[i].f->low == matchtbl[i].tm) {
+ continue;
+ }
+ } else if (matchtbl[i].f->low <= matchtbl[i].tm &&
+ matchtbl[i].f->high >= matchtbl[i].tm) {
+ continue;
+ }
+ break;
+ }
+ if (i != LEN(matchtbl))
+ return 0;
+ return 1;
+}
+
+static int
+parsefield(const char *field, long low, long high, struct field *f)
+{
+ long min, max, div;
+ char *e1, *e2;
+
+ if (strcmp(field, "*") == 0) {
+ f->low = -1;
+ f->high = -1;
+ return 0;
+ }
+
+ div = -1;
+ max = -1;
+ min = strtol(field, &e1, 10);
+
+ switch (e1[0]) {
+ case '-':
+ e1++;
+ errno = 0;
+ max = strtol(e1, &e2, 10);
+ if (e2[0] != '\0' || errno != 0)
+ return -1;
+ break;
+ case '*':
+ e1++;
+ if (e1[0] != '/')
+ return -1;
+ e1++;
+ errno = 0;
+ div = strtol(e1, &e2, 10);
+ if (e2[0] != '\0' || errno != 0)
+ return -1;
+ break;
+ case '\0':
+ break;
+ default:
+ return -1;
+ }
+
+ if (min < low || min > high)
+ return -1;
+ if (max != -1)
+ if (max < low || max > high)
+ return -1;
+ if (div != -1)
+ if (div < low || div > high)
+ return -1;
+
+ f->low = min;
+ f->high = max;
+ f->div = div;
+ return 0;
+}
+
+static void
+unloadentries(void)
+{
+ struct ctabentry *cte, *tmp;
+
+ for (cte = TAILQ_FIRST(&ctabhead); cte; cte = tmp) {
+ tmp = TAILQ_NEXT(cte, entry);
+ TAILQ_REMOVE(&ctabhead, cte, entry);
+ free(cte->cmd);
+ free(cte);
+ }
+}
+
+static int
+loadentries(void)
+{
+ struct ctabentry *cte;
+ FILE *fp;
+ char *line = NULL, *p, *col;
+ int r = 0, y;
+ size_t size = 0;
+ ssize_t len;
+
+ if ((fp = fopen(config, "r")) == NULL) {
+ logerr("error: can't open %s: %s\n", config, strerror(errno));
+ return -1;
+ }
+
+ for (y = 0; (len = getline(&line, &size, fp)) != -1; y++) {
+ p = line;
+ if (line[0] == '#' || line[0] == '\n' || line[0] == '\0')
+ continue;
+
+ cte = emalloc(sizeof(*cte));
+
+ col = strsep(&p, "\t");
+ if (!col || parsefield(col, 0, 59, &cte->min) < 0) {
+ logerr("error: failed to parse `min' field on line %d\n",
+ y + 1);
+ free(cte);
+ r = -1;
+ break;
+ }
+
+ col = strsep(&p, "\t");
+ if (!col || parsefield(col, 0, 23, &cte->hour) < 0) {
+ logerr("error: failed to parse `hour' field on line %d\n",
+ y + 1);
+ free(cte);
+ r = -1;
+ break;
+ }
+
+ col = strsep(&p, "\t");
+ if (!col || parsefield(col, 1, 31, &cte->mday) < 0) {
+ logerr("error: failed to parse `mday' field on line %d\n",
+ y + 1);
+ free(cte);
+ r = -1;
+ break;
+ }
+
+ col = strsep(&p, "\t");
+ if (!col || parsefield(col, 1, 12, &cte->mon) < 0) {
+ logerr("error: failed to parse `mon' field on line %d\n",
+ y + 1);
+ free(cte);
+ r = -1;
+ break;
+ }
+
+ col = strsep(&p, "\t");
+ if (!col || parsefield(col, 0, 6, &cte->wday) < 0) {
+ logerr("error: failed to parse `wday' field on line %d\n",
+ y + 1);
+ free(cte);
+ r = -1;
+ break;
+ }
+
+ col = strsep(&p, "\n");
+ if (!col) {
+ logerr("error: missing `cmd' field on line %d\n",
+ y + 1);
+ free(cte);
+ r = -1;
+ break;
+ }
+ cte->cmd = estrdup(col);
+
+ TAILQ_INSERT_TAIL(&ctabhead, cte, entry);
+ }
+
+ if (r < 0)
+ unloadentries();
+
+ free(line);
+ fclose(fp);
+
+ return r;
+}
+
+static void
+reloadentries(void)
+{
+ unloadentries();
+ if (loadentries() < 0)
+ logwarn("warning: discarding old crontab entries\n");
+}
+
+static void
+sighandler(int sig)
+{
+ switch (sig) {
+ case SIGCHLD:
+ chldreap = 1;
+ break;
+ case SIGHUP:
+ reload = 1;
+ break;
+ case SIGTERM:
+ quit = 1;
+ break;
+ }
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-f file] [-n]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+ struct ctabentry *cte;
+ time_t t;
+ struct tm *tm;
+ struct sigaction sa;
+
+ ARGBEGIN {
+ case 'n':
+ nflag = 1;
+ break;
+ case 'f':
+ config = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc > 0)
+ usage();
+
+ if (nflag == 0) {
+ openlog(argv[0], LOG_CONS | LOG_PID, LOG_CRON);
+ if (daemon(1, 0) < 0) {
+ logerr("error: failed to daemonize %s\n", strerror(errno));
+ return 1;
+ }
+ if ((fp = fopen(pidfile, "w"))) {
+ fprintf(fp, "%d\n", getpid());
+ fclose(fp);
+ }
+ }
+
+ sa.sa_handler = sighandler;
+ sigfillset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sigaction(SIGCHLD, &sa, NULL);
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+
+ loadentries();
+
+ while (1) {
+ t = time(NULL);
+ sleep(60 - t % 60);
+
+ if (quit == 1) {
+ if (nflag == 0)
+ unlink(pidfile);
+ unloadentries();
+ /* Don't wait or kill forked processes, just exit */
+ break;
+ }
+
+ if (reload == 1 || chldreap == 1) {
+ if (reload == 1) {
+ reloadentries();
+ reload = 0;
+ }
+ if (chldreap == 1) {
+ waitjob();
+ chldreap = 0;
+ }
+ continue;
+ }
+
+ TAILQ_FOREACH(cte, &ctabhead, entry) {
+ t = time(NULL);
+ tm = localtime(&t);
+ if (matchentry(cte, tm) == 1)
+ runjob(cte->cmd);
+ }
+ }
+
+ if (nflag == 0)
+ closelog();
+
+ return 0;
+}
diff --git a/sbase/crypt.h b/sbase/crypt.h
@@ -0,0 +1,12 @@
+/* See LICENSE file for copyright and license details. */
+struct crypt_ops {
+ void (*init)(void *);
+ void (*update)(void *, const void *, unsigned long);
+ void (*sum)(void *, uint8_t *);
+ void *s;
+};
+
+int cryptcheck(char *, int, char **, struct crypt_ops *, uint8_t *, size_t);
+int cryptmain(int, char **, struct crypt_ops *, uint8_t *, size_t);
+int cryptsum(struct crypt_ops *, FILE *, const char *, uint8_t *);
+void mdprint(const uint8_t *, const char *, size_t);
diff --git a/sbase/cut.1 b/sbase/cut.1
@@ -0,0 +1,70 @@
+.Dd January 18, 2015
+.Dt CUT 1 sbase\-VERSION
+.Sh NAME
+.Nm cut
+.Nd extract columns of data
+.Sh SYNOPSIS
+.Nm cut
+.Fl b Ar list
+.Op Fl n
+.Op Ar file ...
+.Nm cut
+.Fl c Ar list
+.Op Ar file ...
+.Nm cut
+.Fl f Ar list
+.Op Fl d Ar delim
+.Op Fl s
+.Op Ar file ...
+.Sh DESCRIPTION
+.Nm
+out bytes, characters or delimited fields from each line of
+.Ar file
+and write to stdout.
+.Pp
+If no
+.Ar file
+is given or
+.Ar file
+is '-',
+.Nm
+reads from stdin.
+.Pp
+.Ar list
+is a comma or space separated list of numbers and ranges starting
+from 1. Ranges have the form 'N-M'. If N or M is missing,
+beginning or end of line is assumed. Numbers and ranges
+may be repeated, overlapping and in any order.
+.Pp
+Selected input is written in the same order it is read
+and is written exactly once.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl b Ar list | Fl c Ar list
+.Ar list
+specifies byte | character positions.
+.It Fl n
+Do not split multibyte characters. A character is written when its
+last byte is selected.
+.It Fl f Ar list
+.Ar list
+specifies field numbers. Lines not containing field
+delimiters are passed through, unless
+.Fl s
+is specified.
+.It Fl d Ar delim
+Use first byte of
+.Ar delim
+as field delimiter. Default is \et.
+.It Fl s
+Suppress lines not containing field delimiters.
+.El
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
+.Pp
+The possibility of separating numbers and ranges with a space
+is an extension to that specification.
diff --git a/sbase/cut.c b/sbase/cut.c
@@ -0,0 +1,181 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "text.h"
+#include "util.h"
+
+typedef struct Range {
+ size_t min, max;
+ struct Range *next;
+} Range;
+
+static Range *list = NULL;
+static char mode = 0;
+static char delim = '\t';
+static int nflag = 0;
+static int sflag = 0;
+
+static void
+insert(Range *r)
+{
+ Range *l, *p, *t;
+
+ for (p = NULL, l = list; l; p = l, l = l->next) {
+ if (r->max && r->max + 1 < l->min) {
+ r->next = l;
+ break;
+ } else if (!l->max || r->min < l->max + 2) {
+ l->min = MIN(r->min, l->min);
+ for (p = l, t = l->next; t; p = t, t = t->next)
+ if (r->max && r->max + 1 < t->min)
+ break;
+ l->max = (p->max && r->max) ? MAX(p->max, r->max) : 0;
+ l->next = t;
+ return;
+ }
+ }
+ if (p)
+ p->next = r;
+ else
+ list = r;
+}
+
+static void
+parselist(char *str)
+{
+ char *s;
+ size_t n = 1;
+ Range *r;
+
+ for (s = str; *s; s++) {
+ if (*s == ' ')
+ *s = ',';
+ if (*s == ',')
+ n++;
+ }
+ r = emalloc(n * sizeof(Range));
+ for (s = str; n; n--, s++) {
+ r->min = (*s == '-') ? 1 : strtoul(s, &s, 10);
+ r->max = (*s == '-') ? strtoul(s + 1, &s, 10) : r->min;
+ r->next = NULL;
+ if (!r->min || (r->max && r->max < r->min) || (*s && *s != ','))
+ eprintf("cut: bad list value\n");
+ insert(r++);
+ }
+}
+
+static size_t
+seek(const char *s, size_t pos, size_t *prev, size_t count)
+{
+ const char *t;
+ size_t n = pos - *prev;
+
+ if (mode == 'b') {
+ if ((t = memchr(s, 0, n)))
+ return t - s;
+ if (nflag)
+ while (n && !UTF8_POINT(s[n]))
+ n--;
+ *prev += n;
+ return n;
+ } else if (mode == 'c') {
+ for (n++, t = s; *t; t++)
+ if (UTF8_POINT(*t) && !--n)
+ break;
+ } else {
+ for (t = (count < 2) ? s : s + 1; n && *t; t++)
+ if (*t == delim && !--n && count)
+ break;
+ }
+ *prev = pos;
+ return t - s;
+}
+
+static void
+cut(FILE *fp)
+{
+ static char *buf = NULL;
+ static size_t size = 0;
+ char *s;
+ size_t i, n, p;
+ ssize_t len;
+ Range *r;
+
+ while ((len = getline(&buf, &size, fp)) != -1) {
+ if (len && buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+ if (mode == 'f' && !strchr(buf, delim)) {
+ if (!sflag)
+ puts(buf);
+ continue;
+ }
+ for (i = 0, p = 1, s = buf, r = list; r; r = r->next, s += n) {
+ s += seek(s, r->min, &p, i++);
+ if (!*s)
+ break;
+ if (!r->max) {
+ fputs(s, stdout);
+ break;
+ }
+ n = seek(s, r->max + 1, &p, i++);
+ if (fwrite(s, 1, n, stdout) != n)
+ eprintf("write error:");
+ }
+ putchar('\n');
+ }
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: cut -b list [-n] [file ...]\n"
+ " cut -c list [file ...]\n"
+ " cut -f list [-d delim] [-s] [file ...]\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+
+ ARGBEGIN {
+ case 'b':
+ case 'c':
+ case 'f':
+ mode = ARGC();
+ parselist(ARGF());
+ break;
+ case 'd':
+ delim = *ARGF();
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (!mode)
+ usage();
+
+ if (!argc)
+ cut(stdin);
+ else for (; argc--; argv++) {
+ if (!strcmp(*argv, "-"))
+ cut(stdin);
+ else {
+ if (!(fp = fopen(*argv, "r"))) {
+ weprintf("fopen %s:", *argv);
+ continue;
+ }
+ cut(fp);
+ fclose(fp);
+ }
+ }
+ return 0;
+}
diff --git a/sbase/date.1 b/sbase/date.1
@@ -0,0 +1,26 @@
+.TH DATE 1 sbase\-VERSION
+.SH NAME
+date \- print date and time
+.SH SYNOPSIS
+.B date
+.RB [ \-d
+.IR time ]
+.RB [ \-u ]
+.RI [+ format ]
+.SH DESCRIPTION
+.B date
+prints the date and time. If a
+.I format
+is given it is used to format the date as per
+.IR strftime (3).
+.SH OPTIONS
+.TP
+.BI \-d " time"
+prints
+.I time
+instead of the system time, given as the number of seconds since the Unix epoch.
+.TP
+.B \-u
+prints UTC time instead of local time.
+.SH SEE ALSO
+.IR strftime (3)
diff --git a/sbase/date.c b/sbase/date.c
@@ -0,0 +1,46 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-u] [-d format] [+FORMAT]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char buf[BUFSIZ];
+ char *fmt = "%c";
+ struct tm *now = NULL;
+ struct tm *(*tztime)(const time_t *) = localtime;
+ const char *tz = "local";
+ time_t t;
+
+ t = time(NULL);
+ ARGBEGIN {
+ case 'd':
+ t = estrtol(EARGF(usage()), 0);
+ break;
+ case 'u':
+ tztime = gmtime;
+ tz = "gm";
+ break;
+ default:
+ usage();
+ } ARGEND;
+ if (argc > 0 && argv[0][0] == '+')
+ fmt = &argv[0][1];
+ if (!(now = tztime(&t)))
+ eprintf("%stime failed\n", tz);
+
+ strftime(buf, sizeof buf, fmt, now);
+ puts(buf);
+
+ return 0;
+}
diff --git a/sbase/dirname.1 b/sbase/dirname.1
@@ -0,0 +1,14 @@
+.TH DIRNAME 1 sbase\-VERSION
+.SH NAME
+dirname \- strip final path component
+.SH SYNOPSIS
+.B dirname
+.I pathname
+.SH DESCRIPTION
+.B dirname
+prints the
+.I pathname
+with its final path component removed.
+.SH SEE ALSO
+.IR basename (1),
+.IR dirname (3)
diff --git a/sbase/dirname.c b/sbase/dirname.c
@@ -0,0 +1,29 @@
+/* See LICENSE file for copyright and license details. */
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s pathname\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ puts(dirname(argv[0]));
+
+ return 0;
+}
diff --git a/sbase/du.1 b/sbase/du.1
@@ -0,0 +1,37 @@
+.TH DU 1 sbase\-VERSION
+.SH NAME
+du \- display disk usage statistics
+.SH SYNOPSIS
+.B du
+.RB [ \-a
+.RB |
+.BR \-s ]
+.RB [ \-d
+.IR depth ]
+.RB [ \-k ]
+.RB [ \-h ]
+.RI [ file ...]
+.SH DESCRIPTION
+.B du
+displays the file system block usage for each
+.I file
+argument and for each directory in the file hierarchy rooted in directory argument.
+If no file is specified, the block usage of the hierarchy rooted in the current
+directory is displayed.
+.SH OPTIONS
+.TP
+.BI \-a
+Display an entry for each file in the file hierarchy.
+.TP
+.BI \-s
+Display only the grand total for the specified files.
+.TP
+.BI "\-d depth"
+Maximum directory depth to print files and directories.
+.TP
+.BI \-k
+By default all sizes are reported in 512-byte block counts.
+The -k option causes the numbers to be reported in kilobyte counts.
+.TP
+.BI \-h
+Enable human readable output.
diff --git a/sbase/du.c b/sbase/du.c
@@ -0,0 +1,189 @@
+/* See LICENSE file for copyright and license details. */
+#include <dirent.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static long blksize = 512;
+static char file[PATH_MAX];
+static long depth = -1;
+static long curdepth = 0;
+
+static int aflag = 0;
+static int dflag = 0;
+static int sflag = 0;
+static int kflag = 0;
+static int hflag = 0;
+
+static long du(const char *);
+static void print(long n, char *path);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-a | -s] [-d depth] [-h] [-k] [file...]\n", argv0);
+}
+
+static char *
+xrealpath(const char *pathname, char *resolved)
+{
+ char *r;
+
+ r = realpath(pathname, resolved);
+ if (!r)
+ eprintf("realpath: %s:", pathname);
+ return r;
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *bsize;
+ long n;
+
+ ARGBEGIN {
+ case 'a':
+ aflag = 1;
+ break;
+ case 'd':
+ dflag = 1;
+ depth = estrtol(EARGF(usage()), 0);
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 'k':
+ kflag = 1;
+ break;
+ case 'h':
+ hflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if ((aflag && sflag) || (dflag && sflag))
+ usage();
+
+ bsize = getenv("BLOCKSIZE");
+ if (bsize)
+ blksize = estrtol(bsize, 0);
+
+ if (kflag)
+ blksize = 1024;
+
+ if (argc < 1) {
+ n = du(".");
+ if (sflag)
+ print(n, xrealpath(".", file));
+ } else {
+ for (; argc > 0; argc--, argv++) {
+ curdepth = 0;
+ n = du(argv[0]);
+ if (sflag)
+ print(n, xrealpath(argv[0], file));
+ }
+ }
+ return 0;
+}
+
+static void
+print(long n, char *path)
+{
+ if (hflag)
+ printf("%s\t%s\n", humansize(n * blksize), path);
+ else
+ printf("%lu\t%s\n", n, path);
+}
+
+static char *
+push(const char *path)
+{
+ char *cwd;
+
+ cwd = agetcwd();
+ if (chdir(path) < 0)
+ eprintf("chdir: %s:", path);
+ return cwd;
+}
+
+static void
+pop(char *path)
+{
+ if (chdir(path) < 0)
+ eprintf("chdir: %s:", path);
+ free(path);
+}
+
+static long
+nblks(struct stat *st)
+{
+ return (512 * st->st_blocks + blksize - 1) / blksize;
+}
+
+static long
+du(const char *path)
+{
+ DIR *dp;
+ char *cwd;
+ struct dirent *dent;
+ struct stat st;
+ long n = 0, m, t;
+ int r;
+
+ if (lstat(path, &st) < 0)
+ eprintf("stat: %s:", path);
+ n = nblks(&st);
+
+ if (!S_ISDIR(st.st_mode))
+ goto done;
+
+ dp = opendir(path);
+ if (!dp) {
+ weprintf("opendir %s:", path);
+ goto done;
+ }
+
+ cwd = push(path);
+ while ((dent = readdir(dp))) {
+ if (strcmp(dent->d_name, ".") == 0 ||
+ strcmp(dent->d_name, "..") == 0)
+ continue;
+ if (lstat(dent->d_name, &st) < 0)
+ eprintf("stat: %s:", dent->d_name);
+ if (S_ISDIR(st.st_mode)) {
+ t = curdepth;
+ curdepth++;
+ n += du(dent->d_name);
+ curdepth = t;
+ continue;
+ }
+ m = nblks(&st);
+ n += m;
+ if (aflag && !sflag) {
+ if (S_ISLNK(st.st_mode)) {
+ r = snprintf(file, sizeof(file), "%s/%s",
+ cwd, dent->d_name);
+ if (r >= sizeof(file) || r < 0)
+ eprintf("path too long\n");
+ } else {
+ xrealpath(dent->d_name, file);
+ }
+ if (!dflag || (depth != -1 && curdepth < depth))
+ print(m, file);
+ }
+ }
+ pop(cwd);
+ closedir(dp);
+
+done:
+ if (!sflag && (!dflag || (depth != -1 && curdepth <= depth)))
+ print(n, xrealpath(path, file));
+ return n;
+}
diff --git a/sbase/echo.1 b/sbase/echo.1
@@ -0,0 +1,14 @@
+.TH ECHO 1 sbase\-VERSION
+.SH NAME
+echo \- print arguments
+.SH SYNOPSIS
+.B echo
+.RB [ \-n ]
+.RI [ string ...]
+.SH DESCRIPTION
+.B echo
+prints its arguments to stdout, separated by spaces and terminated by a newline.
+.SH OPTIONS
+.TP
+.B \-n
+Do not print terminating newline.
diff --git a/sbase/echo.c b/sbase/echo.c
@@ -0,0 +1,32 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-n] text\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int nflag = 0;
+
+ ARGBEGIN {
+ case 'n':
+ nflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ for (; argc > 0; argc--, argv++)
+ putword(argv[0]);
+ if (!nflag)
+ putchar('\n');
+
+ return 0;
+}
diff --git a/sbase/env.1 b/sbase/env.1
@@ -0,0 +1,41 @@
+.TH ENV 1 sbase\-VERSION
+.SH NAME
+env \- modify the environment, then print it or run a command.
+.SH SYNOPSIS
+.B env
+.RB [ \-i ]
+.RB [ \-u
+.IR name ]...
+.RI [ name=value ]...
+.RI [ cmd
+.RI [ arg ...]]
+
+.SH DESCRIPTION
+.B env
+removes part of the environment according to the flags, then adds or
+sets each variable specified by
+.IR name
+to equal
+.IR value .
+
+If
+.IR cmd
+is given, it is executed in this new environment; otherwise, the
+modified environment is printed to standard out.
+
+.SH OPTIONS
+.TP
+.B \-i
+Comptetely ignore the existing environment; start fresh.
+
+.TP
+.B \-u name
+Unsets
+.IR name
+from the environment.
+
+.SH SEE ALSO
+.IR printenv (1)
+.IR putenv (3)
+.IR environ (7)
+
diff --git a/sbase/env.c b/sbase/env.c
@@ -0,0 +1,45 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+extern char **environ;
+
+static void
+usage(void)
+{
+ eprintf("usage: env [-i] [-u name]... [name=value]... [cmd [arg...]]\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ ARGBEGIN {
+ case 'i':
+ if(environ)
+ *environ = NULL;
+ break;
+ case 'u':
+ unsetenv(EARGF(usage()));
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ for (; *argv && strchr(*argv, '='); argv++)
+ putenv(*argv);
+
+ if (*argv) {
+ execvp(*argv, argv);
+ enprintf(127 - (errno != EEXIST), "env: '%s':", *argv);
+ }
+
+ while (environ && *environ)
+ printf("%s\n", *environ++);
+
+ return 0;
+}
diff --git a/sbase/expand.1 b/sbase/expand.1
@@ -0,0 +1,25 @@
+.TH EXPAND 1 sbase\-VERSION
+.SH NAME
+expand \- expand tabs to spaces
+.SH SYNOPSIS
+.B expand
+.RB [ \-t
+.IR n ]
+.RI [ file ...]
+.SH DESCRIPTION
+expand processes the named files or the standard input, writing the
+standard output with tabs changed into spaces. Backspace characters
+are preserved into the output and decrement the column count for tab
+calculations.
+.SH OPTIONS
+.TP
+.BI \-i
+Only change tabs to spaces at the start of lines.
+.TP
+.BI \-t " n"
+Expand tabs to
+.I n
+spaces. We currently support only a single numerical argument.
+.SH SEE ALSO
+.IR unexpand (1),
+.IR fold (1)
diff --git a/sbase/expand.c b/sbase/expand.c
@@ -0,0 +1,98 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utf.h"
+#include "util.h"
+
+static int expand(const char *, FILE *, int);
+
+static int iflag = 0;
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-i] [-t n] [file...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+ int tabstop = 8;
+ int ret = 0;
+
+ ARGBEGIN {
+ case 'i':
+ iflag = 1;
+ break;
+ case 't':
+ tabstop = estrtol(EARGF(usage()), 0);
+ if (!tabstop)
+ eprintf("tab size cannot be zero\n");
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0) {
+ expand("<stdin>", stdin, tabstop);
+ } else {
+ for (; argc > 0; argc--, argv++) {
+ if (!(fp = fopen(argv[0], "r"))) {
+ weprintf("fopen %s:", argv[0]);
+ ret = 1;
+ continue;
+ }
+ expand(argv[0], fp, tabstop);
+ fclose(fp);
+ }
+ }
+ return ret;
+}
+
+static int
+expand(const char *file, FILE *fp, int tabstop)
+{
+ int col = 0;
+ Rune r;
+ int bol = 1;
+
+ for (;;) {
+ if (!readrune(file, fp, &r))
+ break;
+
+ switch (r) {
+ case '\t':
+ if (bol || !iflag) {
+ do {
+ col++;
+ putchar(' ');
+ } while (col % tabstop);
+ } else {
+ putchar('\t');
+ col += tabstop - col % tabstop;
+ }
+ break;
+ case '\b':
+ if (col)
+ col--;
+ bol = 0;
+ writerune("<stdout>", stdout, &r);
+ break;
+ case '\n':
+ col = 0;
+ bol = 1;
+ writerune("<stdout>", stdout, &r);
+ break;
+ default:
+ col++;
+ if (r != ' ')
+ bol = 0;
+ writerune("<stdout>", stdout, &r);
+ break;
+ }
+ }
+
+ return 0;
+}
diff --git a/sbase/expr.1 b/sbase/expr.1
@@ -0,0 +1,156 @@
+.\" $OpenBSD: src/bin/expr/expr.1,v 1.22 2014/02/23 18:13:27 schwarze Exp $
+.\" $NetBSD: expr.1,v 1.9 1995/04/28 23:27:13 jtc Exp $
+.\"
+.\" Written by J.T. Conklin <jtc@netbsd.org>.
+.\" Public domain.
+.\"
+.Dd $Mdocdate: September 3 2010 $
+.Dt EXPR 1
+.Os
+.Sh NAME
+.Nm expr
+.Nd evaluate expression
+.Sh SYNOPSIS
+.Nm expr
+.Ar expression
+.Sh DESCRIPTION
+The
+.Nm
+utility evaluates
+.Ar expression
+and writes the result on standard output.
+All operators are separate arguments to the
+.Nm
+utility.
+Characters special to the command interpreter must be escaped.
+.Pp
+Operators are listed below in order of increasing precedence.
+Operators with equal precedence are grouped within { } symbols.
+.Bl -tag -width indent
+.It Ar expr1 | expr2
+Returns the evaluation of
+.Ar expr1
+if it is neither an empty string nor zero;
+otherwise, returns the evaluation of
+.Ar expr2 .
+.It Ar expr1 Li & Ar expr2
+Returns the evaluation of
+.Ar expr1
+if neither expression evaluates to an empty string or zero;
+otherwise, returns zero.
+.It Ar expr1 Li "{=, >, >=, <, <=, !=}" Ar expr2
+Returns the results of integer comparison if both arguments are integers;
+otherwise, returns the results of string comparison using the locale-specific
+collation sequence.
+The result of each comparison is 1 if the specified relation is true,
+or 0 if the relation is false.
+.It Ar expr1 Li "{+, -}" Ar expr2
+Returns the results of addition or subtraction of integer-valued arguments.
+.It Ar expr1 Li "{*, /, %}" Ar expr2
+Returns the results of multiplication, integer division, or remainder of
+integer-valued arguments.
+.It Ar expr1 Li : Ar expr2
+The
+.Ql \&:
+operator matches
+.Ar expr1
+against
+.Ar expr2 ,
+which must be a basic regular expression.
+The regular expression is anchored
+to the beginning of the string with an implicit
+.Ql ^ .
+.Pp
+If the match succeeds and the pattern contains at least one regular
+expression subexpression
+.Dq "\e(...\e)" ,
+the string corresponding to
+.Dq "\e1"
+is returned;
+otherwise, the matching operator returns the number of characters matched.
+If the match fails and the pattern contains a regular expression subexpression
+the null string is returned;
+otherwise, returns 0.
+.Pp
+Note: the empty string cannot be matched using
+.Bd -literal -offset indent
+expr '' : '$'
+.Ed
+.Pp
+This is because the returned number of matched characters
+.Pq zero
+is indistinguishable from a failed match, so
+.Nm
+returns failure
+.Pq 0 .
+To match the empty string, use a structure such as:
+.Bd -literal -offset indent
+expr X'' : 'X$'
+.Ed
+.El
+.Pp
+Parentheses are used for grouping in the usual manner.
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It 0
+The expression is neither an empty string nor 0.
+.It 1
+The expression is an empty string or 0.
+.It 2
+The expression is invalid.
+.It \*(Gt2
+An error occurred (such as memory allocation failure).
+.El
+.Sh EXAMPLES
+Add 1 to the variable
+.Va a :
+.Bd -literal -offset indent
+$ a=`expr $a + 1`
+.Ed
+.Pp
+Return the filename portion of a pathname stored
+in variable
+.Va a .
+The
+.Ql //
+characters act to eliminate ambiguity with the division operator:
+.Bd -literal -offset indent
+$ expr "//$a" \&: '.*/\e(.*\e)'
+.Ed
+.Pp
+Return the number of characters in variable
+.Va a :
+.Bd -literal -offset indent
+$ expr $a \&: '.*'
+.Ed
+.Sh SEE ALSO
+.Xr test 1 ,
+.Xr re_format 7
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
+.Sh HISTORY
+The
+.Nm
+utility first appeared in the Programmer's Workbench (PWB/UNIX)
+and has supported regular expressions since
+.At v7 .
+It was rewritten from scratch for
+.Bx 386 0.1
+and again for
+.Nx 1.1 .
+.Sh AUTHORS
+.An -nosplit
+The first free version was written by
+.An Pace Willisson
+in 1992.
+This version was written by
+.An John T. Conklin
+in 1994.
diff --git a/sbase/expr.c b/sbase/expr.c
@@ -0,0 +1,287 @@
+/* See LICENSE file for copyright and license details. */
+#include <inttypes.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+enum {
+ VAL = CHAR_MAX + 1, GE, LE, NE
+};
+
+typedef struct {
+ char *s;
+ intmax_t n;
+} Val;
+
+static void enan(Val);
+static void ezero(intmax_t);
+static void doop(int*, int**, Val*, Val**);
+static Val match(Val, Val);
+static int valcmp(Val, Val);
+static char *valstr(Val, char *, size_t);
+static int lex(char *);
+static int parse(char **, int);
+
+static size_t intlen;
+static Val lastval;
+
+static void
+enan(Val v)
+{
+ if (v.s)
+ enprintf(2, "syntax error: expected integer got `%s'\n", v.s);
+}
+
+static void
+ezero(intmax_t n)
+{
+ if (n == 0)
+ enprintf(2, "division by zero\n");
+}
+
+static void
+doop(int *op, int **opp, Val *val, Val **valp)
+{
+ Val ret, a, b;
+ int o;
+
+ /* For an operation, we need a valid operator
+ * and two values on the stack */
+ if ((*opp)[-1] == '(')
+ enprintf(2, "syntax error: extra (\n");
+ if (*valp - val < 2)
+ enprintf(2, "syntax error: missing expression or extra operator\n");
+
+ a = (*valp)[-2];
+ b = (*valp)[-1];
+ o = (*opp)[-1];
+
+ switch (o) {
+ case '|':
+ if (a.s && *a.s) {
+ ret = (Val){ a.s, 0 };
+ } else if (!a.s && a.n) {
+ ret = (Val){ NULL, a.n };
+ } else if (b.s && *b.s) {
+ ret = (Val){ b.s, 0 };
+ } else {
+ ret = (Val){ NULL, b.n };
+ }
+ break;
+ case '&':
+ if (((a.s && *a.s) || a.n) &&
+ ((b.s && *b.s) || b.n)) {
+ ret = a;
+ } else {
+ ret = (Val){ NULL, 0 };
+ }
+ break;
+ case '=': ret = (Val){ NULL, valcmp(a, b) == 0 }; break;
+ case '>': ret = (Val){ NULL, valcmp(a, b) > 0 }; break;
+ case GE : ret = (Val){ NULL, valcmp(a, b) >= 0 }; break;
+ case '<': ret = (Val){ NULL, valcmp(a, b) < 0 }; break;
+ case LE : ret = (Val){ NULL, valcmp(a, b) <= 0 }; break;
+ case NE : ret = (Val){ NULL, valcmp(a, b) != 0 }; break;
+
+ case '+': enan(a); enan(b); ret = (Val){ NULL, a.n + b.n }; break;
+ case '-': enan(a); enan(b); ret = (Val){ NULL, a.n - b.n }; break;
+ case '*': enan(a); enan(b); ret = (Val){ NULL, a.n * b.n }; break;
+ case '/': enan(a); enan(b); ezero(b.n); ret = (Val){ NULL, a.n / b.n }; break;
+ case '%': enan(a); enan(b); ezero(b.n); ret = (Val){ NULL, a.n % b.n }; break;
+
+ case ':': ret = match(a, b); break;
+ }
+
+ (*valp)[-2] = ret;
+ (*opp)--;
+ (*valp)--;
+}
+
+static Val
+match(Val vstr, Val vregx)
+{
+ intmax_t d;
+ char *anchreg, *ret, *p;
+ char buf1[intlen], buf2[intlen], *str, *regx;
+ regoff_t len;
+ regex_t re;
+ regmatch_t matches[2];
+
+ str = valstr(vstr, buf1, sizeof(buf1));
+ regx = valstr(vregx, buf2, sizeof(buf2));
+
+ anchreg = malloc(strlen(regx) + 2);
+ if (!anchreg)
+ enprintf(3, "malloc:");
+ snprintf(anchreg, strlen(regx) + 2, "^%s", regx);
+
+ enregcomp(3, &re, anchreg, 0);
+ free(anchreg);
+
+ if (regexec(&re, str, 2, matches, 0)) {
+ regfree(&re);
+ return (Val){ (re.re_nsub ? "" : NULL), 0 };
+ }
+
+ if (re.re_nsub) {
+ regfree(&re);
+ len = matches[1].rm_eo - matches[1].rm_so + 1;
+ ret = malloc(len);
+ if (!ret)
+ enprintf(3, "malloc:");
+ strlcpy(ret, str + matches[1].rm_so, len);
+ d = strtoimax(ret, &p, 10);
+ if (*ret && !*p) {
+ free(ret);
+ return (Val){ NULL, d };
+ }
+ return (Val){ ret, 0 };
+ }
+ regfree(&re);
+ return (Val){ NULL, matches[0].rm_eo - matches[0].rm_so };
+}
+
+
+
+static int
+valcmp(Val a, Val b)
+{
+ char buf1[intlen], buf2[intlen], *astr, *bstr;
+
+ astr = valstr(a, buf1, sizeof(buf1));
+ bstr = valstr(b, buf2, sizeof(buf2));
+
+ return strcmp(astr, bstr);
+}
+
+static char *
+valstr(Val val, char *buf, size_t bufsiz)
+{
+ if (val.s)
+ return val.s;
+ snprintf(buf, bufsiz, "%"PRIdMAX, val.n);
+ return buf;
+}
+
+static int
+lex(char *p)
+{
+ intmax_t d;
+ char *q, *ops = "|&=><+-*/%():";
+
+ /* clean integer */
+ d = strtoimax(p, &q, 10);
+ if (*p && !*q) {
+ lastval = (Val){ NULL, d };
+ return VAL;
+ }
+
+ /* one-char operand */
+ if (*p && !*(p+1) && strchr(ops, *p))
+ return *p;
+
+ /* two-char operand */
+ if (*p && *(p+1) == '=' && !*(p+2)) {
+ switch (*p) {
+ case '>':
+ return GE;
+ case '<':
+ return LE;
+ case '!':
+ return NE;
+ }
+ }
+
+ /* nothing matched, treat as string */
+ lastval = (Val){ p, 0 };
+ return VAL;
+}
+
+static int
+parse(char **expr, int exprlen)
+{
+ Val val[exprlen], *valp = val;
+ int op[exprlen], *opp = op;
+ int i, type, lasttype = 0;
+ char prec[] = {
+ [ 0 ] = 0, [VAL] = 0,
+ ['|'] = 1,
+ ['&'] = 2,
+ ['='] = 3, ['>'] = 3, [GE] = 3, ['<'] = 3, [LE] = 3, [NE] = 3,
+ ['+'] = 4, ['-'] = 4,
+ ['*'] = 5, ['/'] = 5, ['%'] = 5,
+ [':'] = 6,
+ };
+
+ for (i = 0; i < exprlen; i++) {
+ type = lex(expr[i]);
+
+ switch (type) {
+ case VAL:
+ *valp++ = lastval;
+ break;
+ case '(':
+ *opp++ = '(';
+ break;
+ case ')':
+ if (lasttype == '(')
+ enprintf(2, "syntax error: empty ( )\n");
+ while (opp > op && opp[-1] != '(')
+ doop(op, &opp, val, &valp);
+ if (opp == op)
+ enprintf(2, "syntax error: extra )\n");
+ opp--;
+ break;
+ default :
+ if (prec[lasttype])
+ enprintf(2, "syntax error: extra operator\n");
+ while (opp > op && prec[opp[-1]] >= prec[type])
+ doop(op, &opp, val, &valp);
+ *opp++ = type;
+ break;
+ }
+ lasttype = type;
+ }
+ while (opp > op)
+ doop(op, &opp, val, &valp);
+
+ if (valp == val)
+ enprintf(2, "syntax error: missing expression\n");
+ if (valp - val > 1)
+ enprintf(2, "syntax error: extra expression\n");
+
+ valp--;
+ if (valp->s)
+ printf("%s\n", valp->s);
+ else
+ printf("%"PRIdMAX"\n", valp->n);
+
+ return (valp->s && *valp->s) || valp->n;
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s EXPRESSION\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ intmax_t n = INTMAX_MIN;
+
+ /* Get the maximum number of digits (+ sign) */
+ for (intlen = (n < 0); n; n /= 10, ++intlen);
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ return !parse(argv, argc);
+}
diff --git a/sbase/false.1 b/sbase/false.1
@@ -0,0 +1,16 @@
+.Dd January 16, 2015
+.Dt FALSE 1 sbase\-VERSION
+.Sh NAME
+.Nm false
+.Nd return failure
+.Sh SYNOPSIS
+.Nm false
+.Sh DESCRIPTION
+.Nm
+returns a status code indicating failure.
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
diff --git a/sbase/false.c b/sbase/false.c
@@ -0,0 +1,6 @@
+/* See LICENSE file for copyright and license details. */
+int
+main(void)
+{
+ return 1;
+}
diff --git a/sbase/fold.1 b/sbase/fold.1
@@ -0,0 +1,25 @@
+.TH FOLD 1 sbase\-VERSION
+.SH NAME
+fold \- wrap lines to width
+.SH SYNOPSIS
+.B fold
+.RB [ \-bs ]
+.RB [ \-w
+.IR width ]
+.RI [ file ...]
+.SH DESCRIPTION
+.B fold
+reads each file in sequence and prints its lines, broken such that no line
+exceeds 80 UTF-8 characters. If no file is given, fold reads from stdin.
+.SH OPTIONS
+.TP
+.B \-b
+counts bytes rather than characters.
+.TP
+.B \-s
+breaks only at spaces.
+.TP
+.BI \-w " width"
+breaks at
+.I width
+characters, instead of 80.
diff --git a/sbase/fold.c b/sbase/fold.c
@@ -0,0 +1,113 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "text.h"
+#include "util.h"
+
+static void fold(FILE *, long);
+static void foldline(const char *, long);
+
+static int bflag = 0;
+static int sflag = 0;
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-bs] [-w width] [FILE...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ long width = 80;
+ FILE *fp;
+
+ ARGBEGIN {
+ case 'b':
+ bflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 'w':
+ width = estrtol(EARGF(usage()), 0);
+ break;
+ ARGNUM:
+ width = ARGNUMF(0);
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0) {
+ fold(stdin, width);
+ } else {
+ for (; argc > 0; argc--, argv++) {
+ if (!(fp = fopen(argv[0], "r"))) {
+ weprintf("fopen %s:", argv[0]);
+ continue;
+ }
+ fold(fp, width);
+ fclose(fp);
+ }
+ }
+
+ return 0;
+}
+
+static void
+fold(FILE *fp, long width)
+{
+ char *buf = NULL;
+ size_t size = 0;
+
+ while (getline(&buf, &size, fp) != -1)
+ foldline(buf, width);
+ free(buf);
+}
+
+static void
+foldline(const char *str, long width)
+{
+ int space;
+ long col, j;
+ size_t i = 0, n = 0;
+ int c;
+
+ do {
+ space = 0;
+ for (j = i, col = 0; str[j] && col <= width; j++) {
+ c = str[j];
+ if (!UTF8_POINT(c) && !bflag)
+ continue;
+ if (sflag && isspace(c)) {
+ space = 1;
+ n = j+1;
+ }
+ else if (!space)
+ n = j;
+
+ if (!bflag && iscntrl(c))
+ switch(c) {
+ case '\b':
+ col--;
+ break;
+ case '\r':
+ col = 0;
+ break;
+ case '\t':
+ col += (col+1) % 8;
+ break;
+ }
+ else
+ col++;
+ }
+ if (fwrite(&str[i], 1, n-i, stdout) != n-i)
+ eprintf("<stdout>: write error:");
+ if (str[n])
+ putchar('\n');
+ } while (str[i = n] && str[i] != '\n');
+}
diff --git a/sbase/fs.h b/sbase/fs.h
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+extern int cp_aflag;
+extern int cp_fflag;
+extern int cp_Pflag;
+extern int cp_pflag;
+extern int cp_rflag;
+extern int cp_vflag;
+extern int cp_status;
+
+extern int rm_fflag;
+extern int rm_rflag;
+
+int cp(const char *, const char *);
+void rm(const char *);
diff --git a/sbase/grep.1 b/sbase/grep.1
@@ -0,0 +1,86 @@
+.Dd November 21, 2014
+.Dt GREP 1 sbase\-VERSION
+.Sh NAME
+.Nm grep
+.Nd search files for patterns
+.Sh SYNOPSIS
+.Nm grep
+.Op Fl EFHchilnqsvx
+.Op Fl e Ar pattern
+.Op Fl f Ar file
+.Op Ar pattern
+.Op Ar file ...
+.Sh DESCRIPTION
+.Nm
+searches the input files for lines that match the
+.Ar pattern ,
+a regular expression as defined in
+.Xr regex 7 .
+By default each matching line is printed to stdout. If no file is given
+.Nm
+reads from stdin.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl E
+Match using extended regex.
+.It Fl F
+Match using fixed strings. Treat each pattern specified as a string instead of a regular
+expression.
+.It Fl H
+Prefix each matching line with its filename in the output. This is the
+default when there is more than one file specified.
+.It Fl c
+Print only a count of matching lines.
+.It Fl e Ar pattern
+Specify a pattern used during the search of the input: an input
+line is selected if it matches any of the specified patterns.
+This option is most useful when multiple -e options are used to
+specify multiple patterns, or when a pattern begins with a dash
+.It Fl f Ar file
+Read one or more patterns from the file named by the pathname file.
+Patterns in file shall be terminated by a <newline>. A null pattern can be
+specified by an empty line in pattern_file. Unless the -E or -F option is
+also specified, each pattern shall be treated as a BRE.
+(`-').
+.It Fl h
+Do not prefix each line with 'filename:' prefix.
+.It Fl i
+Match lines case insensitively.
+.It Fl l
+Print only the names of files with matching lines.
+.It Fl n
+Prefix each matching line with its line number in the input.
+.It Fl q
+Print nothing, only return status.
+.It Fl s
+Suppress the error messages ordinarily written for nonexistent or unreadable files.
+.It Fl v
+Select lines which do
+.B not
+Match the pattern.
+.It Fl x
+Consider only input lines that use all characters in the line excluding the terminating <newline> to
+match an entire fixed string or regular expression to be matching lines.
+.El
+.Sh EXIT STATUS
+.Bl -tag -width Ds
+.It 0
+One or more lines were matched.
+.It 1
+No lines were matched.
+.It > 1
+An error occurred.
+.El
+.Sh SEE ALSO
+.Xr regex 7 ,
+.Xr sed 1
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
+.Pp
+The flags
+.Op Fl Hh
+are an extension to that specification.
diff --git a/sbase/grep.c b/sbase/grep.c
@@ -0,0 +1,255 @@
+/* See LICENSE file for copyright and license details. */
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "queue.h"
+#include "text.h"
+#include "util.h"
+
+enum { Match = 0, NoMatch = 1, Error = 2 };
+
+static void addpattern(const char *);
+static void addpatternfile(FILE *);
+static int grep(FILE *, const char *);
+
+static int Fflag;
+static int Hflag;
+static int eflag;
+static int fflag;
+static int hflag;
+static int iflag;
+static int sflag;
+static int vflag;
+static int xflag;
+static int many;
+static int mode;
+
+struct pattern {
+ char *pattern;
+ regex_t preg;
+ SLIST_ENTRY(pattern) entry;
+};
+
+static SLIST_HEAD(phead, pattern) phead;
+
+static void
+usage(void)
+{
+ enprintf(Error, "usage: %s [-EFHchilnqsvx] [-e pattern] [-f file] [pattern] [file ...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct pattern *pnode;
+ int i, m, flags = REG_NOSUB, match = NoMatch;
+ FILE *fp;
+ char *arg;
+
+ SLIST_INIT(&phead);
+
+ ARGBEGIN {
+ case 'E':
+ flags |= REG_EXTENDED;
+ break;
+ case 'F':
+ Fflag = 1;
+ break;
+ case 'H':
+ Hflag = 1;
+ hflag = 0;
+ break;
+ case 'e':
+ arg = EARGF(usage());
+ fp = fmemopen(arg, strlen(arg) + 1, "r");
+ addpatternfile(fp);
+ fclose(fp);
+ eflag = 1;
+ break;
+ case 'f':
+ arg = EARGF(usage());
+ fp = fopen(arg, "r");
+ if (!fp)
+ enprintf(Error, "fopen %s:", arg);
+ addpatternfile(fp);
+ fclose(fp);
+ fflag = 1;
+ break;
+ case 'h':
+ hflag = 1;
+ Hflag = 0;
+ break;
+ case 'c':
+ case 'l':
+ case 'n':
+ case 'q':
+ mode = ARGC();
+ break;
+ case 'i':
+ flags |= REG_ICASE;
+ iflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ case 'x':
+ xflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0 && !eflag && !fflag)
+ usage(); /* no pattern */
+
+ /* just add literal pattern to list */
+ if (!eflag && !fflag) {
+ fp = fmemopen(argv[0], strlen(argv[0]) + 1, "r");
+ addpatternfile(fp);
+ fclose(fp);
+ argc--;
+ argv++;
+ }
+
+ if (!Fflag)
+ /* Compile regex for all search patterns */
+ SLIST_FOREACH(pnode, &phead, entry)
+ enregcomp(Error, &pnode->preg, pnode->pattern, flags);
+ many = (argc > 1);
+ if (argc == 0) {
+ match = grep(stdin, "<stdin>");
+ } else {
+ for (i = 0; i < argc; i++) {
+ if (!(fp = fopen(argv[i], "r"))) {
+ if (!sflag)
+ weprintf("fopen %s:", argv[i]);
+ match = Error;
+ continue;
+ }
+ m = grep(fp, argv[i]);
+ if (m == Error || (match != Error && m == Match))
+ match = m;
+ fclose(fp);
+ }
+ }
+ return match;
+}
+
+static void
+addpattern(const char *pattern)
+{
+ struct pattern *pnode;
+ char *tmp;
+
+ /* a null BRE/ERE matches every line */
+ if (!Fflag)
+ if (pattern[0] == '\0')
+ pattern = ".";
+
+ if (!Fflag && xflag) {
+ tmp = malloc(strlen(pattern) + 3);
+ if (!tmp)
+ enprintf(Error, "malloc:");
+ snprintf(tmp, strlen(pattern) + 3, "%s%s%s",
+ pattern[0] == '^' ? "" : "^",
+ pattern,
+ pattern[strlen(pattern) - 1] == '$' ? "" : "$");
+ } else {
+ tmp = strdup(pattern);
+ if (!tmp)
+ enprintf(Error, "strdup:");
+ }
+
+ pnode = malloc(sizeof(*pnode));
+ if (!pnode)
+ enprintf(Error, "malloc:");
+ pnode->pattern = tmp;
+ SLIST_INSERT_HEAD(&phead, pnode, entry);
+}
+
+static void
+addpatternfile(FILE *fp)
+{
+ static char *buf = NULL;
+ static size_t size = 0;
+ size_t len = 0;
+
+ while ((len = getline(&buf, &size, fp)) != -1) {
+ if (len && buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+ addpattern(buf);
+ }
+ if (ferror(fp))
+ enprintf(Error, "read error:");
+}
+
+static int
+grep(FILE *fp, const char *str)
+{
+ static char *buf = NULL;
+ static size_t size = 0;
+ size_t len = 0;
+ long c = 0, n;
+ struct pattern *pnode;
+ int match = NoMatch;
+
+ for (n = 1; (len = getline(&buf, &size, fp)) != -1; n++) {
+ /* Remove the trailing newline if one is present. */
+ if (len && buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+ SLIST_FOREACH(pnode, &phead, entry) {
+ if (!Fflag) {
+ if (regexec(&pnode->preg, buf[0] == '\0' ? "\n" : buf, 0, NULL, 0) ^ vflag)
+ continue;
+ } else {
+ if (!xflag) {
+ if ((iflag ? strcasestr : strstr)(buf, pnode->pattern))
+ match = Match;
+ else
+ match = NoMatch;
+ } else {
+ if (!(iflag ? strcasecmp : strcmp)(buf, pnode->pattern))
+ match = Match;
+ else
+ match = NoMatch;
+ }
+ if (match ^ vflag)
+ continue;
+ }
+ switch (mode) {
+ case 'c':
+ c++;
+ break;
+ case 'l':
+ puts(str);
+ goto end;
+ case 'q':
+ exit(Match);
+ default:
+ if (!hflag && (many || Hflag))
+ printf("%s:", str);
+ if (mode == 'n')
+ printf("%ld:", n);
+ puts(buf);
+ break;
+ }
+ match = Match;
+ break;
+ }
+ }
+ if (mode == 'c')
+ printf("%ld\n", c);
+end:
+ if (ferror(fp)) {
+ weprintf("%s: read error:", str);
+ match = Error;
+ }
+ return match;
+}
diff --git a/sbase/head.1 b/sbase/head.1
@@ -0,0 +1,18 @@
+.TH HEAD 1 sbase\-VERSION
+.SH NAME
+head \- output first part of files
+.SH SYNOPSIS
+.B head
+.RB [ \-n
+.IR lines ]
+.RI [ file ...]
+.SH DESCRIPTION
+.B head
+writes the first 10 lines of each file to stdout. If no file is given, head
+reads from stdin.
+.SH OPTIONS
+.TP
+.BI \-n " lines"
+outputs the given number of lines.
+.SH SEE ALSO
+.IR tail (1)
diff --git a/sbase/head.c b/sbase/head.c
@@ -0,0 +1,74 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "text.h"
+#include "util.h"
+
+static void head(FILE *, const char *, long);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-n lines] [file...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ long n = 10;
+ FILE *fp;
+ int ret = 0;
+ int newline, many;
+
+ ARGBEGIN {
+ case 'n':
+ n = estrtol(EARGF(usage()), 0);
+ break;
+ ARGNUM:
+ n = ARGNUMF(0);
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0) {
+ head(stdin, "<stdin>", n);
+ } else {
+ many = argc > 1;
+ for (newline = 0; argc > 0; argc--, argv++) {
+ if (!(fp = fopen(argv[0], "r"))) {
+ weprintf("fopen %s:", argv[0]);
+ ret = 1;
+ continue;
+ }
+ if (many)
+ printf("%s==> %s <==\n",
+ newline ? "\n" : "", argv[0]);
+ newline = 1;
+ head(fp, argv[0], n);
+ fclose(fp);
+ }
+ }
+ return ret;
+}
+
+static void
+head(FILE *fp, const char *str, long n)
+{
+ char *buf = NULL;
+ size_t size = 0;
+ ssize_t len;
+ unsigned long i = 0;
+
+ while (i < n && ((len = getline(&buf, &size, fp)) != -1)) {
+ fputs(buf, stdout);
+ if (buf[len - 1] == '\n')
+ i++;
+ }
+ free(buf);
+ if (ferror(fp))
+ eprintf("%s: read error:", str);
+}
diff --git a/sbase/hostname.1 b/sbase/hostname.1
@@ -0,0 +1,12 @@
+.TH HOSTNAME 1 sbase\-VERSION
+.SH NAME
+hostname \- set or print name of current host system
+.SH SYNOPSIS
+.B hostname
+.I [name]
+.SH DESCRIPTION
+.B hostname
+sets or prints the name of the current host. If no argument is given,
+the name of the current host is printed.
+.SH SEE ALSO
+.IR hostname (7)
diff --git a/sbase/hostname.c b/sbase/hostname.c
@@ -0,0 +1,34 @@
+/* See LICENSE file for copyright and license details. */
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [name]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char host[HOST_NAME_MAX + 1];
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1) {
+ if (gethostname(host, sizeof(host)) < 0)
+ eprintf("gethostname:");
+ puts(host);
+ } else {
+ if (sethostname(argv[0], strlen(argv[0])) < 0)
+ eprintf("sethostname:");
+ }
+ return 0;
+}
diff --git a/sbase/kill.1 b/sbase/kill.1
@@ -0,0 +1,59 @@
+.Dd November 23, 2014
+.Dt KILL 1 sbase\-VERSION
+.Os
+.Sh NAME
+.Nm kill
+.Nd signal processes
+.Sh SYNOPSIS
+.Nm kill
+.Op Fl s Ar signal_name
+.Ar pid ...
+.Nm kill
+.Fl l Op Ar exit_status
+.Nm kill
+.Fl Ar signal_name
+.Ar pid ...
+.Nm kill
+.Fl Ar signal_number
+.Ar pid ...
+.Sh DESCRIPTION
+.Nm
+by default sends a
+.I TERM
+signal to the given processes.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl s Ar signal_name
+A symbolic signal name specifying the signal to be sent instead
+of the default SIGTERM.
+Sends the named signal.
+.It Fl l Op Ar exit_status
+Lists available signals. If an
+.Ar exit_status
+is given, only the corresponding signal name will be printed.
+.It Fl signal_name
+A symbolic signal name specifying the signal to be sent instead
+of the default SIGTERM.
+.It Fl signal_number
+A non-negative decimal integer specifying the signal to be sent
+instead of the default SIGTERM.
+.El
+.Sh SEE ALSO
+.Xr kill 2 ,
+.Xr signal 7
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
+.Pp
+The
+.Fl Ar signal_name
+and
+.Fl Ar signal_number
+syntax is marked by
+.St -p1003.1-2008
+as being an
+X/OPEN System Interfaces
+option.
diff --git a/sbase/kill.c b/sbase/kill.c
@@ -0,0 +1,138 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "util.h"
+
+struct {
+ const char *name;
+ int sig;
+} sigs[] = {
+ { "0", 0 },
+#define SIG(n) { #n, SIG##n }
+ SIG(ABRT), SIG(ALRM), SIG(BUS), SIG(CHLD), SIG(CONT), SIG(FPE), SIG(HUP),
+ SIG(ILL), SIG(INT), SIG(KILL), SIG(PIPE), SIG(QUIT), SIG(SEGV), SIG(STOP),
+ SIG(TERM), SIG(TSTP), SIG(TTIN), SIG(TTOU), SIG(USR1), SIG(USR2), SIG(URG),
+#undef SIG
+};
+
+const char *sig2name(int);
+int name2sig(const char *);
+
+static void
+usage(void)
+{
+ weprintf("usage: %s [-s signame | -signum | -signame] pid ...\n", argv0);
+ weprintf(" %s -l [exit_status]\n", argv0);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *end;
+ int ret = 0;
+ int sig = SIGTERM;
+ pid_t pid;
+ size_t i;
+
+ argv0 = argv[0];
+ if (argc < 2)
+ usage();
+
+ argc--, argv++;
+ if (strcmp(argv[0], "-l") == 0) {
+ argc--, argv++;
+ if (argc == 0) {
+ for (i = 0; i < LEN(sigs); i++)
+ puts(sigs[i].name);
+ exit(0);
+ } else if (argc > 1)
+ usage();
+ errno = 0;
+ sig = strtol(argv[0], &end, 10);
+ if (*end != '\0' || errno != 0)
+ eprintf("%s: bad signal number\n", argv[0]);
+ if (sig > 128)
+ sig = WTERMSIG(sig);
+ puts(sig2name(sig));
+ exit(0);
+ }
+
+ if (strcmp(argv[0], "-s") == 0) {
+ argc--, argv++;
+ if (argc == 0)
+ usage();
+ sig = name2sig(argv[0]);
+ argc--, argv++;
+ } else if (argv[0][0] == '-') {
+ if (isdigit(argv[0][1])) {
+ /* handle XSI extension -signum */
+ errno = 0;
+ sig = strtol(&argv[0][1], &end, 10);
+ if (*end != '\0' || errno != 0)
+ eprintf("%s: bad signal number\n", &argv[0][1]);
+ sig2name(sig);
+ argc--, argv++;
+ } else if (argv[0][1] != '-') {
+ /* handle XSI extension -signame */
+ sig = name2sig(&argv[0][1]);
+ argc--, argv++;
+ }
+ }
+
+ if (argc > 0 && strcmp(argv[0], "--") == 0)
+ argc--, argv++;
+
+ if (argc == 0)
+ usage();
+
+ for (; argc; argc--, argv++) {
+ errno = 0;
+ pid = strtol(argv[0], &end, 10);
+ if (*end == '\0' && errno == 0) {
+ if (kill(pid, sig) < 0) {
+ weprintf("kill %d:", pid);
+ ret = 1;
+ }
+ } else {
+ weprintf("%s: bad pid\n", argv[0]);
+ ret = 1;
+ }
+ }
+
+ exit(ret);
+}
+
+const char *
+sig2name(int sig)
+{
+ size_t i;
+
+ for (i = 0; i < LEN(sigs); i++)
+ if (sigs[i].sig == sig)
+ return sigs[i].name;
+ eprintf("%d: bad signal number\n", sig);
+ /* unreachable */
+ return NULL;
+}
+
+int
+name2sig(const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < LEN(sigs); i++)
+ if (strcasecmp(sigs[i].name, name) == 0)
+ return sigs[i].sig;
+ eprintf("%s: bad signal name\n", name);
+ /* unreachable */
+ return -1;
+}
diff --git a/sbase/libutf/chartorunearr.c b/sbase/libutf/chartorunearr.c
@@ -0,0 +1,27 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdlib.h>
+#include <string.h>
+
+#include "../util.h"
+#include "../utf.h"
+
+int
+chartorunearr(const char *str, Rune **r)
+{
+ size_t len = strlen(str), rlen, roff, ret = 1, i;
+ Rune s;
+
+ for (rlen = 0, roff = 0; roff < len && ret; rlen++) {
+ ret = charntorune(&s, str + roff, MAX(UTFmax, len - roff));
+ roff += ret;
+ }
+
+ *r = emalloc(rlen * sizeof(Rune) + 1);
+ (*r)[rlen] = 0;
+
+ for (i = 0, roff = 0; roff < len && i < rlen; i++) {
+ roff += charntorune(&(*r)[i], str + roff, MAX(UTFmax, len - roff));
+ }
+
+ return rlen;
+}
diff --git a/sbase/libutf/readrune.c b/sbase/libutf/readrune.c
@@ -0,0 +1,46 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../utf.h"
+
+int
+readrune(const char *file, FILE *fp, Rune *r)
+{
+ char buf[UTFmax];
+ int c, i;
+
+ if ((c = fgetc(fp)) == EOF) {
+ if (ferror(fp)) {
+ fprintf(stderr, "%s: read error: %s\n",
+ file, strerror(errno));
+ exit(1);
+ }
+ return 0;
+ }
+
+ if (c < Runeself) {
+ *r = (Rune)c;
+ return 1;
+ }
+
+ buf[0] = c;
+ for (i = 1; ;) {
+ if ((c = fgetc(fp)) == EOF) {
+ if (ferror(fp)) {
+ fprintf(stderr, "%s: read error: %s\n",
+ file, strerror(errno));
+ exit(1);
+ }
+ return 0;
+ }
+ buf[i++] = c;
+ if (fullrune(buf, i)) {
+ chartorune(r, buf);
+ break;
+ }
+ }
+ return 1;
+}
diff --git a/sbase/libutf/rune.c b/sbase/libutf/rune.c
@@ -0,0 +1,148 @@
+/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#include "../utf.h"
+
+#define MIN(x,y) ((x) < (y) ? (x) : (y))
+
+#define UTFSEQ(x) ((((x) & 0x80) == 0x00) ? 1 /* 0xxxxxxx */ \
+ : (((x) & 0xC0) == 0x80) ? 0 /* 10xxxxxx */ \
+ : (((x) & 0xE0) == 0xC0) ? 2 /* 110xxxxx */ \
+ : (((x) & 0xF0) == 0xE0) ? 3 /* 1110xxxx */ \
+ : (((x) & 0xF8) == 0xF0) ? 4 /* 11110xxx */ \
+ : (((x) & 0xFC) == 0xF8) ? 5 /* 111110xx */ \
+ : (((x) & 0xFE) == 0xFC) ? 6 /* 1111110x */ \
+ : 0 )
+
+#define BADRUNE(x) ((x) < 0 || (x) > Runemax \
+ || ((x) & 0xFFFE) == 0xFFFE \
+ || ((x) >= 0xD800 && (x) <= 0xDFFF) \
+ || ((x) >= 0xFDD0 && (x) <= 0xFDEF))
+
+int
+runetochar(char *s, const Rune *p)
+{
+ Rune r = *p;
+
+ switch(runelen(r)) {
+ case 1: /* 0aaaaaaa */
+ s[0] = r;
+ return 1;
+ case 2: /* 00000aaa aabbbbbb */
+ s[0] = 0xC0 | ((r & 0x0007C0) >> 6); /* 110aaaaa */
+ s[1] = 0x80 | (r & 0x00003F); /* 10bbbbbb */
+ return 2;
+ case 3: /* aaaabbbb bbcccccc */
+ s[0] = 0xE0 | ((r & 0x00F000) >> 12); /* 1110aaaa */
+ s[1] = 0x80 | ((r & 0x000FC0) >> 6); /* 10bbbbbb */
+ s[2] = 0x80 | (r & 0x00003F); /* 10cccccc */
+ return 3;
+ case 4: /* 000aaabb bbbbcccc ccdddddd */
+ s[0] = 0xF0 | ((r & 0x1C0000) >> 18); /* 11110aaa */
+ s[1] = 0x80 | ((r & 0x03F000) >> 12); /* 10bbbbbb */
+ s[2] = 0x80 | ((r & 0x000FC0) >> 6); /* 10cccccc */
+ s[3] = 0x80 | (r & 0x00003F); /* 10dddddd */
+ return 4;
+ default:
+ return 0; /* error */
+ }
+}
+
+int
+chartorune(Rune *p, const char *s)
+{
+ return charntorune(p, s, UTFmax);
+}
+
+int
+charntorune(Rune *p, const char *s, size_t len)
+{
+ unsigned int i, n;
+ Rune r;
+
+ if(len == 0) /* can't even look at s[0] */
+ return 0;
+
+ switch((n = UTFSEQ(s[0]))) {
+ case 1: r = s[0]; break; /* 0xxxxxxx */
+ case 2: r = s[0] & 0x1F; break; /* 110xxxxx */
+ case 3: r = s[0] & 0x0F; break; /* 1110xxxx */
+ case 4: r = s[0] & 0x07; break; /* 11110xxx */
+ case 5: r = s[0] & 0x03; break; /* 111110xx */
+ case 6: r = s[0] & 0x01; break; /* 1111110x */
+ default: /* invalid sequence */
+ *p = Runeerror;
+ return 1;
+ }
+ /* add values from continuation bytes */
+ for(i = 1; i < MIN(n, len); i++)
+ if((s[i] & 0xC0) == 0x80) {
+ /* add bits from continuation byte to rune value
+ * cannot overflow: 6 byte sequences contain 31 bits */
+ r = (r << 6) | (s[i] & 0x3F); /* 10xxxxxx */
+ }
+ else { /* expected continuation */
+ *p = Runeerror;
+ return i;
+ }
+
+ if(i < n) /* must have reached len limit */
+ return 0;
+
+ /* reject invalid or overlong sequences */
+ if(BADRUNE(r) || runelen(r) < (int)n)
+ r = Runeerror;
+
+ *p = r;
+ return n;
+}
+
+int
+runelen(Rune r)
+{
+ if(BADRUNE(r))
+ return 0; /* error */
+ else if(r <= 0x7F)
+ return 1;
+ else if(r <= 0x07FF)
+ return 2;
+ else if(r <= 0xFFFF)
+ return 3;
+ else
+ return 4;
+}
+
+size_t
+runenlen(const Rune *p, size_t len)
+{
+ size_t i, n = 0;
+
+ for(i = 0; i < len; i++)
+ n += runelen(p[i]);
+ return n;
+}
+
+int
+fullrune(const char *s, size_t len)
+{
+ Rune r;
+
+ return charntorune(&r, s, len) > 0;
+}
diff --git a/sbase/libutf/runetype.c b/sbase/libutf/runetype.c
@@ -0,0 +1,48 @@
+/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include "../utf.h"
+
+#define nelem(x) (sizeof (x) / sizeof *(x))
+
+static int rune1cmp(const void *, const void *);
+static int rune2cmp(const void *, const void *);
+
+#include "../runetypebody.h"
+
+int
+rune1cmp(const void *v1, const void *v2)
+{
+ Rune r1 = *(Rune *)v1, r2 = *(Rune *)v2;
+
+ return r1 - r2;
+}
+
+int
+rune2cmp(const void *v1, const void *v2)
+{
+ Rune r = *(Rune *)v1, *p = (Rune *)v2;
+
+ if(r >= p[0] && r <= p[1])
+ return 0;
+ else
+ return r - p[0];
+}
diff --git a/sbase/libutf/utf.c b/sbase/libutf/utf.c
@@ -0,0 +1,129 @@
+/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include "../utf.h"
+
+char *
+utfecpy(char *to, char *end, const char *from)
+{
+ Rune r = Runeerror;
+ size_t i, n;
+
+ /* seek through to find final full rune */
+ for(i = 0; r != '\0' && (n = charntorune(&r, &from[i], end - &to[i])); i += n)
+ ;
+ memcpy(to, from, i); /* copy over bytes up to this rune */
+
+ if(i > 0 && r != '\0')
+ to[i] = '\0'; /* terminate if unterminated */
+ return &to[i];
+}
+
+size_t
+utflen(const char *s)
+{
+ const char *p = s;
+ size_t i;
+ Rune r;
+
+ for(i = 0; *p != '\0'; i++)
+ p += chartorune(&r, p);
+ return i;
+}
+
+size_t
+utfnlen(const char *s, size_t len)
+{
+ const char *p = s;
+ size_t i;
+ Rune r;
+ int n;
+
+ for(i = 0; (n = charntorune(&r, p, len-(p-s))) && r != '\0'; i++)
+ p += n;
+ return i;
+}
+
+char *
+utfrune(const char *s, Rune r)
+{
+ if(r < Runeself) {
+ return strchr(s, r);
+ }
+ else if(r == Runeerror) {
+ Rune r0;
+ int n;
+
+ for(; *s != '\0'; s += n) {
+ n = chartorune(&r0, s);
+ if(r == r0)
+ return (char *)s;
+ }
+ }
+ else {
+ char buf[UTFmax+1];
+ int n;
+
+ if(!(n = runetochar(buf, &r)))
+ return NULL;
+ buf[n] = '\0';
+ return strstr(s, buf);
+ }
+ return NULL;
+}
+
+char *
+utfrrune(const char *s, Rune r)
+{
+ const char *p = NULL;
+ Rune r0;
+ int n;
+
+ if(r < Runeself)
+ return strrchr(s, r);
+
+ for(; *s != '\0'; s += n) {
+ n = chartorune(&r0, s);
+ if(r == r0)
+ p = s;
+ }
+ return (char *)p;
+}
+
+char *
+utfutf(const char *s, const char *t)
+{
+ const char *p, *q;
+ Rune r0, r1, r2;
+ int n, m;
+
+ for(chartorune(&r0, t); (s = utfrune(s, r0)); s++) {
+ for(p = s, q = t; *q && *p; p += n, q += m) {
+ n = chartorune(&r1, p);
+ m = chartorune(&r2, q);
+ if(r1 != r2)
+ break;
+ }
+ if(!*q)
+ return (char *)s;
+ }
+ return NULL;
+}
diff --git a/sbase/libutf/writerune.c b/sbase/libutf/writerune.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "../utf.h"
+
+void
+writerune(const char *file, FILE *fp, Rune *r)
+{
+ char buf[UTFmax];
+ int n;
+
+ if ((n = runetochar(buf, r)) > 0) {
+ fwrite(buf, n, 1, fp);
+ if (ferror(fp)) {
+ fprintf(stderr, "%s: write error: %s\n",
+ file, strerror(errno));
+ exit(1);
+ }
+ }
+}
diff --git a/sbase/libutil/agetcwd.c b/sbase/libutil/agetcwd.c
@@ -0,0 +1,18 @@
+/* See LICENSE file for copyright and license details. */
+#include <unistd.h>
+
+#include "../util.h"
+
+char *
+agetcwd(void)
+{
+ char *buf;
+ long size;
+
+ apathmax(&buf, &size);
+ if (!getcwd(buf, size))
+ eprintf("getcwd:");
+
+ return buf;
+}
+
diff --git a/sbase/libutil/apathmax.c b/sbase/libutil/apathmax.c
@@ -0,0 +1,22 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "../util.h"
+
+void
+apathmax(char **p, long *size)
+{
+ errno = 0;
+
+ if ((*size = pathconf("/", _PC_PATH_MAX)) < 0) {
+ if (errno == 0) {
+ *size = BUFSIZ;
+ } else {
+ eprintf("pathconf:");
+ }
+ }
+ *p = emalloc(*size);
+}
diff --git a/sbase/libutil/concat.c b/sbase/libutil/concat.c
@@ -0,0 +1,20 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <unistd.h>
+
+#include "../text.h"
+#include "../util.h"
+
+void
+concat(FILE *fp1, const char *s1, FILE *fp2, const char *s2)
+{
+ char buf[BUFSIZ];
+ ssize_t n;
+
+ while ((n = read(fileno(fp1), buf, sizeof buf)) > 0) {
+ if (write(fileno(fp2), buf, n) != n)
+ eprintf("%s: write error:", s2);
+ }
+ if (n < 0)
+ eprintf("%s: read error:", s1);
+}
diff --git a/sbase/libutil/cp.c b/sbase/libutil/cp.c
@@ -0,0 +1,153 @@
+/* See LICENSE file for copyright and license details. */
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <utime.h>
+
+#include "../fs.h"
+#include "../text.h"
+#include "../util.h"
+
+int cp_aflag = 0;
+int cp_fflag = 0;
+int cp_Pflag = 0;
+int cp_pflag = 0;
+int cp_rflag = 0;
+int cp_vflag = 0;
+int cp_status = 0;
+
+int
+cp(const char *s1, const char *s2)
+{
+ FILE *f1, *f2;
+ char *ns1, *ns2;
+ long size1, size2;
+ struct dirent *d;
+ struct stat st;
+ struct utimbuf ut;
+ char buf[PATH_MAX];
+ DIR *dp;
+ int r;
+
+ if (cp_vflag)
+ printf("'%s' -> '%s'\n", s1, s2);
+
+ r = cp_Pflag ? lstat(s1, &st) : stat(s1, &st);
+ if (r < 0) {
+ weprintf("%s %s:", cp_Pflag ? "lstat" : "stat", s1);
+ cp_status = 1;
+ return 0;
+ }
+
+ if (S_ISLNK(st.st_mode)) {
+ if (readlink(s1, buf, sizeof(buf) - 1) >= 0) {
+ if (cp_fflag)
+ unlink(s2);
+ if (symlink(buf, s2) != 0) {
+ weprintf("%s: can't create '%s'\n", argv0, s2);
+ cp_status = 1;
+ return 0;
+ }
+ }
+ goto preserve;
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ if (!cp_rflag)
+ eprintf("%s: is a directory\n", s1);
+
+ if (!(dp = opendir(s1)))
+ eprintf("opendir %s:", s1);
+
+ if (mkdir(s2, st.st_mode) < 0 && errno != EEXIST)
+ eprintf("mkdir %s:", s2);
+
+ apathmax(&ns1, &size1);
+ apathmax(&ns2, &size2);
+ while ((d = readdir(dp))) {
+ if (strcmp(d->d_name, ".") && strcmp(d->d_name, "..")) {
+ r = snprintf(ns1, size1, "%s/%s", s1, d->d_name);
+ if (r >= size1 || r < 0) {
+ eprintf("%s/%s: filename too long\n",
+ s1, d->d_name);
+ }
+ r = snprintf(ns2, size2, "%s/%s", s2, d->d_name);
+ if (r >= size2 || r < 0) {
+ eprintf("%s/%s: filename too long\n",
+ s2, d->d_name);
+ }
+ fnck(ns1, ns2, cp);
+ }
+ }
+ closedir(dp);
+ free(ns1);
+ free(ns2);
+ goto preserve;
+ }
+
+ if (cp_aflag) {
+ if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode) ||
+ S_ISSOCK(st.st_mode) || S_ISFIFO(st.st_mode)) {
+ unlink(s2);
+ if (mknod(s2, st.st_mode, st.st_rdev) < 0) {
+ weprintf("%s: can't create '%s':", argv0, s2);
+ cp_status = 1;
+ return 0;
+ }
+ goto preserve;
+ }
+ }
+
+ if (!(f1 = fopen(s1, "r"))) {
+ weprintf("fopen %s:", s1);
+ cp_status = 1;
+ return 0;
+ }
+
+ if (!(f2 = fopen(s2, "w"))) {
+ if (cp_fflag) {
+ unlink(s2);
+ if (!(f2 = fopen(s2, "w"))) {
+ weprintf("fopen %s:", s2);
+ cp_status = 1;
+ return 0;
+ }
+ } else {
+ weprintf("fopen %s:", s2);
+ cp_status = 1;
+ return 0;
+ }
+ }
+ concat(f1, s1, f2, s2);
+ /* preserve permissions by default */
+ fchmod(fileno(f2), st.st_mode);
+ fclose(f2);
+ fclose(f1);
+
+preserve:
+ if (cp_aflag || cp_pflag) {
+ if (!(S_ISLNK(st.st_mode))) {
+ /* timestamp */
+ ut.actime = st.st_atime;
+ ut.modtime = st.st_mtime;
+ utime(s2, &ut);
+ }
+ /* preserve owner ? */
+ if (S_ISLNK(st.st_mode))
+ r = lchown(s2, st.st_uid, st.st_gid);
+ else
+ r = chown(s2, st.st_uid, st.st_gid);
+ if (r < 0) {
+ weprintf("cp: can't preserve ownership of '%s':", s2);
+ cp_status = 1;
+ }
+ }
+ return 0;
+}
diff --git a/sbase/libutil/crypt.c b/sbase/libutil/crypt.c
@@ -0,0 +1,156 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../crypt.h"
+#include "../text.h"
+#include "../util.h"
+
+static int
+hexdec(int c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ else if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ else if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ return -1; /* unknown character */
+}
+
+static int
+mdcheckline(const char *s, uint8_t *md, size_t sz)
+{
+ size_t i;
+ int b1, b2;
+
+ for (i = 0; i < sz; i++) {
+ if (!*s || (b1 = hexdec(*s++)) < 0)
+ return -1; /* invalid format */
+ if (!*s || (b2 = hexdec(*s++)) < 0)
+ return -1; /* invalid format */
+ if ((uint8_t)((b1 << 4) | b2) != md[i])
+ return 0; /* value mismatch */
+ }
+ return (i == sz) ? 1 : 0;
+}
+
+int
+cryptcheck(char *sumfile, int argc, char *argv[],
+ struct crypt_ops *ops, uint8_t *md, size_t sz)
+{
+ FILE *cfp, *fp;
+ char *line = NULL, *file, *p;
+ int r, nonmatch = 0, formatsucks = 0, noread = 0, ret = 0;
+ size_t bufsiz = 0;
+
+ if (!sumfile)
+ cfp = stdin;
+ else if (!(cfp = fopen(sumfile, "r")))
+ eprintf("fopen %s:", sumfile);
+
+ while (getline(&line, &bufsiz, cfp) != -1) {
+ if (!(file = strstr(line, " "))) {
+ formatsucks++;
+ continue;
+ }
+ if ((file - line) / 2 != sz) {
+ formatsucks++; /* checksum length mismatch */
+ continue;
+ }
+ *file = '\0';
+ file += 2;
+ for (p = file; *p && *p != '\n' && *p != '\r'; p++); /* strip newline */
+ *p = '\0';
+ if (!(fp = fopen(file, "r"))) {
+ weprintf("fopen %s:", file);
+ noread++;
+ continue;
+ }
+ cryptsum(ops, fp, file, md);
+ r = mdcheckline(line, md, sz);
+ if (r == 1) {
+ printf("%s: OK\n", file);
+ } else if (r == 0) {
+ printf("%s: FAILED\n", file);
+ nonmatch++;
+ } else {
+ formatsucks++;
+ }
+ fclose(fp);
+ }
+ if (sumfile)
+ fclose(cfp);
+ free(line);
+ if (formatsucks > 0) {
+ weprintf("%d lines are improperly formatted\n", formatsucks);
+ ret = 1;
+ }
+ if (noread > 0) {
+ weprintf("%d listed file could not be read\n", noread);
+ ret = 1;
+ }
+ if (nonmatch > 0) {
+ weprintf("%d computed checksums did NOT match\n", nonmatch);
+ ret = 1;
+ }
+ return ret;
+}
+
+int
+cryptmain(int argc, char *argv[],
+ struct crypt_ops *ops, uint8_t *md, size_t sz)
+{
+ FILE *fp;
+ int ret = 0;
+
+ if (argc == 0) {
+ cryptsum(ops, stdin, "<stdin>", md);
+ mdprint(md, "<stdin>", sz);
+ } else {
+ for (; argc > 0; argc--) {
+ if (!(fp = fopen(*argv, "r"))) {
+ weprintf("fopen %s:", *argv);
+ ret = 1;
+ continue;
+ }
+ if (cryptsum(ops, fp, *argv, md) == 1)
+ ret = 1;
+ else
+ mdprint(md, *argv, sz);
+ fclose(fp);
+ argv++;
+ }
+ }
+ return ret;
+}
+
+int
+cryptsum(struct crypt_ops *ops, FILE *fp, const char *f,
+ uint8_t *md)
+{
+ uint8_t buf[BUFSIZ];
+ size_t n;
+
+ ops->init(ops->s);
+ while ((n = fread(buf, 1, sizeof(buf), fp)) > 0)
+ ops->update(ops->s, buf, n);
+ if (ferror(fp)) {
+ weprintf("%s: read error:", f);
+ return 1;
+ }
+ ops->sum(ops->s, md);
+ return 0;
+}
+
+void
+mdprint(const uint8_t *md, const char *f, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ printf("%02x", md[i]);
+ printf(" %s\n", f);
+}
diff --git a/sbase/libutil/ealloc.c b/sbase/libutil/ealloc.c
@@ -0,0 +1,47 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdlib.h>
+#include <string.h>
+
+#include "../util.h"
+
+void *
+ecalloc(size_t nmemb, size_t size)
+{
+ void *p;
+
+ p = calloc(nmemb, size);
+ if (!p)
+ eprintf("calloc: out of memory\n");
+ return p;
+}
+
+void *
+emalloc(size_t size)
+{
+ void *p;
+
+ p = malloc(size);
+ if (!p)
+ eprintf("malloc: out of memory\n");
+ return p;
+}
+
+void *
+erealloc(void *p, size_t size)
+{
+ p = realloc(p, size);
+ if (!p)
+ eprintf("realloc: out of memory\n");
+ return p;
+}
+
+char *
+estrdup(const char *s)
+{
+ char *p;
+
+ p = strdup(s);
+ if (!p)
+ eprintf("strdup: out of memory\n");
+ return p;
+}
diff --git a/sbase/libutil/enmasse.c b/sbase/libutil/enmasse.c
@@ -0,0 +1,41 @@
+/* See LICENSE file for copyright and license details. */
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "../util.h"
+
+void
+enmasse(int argc, char *argv[], int (*fn)(const char *, const char *))
+{
+ char *buf, *dir;
+ int i, len;
+ long size;
+ struct stat st;
+ size_t dlen;
+
+ if (argc == 2 && !(stat(argv[1], &st) == 0 && S_ISDIR(st.st_mode))) {
+ fnck(argv[0], argv[1], fn);
+ return;
+ } else {
+ dir = (argc == 1) ? "." : argv[--argc];
+ }
+
+ apathmax(&buf, &size);
+ for (i = 0; i < argc; i++) {
+ dlen = strlen(dir);
+ if (dlen > 0 && dir[dlen - 1] == '/')
+ len = snprintf(buf, size, "%s%s", dir, basename(argv[i]));
+ else
+ len = snprintf(buf, size, "%s/%s", dir, basename(argv[i]));
+ if (len < 0 || len >= size) {
+ eprintf("%s/%s: filename too long\n", dir,
+ basename(argv[i]));
+ }
+ fnck(argv[i], buf, fn);
+ }
+ free(buf);
+}
diff --git a/sbase/libutil/eprintf.c b/sbase/libutil/eprintf.c
@@ -0,0 +1,67 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../util.h"
+
+char *argv0;
+
+static void venprintf(int, const char *, va_list);
+
+void
+eprintf(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ venprintf(1, fmt, ap);
+ va_end(ap);
+}
+
+void
+enprintf(int status, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ venprintf(status, fmt, ap);
+ va_end(ap);
+}
+
+void
+venprintf(int status, const char *fmt, va_list ap)
+{
+#ifdef DEBUG
+ fprintf(stderr, "%s: ", argv0);
+#endif
+
+ vfprintf(stderr, fmt, ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ }
+
+ exit(status);
+}
+
+void
+weprintf(const char *fmt, ...)
+{
+ va_list ap;
+
+#ifdef DEBUG
+ fprintf(stderr, "%s: ", argv0);
+#endif
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ }
+}
diff --git a/sbase/libutil/eregcomp.c b/sbase/libutil/eregcomp.c
@@ -0,0 +1,25 @@
+#include <regex.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "../util.h"
+
+int
+enregcomp(int status, regex_t *preg, const char *regex, int cflags)
+{
+ char errbuf[BUFSIZ] = "";
+ int r;
+
+ if((r = regcomp(preg, regex, cflags)) == 0)
+ return r;
+
+ regerror(r, preg, errbuf, sizeof(errbuf));
+ enprintf(status, "invalid regex: %s\n", errbuf);
+ return r;
+}
+
+int
+eregcomp(regex_t *preg, const char *regex, int cflags)
+{
+ return enregcomp(1, preg, regex, cflags);
+}
diff --git a/sbase/libutil/estrtod.c b/sbase/libutil/estrtod.c
@@ -0,0 +1,18 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../util.h"
+
+double
+estrtod(const char *s)
+{
+ char *end;
+ double d;
+
+ d = strtod(s, &end);
+ if (end == s || *end != '\0')
+ eprintf("%s: not a real number\n", s);
+ return d;
+}
diff --git a/sbase/libutil/estrtol.c b/sbase/libutil/estrtol.c
@@ -0,0 +1,27 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../util.h"
+
+long
+estrtol(const char *s, int base)
+{
+ char *end;
+ long n;
+
+ errno = 0;
+ n = strtol(s, &end, base);
+ if (*end != '\0') {
+ if (base == 0)
+ eprintf("%s: not an integer\n", s);
+ else
+ eprintf("%s: not a base %d integer\n", s, base);
+ }
+ if (errno != 0)
+ eprintf("%s:", s);
+
+ return n;
+}
+
diff --git a/sbase/libutil/fnck.c b/sbase/libutil/fnck.c
@@ -0,0 +1,20 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include "../util.h"
+
+void
+fnck(const char *a, const char *b, int (*fn)(const char *, const char *))
+{
+ struct stat sta, stb;
+
+ if (!stat(a, &sta)
+ && !stat(b, &stb)
+ && sta.st_dev == stb.st_dev
+ && sta.st_ino == stb.st_ino) {
+ eprintf("%s -> %s: same file\n", a, b);
+ }
+
+ if (fn(a, b) < 0)
+ eprintf("%s -> %s:", a, b);
+}
diff --git a/sbase/libutil/getlines.c b/sbase/libutil/getlines.c
@@ -0,0 +1,27 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../text.h"
+#include "../util.h"
+
+void
+getlines(FILE *fp, struct linebuf *b)
+{
+ char *line = NULL, **nline;
+ size_t size = 0, linelen;
+ ssize_t len;
+
+ while ((len = getline(&line, &size, fp)) != -1) {
+ if (++b->nlines > b->capacity) {
+ b->capacity += 512;
+ nline = erealloc(b->lines, b->capacity * sizeof(*b->lines));
+ b->lines = nline;
+ }
+ linelen = len + 1;
+ b->lines[b->nlines-1] = emalloc(linelen);
+ memcpy(b->lines[b->nlines-1], line, linelen);
+ }
+ free(line);
+}
diff --git a/sbase/libutil/human.c b/sbase/libutil/human.c
@@ -0,0 +1,22 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <string.h>
+
+#include "../util.h"
+
+char *
+humansize(double n)
+{
+ static char buf[16];
+ const char postfixes[] = "BKMGTPE";
+ size_t i;
+
+ for (i = 0; n >= 1024 && i < strlen(postfixes); i++)
+ n /= 1024;
+
+ if (!i)
+ snprintf(buf, sizeof(buf), "%lu", (unsigned long)n);
+ else
+ snprintf(buf, sizeof(buf), "%.1f%c", n, postfixes[i]);
+ return buf;
+}
diff --git a/sbase/libutil/md5.c b/sbase/libutil/md5.c
@@ -0,0 +1,148 @@
+/* public domain md5 implementation based on rfc1321 and libtomcrypt */
+#include <stdint.h>
+#include <string.h>
+
+#include "../md5.h"
+
+static uint32_t rol(uint32_t n, int k) { return (n << k) | (n >> (32-k)); }
+#define F(x,y,z) (z ^ (x & (y ^ z)))
+#define G(x,y,z) (y ^ (z & (y ^ x)))
+#define H(x,y,z) (x ^ y ^ z)
+#define I(x,y,z) (y ^ (x | ~z))
+#define FF(a,b,c,d,w,s,t) a += F(b,c,d) + w + t; a = rol(a,s) + b
+#define GG(a,b,c,d,w,s,t) a += G(b,c,d) + w + t; a = rol(a,s) + b
+#define HH(a,b,c,d,w,s,t) a += H(b,c,d) + w + t; a = rol(a,s) + b
+#define II(a,b,c,d,w,s,t) a += I(b,c,d) + w + t; a = rol(a,s) + b
+
+static const uint32_t tab[64] = {
+ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
+ 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
+ 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
+ 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
+ 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
+ 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+ 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
+ 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
+};
+
+static void
+processblock(struct md5 *s, const uint8_t *buf)
+{
+ uint32_t i, W[16], a, b, c, d;
+
+ for (i = 0; i < 16; i++) {
+ W[i] = buf[4*i];
+ W[i] |= (uint32_t)buf[4*i+1]<<8;
+ W[i] |= (uint32_t)buf[4*i+2]<<16;
+ W[i] |= (uint32_t)buf[4*i+3]<<24;
+ }
+
+ a = s->h[0];
+ b = s->h[1];
+ c = s->h[2];
+ d = s->h[3];
+
+ i = 0;
+ while (i < 16) {
+ FF(a,b,c,d, W[i], 7, tab[i]); i++;
+ FF(d,a,b,c, W[i], 12, tab[i]); i++;
+ FF(c,d,a,b, W[i], 17, tab[i]); i++;
+ FF(b,c,d,a, W[i], 22, tab[i]); i++;
+ }
+ while (i < 32) {
+ GG(a,b,c,d, W[(5*i+1)%16], 5, tab[i]); i++;
+ GG(d,a,b,c, W[(5*i+1)%16], 9, tab[i]); i++;
+ GG(c,d,a,b, W[(5*i+1)%16], 14, tab[i]); i++;
+ GG(b,c,d,a, W[(5*i+1)%16], 20, tab[i]); i++;
+ }
+ while (i < 48) {
+ HH(a,b,c,d, W[(3*i+5)%16], 4, tab[i]); i++;
+ HH(d,a,b,c, W[(3*i+5)%16], 11, tab[i]); i++;
+ HH(c,d,a,b, W[(3*i+5)%16], 16, tab[i]); i++;
+ HH(b,c,d,a, W[(3*i+5)%16], 23, tab[i]); i++;
+ }
+ while (i < 64) {
+ II(a,b,c,d, W[7*i%16], 6, tab[i]); i++;
+ II(d,a,b,c, W[7*i%16], 10, tab[i]); i++;
+ II(c,d,a,b, W[7*i%16], 15, tab[i]); i++;
+ II(b,c,d,a, W[7*i%16], 21, tab[i]); i++;
+ }
+
+ s->h[0] += a;
+ s->h[1] += b;
+ s->h[2] += c;
+ s->h[3] += d;
+}
+
+static void
+pad(struct md5 *s)
+{
+ unsigned r = s->len % 64;
+
+ s->buf[r++] = 0x80;
+ if (r > 56) {
+ memset(s->buf + r, 0, 64 - r);
+ r = 0;
+ processblock(s, s->buf);
+ }
+ memset(s->buf + r, 0, 56 - r);
+ s->len *= 8;
+ s->buf[56] = s->len;
+ s->buf[57] = s->len >> 8;
+ s->buf[58] = s->len >> 16;
+ s->buf[59] = s->len >> 24;
+ s->buf[60] = s->len >> 32;
+ s->buf[61] = s->len >> 40;
+ s->buf[62] = s->len >> 48;
+ s->buf[63] = s->len >> 56;
+ processblock(s, s->buf);
+}
+
+void
+md5_init(void *ctx)
+{
+ struct md5 *s = ctx;
+ s->len = 0;
+ s->h[0] = 0x67452301;
+ s->h[1] = 0xefcdab89;
+ s->h[2] = 0x98badcfe;
+ s->h[3] = 0x10325476;
+}
+
+void
+md5_sum(void *ctx, uint8_t md[MD5_DIGEST_LENGTH])
+{
+ struct md5 *s = ctx;
+ int i;
+
+ pad(s);
+ for (i = 0; i < 4; i++) {
+ md[4*i] = s->h[i];
+ md[4*i+1] = s->h[i] >> 8;
+ md[4*i+2] = s->h[i] >> 16;
+ md[4*i+3] = s->h[i] >> 24;
+ }
+}
+
+void
+md5_update(void *ctx, const void *m, unsigned long len)
+{
+ struct md5 *s = ctx;
+ const uint8_t *p = m;
+ unsigned r = s->len % 64;
+
+ s->len += len;
+ if (r) {
+ if (len < 64 - r) {
+ memcpy(s->buf + r, p, len);
+ return;
+ }
+ memcpy(s->buf + r, p, 64 - r);
+ len -= 64 - r;
+ p += 64 - r;
+ processblock(s, s->buf);
+ }
+ for (; len >= 64; len -= 64, p += 64)
+ processblock(s, p);
+ memcpy(s->buf, p, len);
+}
diff --git a/sbase/libutil/mode.c b/sbase/libutil/mode.c
@@ -0,0 +1,163 @@
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "../util.h"
+
+mode_t
+getumask(void)
+{
+ mode_t mask = umask(0);
+ umask(mask);
+ return mask;
+}
+
+mode_t
+parsemode(const char *str, mode_t mode, mode_t mask)
+{
+ char *end;
+ const char *p = str;
+ int octal, op;
+ mode_t who, perm, clear;
+
+ octal = strtol(str, &end, 8);
+ if (*end == '\0') {
+ if (octal < 0 || octal > 07777) {
+ eprintf("%s: invalid mode\n", str);
+ return -1;
+ }
+ mode = 0;
+ if (octal & 04000) mode |= S_ISUID;
+ if (octal & 02000) mode |= S_ISGID;
+ if (octal & 01000) mode |= S_ISVTX;
+ if (octal & 00400) mode |= S_IRUSR;
+ if (octal & 00200) mode |= S_IWUSR;
+ if (octal & 00100) mode |= S_IXUSR;
+ if (octal & 00040) mode |= S_IRGRP;
+ if (octal & 00020) mode |= S_IWGRP;
+ if (octal & 00010) mode |= S_IXGRP;
+ if (octal & 00004) mode |= S_IROTH;
+ if (octal & 00002) mode |= S_IWOTH;
+ if (octal & 00001) mode |= S_IXOTH;
+ return mode;
+ }
+next:
+ /* first, determine which bits we will be modifying */
+ for (who = 0; *p; p++) {
+ switch (*p) {
+ /* masks */
+ case 'u':
+ who |= S_IRWXU|S_ISUID;
+ continue;
+ case 'g':
+ who |= S_IRWXG|S_ISGID;
+ continue;
+ case 'o':
+ who |= S_IRWXO;
+ continue;
+ case 'a':
+ who |= S_IRWXU|S_ISUID|S_IRWXG|S_ISGID|S_IRWXO;
+ continue;
+ }
+ break;
+ }
+ if (who) {
+ clear = who;
+ } else {
+ clear = S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO;
+ who = ~mask;
+ }
+ while (*p) {
+ switch (*p) {
+ /* opers */
+ case '=':
+ case '+':
+ case '-':
+ op = (int)*p;
+ break;
+ default:
+ eprintf("%s: invalid mode\n", str);
+ return -1;
+ }
+
+ perm = 0;
+ switch (*++p) {
+ /* copy */
+ case 'u':
+ if (mode & S_IRUSR)
+ perm |= S_IRUSR|S_IRGRP|S_IROTH;
+ if (mode & S_IWUSR)
+ perm |= S_IWUSR|S_IWGRP|S_IWOTH;
+ if (mode & S_IXUSR)
+ perm |= S_IXUSR|S_IXGRP|S_IXOTH;
+ if (mode & S_ISUID)
+ perm |= S_ISUID|S_ISGID;
+ p++;
+ break;
+ case 'g':
+ if (mode & S_IRGRP)
+ perm |= S_IRUSR|S_IRGRP|S_IROTH;
+ if (mode & S_IWGRP)
+ perm |= S_IWUSR|S_IWGRP|S_IWOTH;
+ if (mode & S_IXGRP)
+ perm |= S_IXUSR|S_IXGRP|S_IXOTH;
+ if (mode & S_ISGID)
+ perm |= S_ISUID|S_ISGID;
+ p++;
+ break;
+ case 'o':
+ if (mode & S_IROTH)
+ perm |= S_IRUSR|S_IRGRP|S_IROTH;
+ if (mode & S_IWOTH)
+ perm |= S_IWUSR|S_IWGRP|S_IWOTH;
+ if (mode & S_IXOTH)
+ perm |= S_IXUSR|S_IXGRP|S_IXOTH;
+ p++;
+ break;
+ default:
+ for (; *p; p++) {
+ switch (*p) {
+ /* modes */
+ case 'r':
+ perm |= S_IRUSR|S_IRGRP|S_IROTH;
+ break;
+ case 'w':
+ perm |= S_IWUSR|S_IWGRP|S_IWOTH;
+ break;
+ case 'x':
+ perm |= S_IXUSR|S_IXGRP|S_IXOTH;
+ break;
+ case 's':
+ perm |= S_ISUID|S_ISGID;
+ break;
+ case 't':
+ perm |= S_ISVTX;
+ break;
+ default:
+ goto apply;
+ }
+ }
+ }
+
+ apply:
+ /* apply */
+ switch (op) {
+ case '=':
+ mode &= ~clear;
+ /* fallthrough */
+ case '+':
+ mode |= perm & who;
+ break;
+ case '-':
+ mode &= ~(perm & who);
+ break;
+ }
+ /* if we hit a comma, move on to the next clause */
+ if (*p == ',') {
+ p++;
+ goto next;
+ }
+ }
+ return mode;
+}
diff --git a/sbase/libutil/putword.c b/sbase/libutil/putword.c
@@ -0,0 +1,16 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+
+#include "../util.h"
+
+void
+putword(const char *s)
+{
+ static int first = 1;
+
+ if (!first)
+ putchar(' ');
+
+ fputs(s, stdout);
+ first = 0;
+}
diff --git a/sbase/libutil/recurse.c b/sbase/libutil/recurse.c
@@ -0,0 +1,42 @@
+/* See LICENSE file for copyright and license details. */
+#include <dirent.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "../util.h"
+
+void
+recurse(const char *path, void (*fn)(const char *))
+{
+ char buf[PATH_MAX];
+ struct dirent *d;
+ struct stat st;
+ DIR *dp;
+
+ if (lstat(path, &st) < 0 || !S_ISDIR(st.st_mode))
+ return;
+
+ if (!(dp = opendir(path)))
+ eprintf("opendir %s:", path);
+
+ while ((d = readdir(dp))) {
+ if (strcmp(d->d_name, ".") == 0 ||
+ strcmp(d->d_name, "..") == 0)
+ continue;
+ if (strlcpy(buf, path, sizeof(buf)) >= sizeof(buf))
+ eprintf("path too long\n");
+ if (buf[strlen(buf) - 1] != '/')
+ if (strlcat(buf, "/", sizeof(buf)) >= sizeof(buf))
+ eprintf("path too long\n");
+ if (strlcat(buf, d->d_name, sizeof(buf)) >= sizeof(buf))
+ eprintf("path too long\n");
+ fn(buf);
+ }
+
+ closedir(dp);
+}
diff --git a/sbase/libutil/rm.c b/sbase/libutil/rm.c
@@ -0,0 +1,17 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+
+#include "../fs.h"
+#include "../util.h"
+
+int rm_fflag = 0;
+int rm_rflag = 0;
+
+void
+rm(const char *path)
+{
+ if (rm_rflag)
+ recurse(path, rm);
+ if (remove(path) < 0 && !rm_fflag)
+ eprintf("remove %s:", path);
+}
diff --git a/sbase/libutil/sha1.c b/sbase/libutil/sha1.c
@@ -0,0 +1,144 @@
+/* public domain sha1 implementation based on rfc3174 and libtomcrypt */
+#include <stdint.h>
+#include <string.h>
+
+#include "../sha1.h"
+
+static uint32_t rol(uint32_t n, int k) { return (n << k) | (n >> (32-k)); }
+#define F0(b,c,d) (d ^ (b & (c ^ d)))
+#define F1(b,c,d) (b ^ c ^ d)
+#define F2(b,c,d) ((b & c) | (d & (b | c)))
+#define F3(b,c,d) (b ^ c ^ d)
+#define G0(a,b,c,d,e,i) e += rol(a,5)+F0(b,c,d)+W[i]+0x5A827999; b = rol(b,30)
+#define G1(a,b,c,d,e,i) e += rol(a,5)+F1(b,c,d)+W[i]+0x6ED9EBA1; b = rol(b,30)
+#define G2(a,b,c,d,e,i) e += rol(a,5)+F2(b,c,d)+W[i]+0x8F1BBCDC; b = rol(b,30)
+#define G3(a,b,c,d,e,i) e += rol(a,5)+F3(b,c,d)+W[i]+0xCA62C1D6; b = rol(b,30)
+
+static void
+processblock(struct sha1 *s, const uint8_t *buf)
+{
+ uint32_t W[80], a, b, c, d, e;
+ int i;
+
+ for (i = 0; i < 16; i++) {
+ W[i] = (uint32_t)buf[4*i]<<24;
+ W[i] |= (uint32_t)buf[4*i+1]<<16;
+ W[i] |= (uint32_t)buf[4*i+2]<<8;
+ W[i] |= buf[4*i+3];
+ }
+ for (; i < 80; i++)
+ W[i] = rol(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16], 1);
+ a = s->h[0];
+ b = s->h[1];
+ c = s->h[2];
+ d = s->h[3];
+ e = s->h[4];
+ for (i = 0; i < 20; ) {
+ G0(a,b,c,d,e,i++);
+ G0(e,a,b,c,d,i++);
+ G0(d,e,a,b,c,i++);
+ G0(c,d,e,a,b,i++);
+ G0(b,c,d,e,a,i++);
+ }
+ while (i < 40) {
+ G1(a,b,c,d,e,i++);
+ G1(e,a,b,c,d,i++);
+ G1(d,e,a,b,c,i++);
+ G1(c,d,e,a,b,i++);
+ G1(b,c,d,e,a,i++);
+ }
+ while (i < 60) {
+ G2(a,b,c,d,e,i++);
+ G2(e,a,b,c,d,i++);
+ G2(d,e,a,b,c,i++);
+ G2(c,d,e,a,b,i++);
+ G2(b,c,d,e,a,i++);
+ }
+ while (i < 80) {
+ G3(a,b,c,d,e,i++);
+ G3(e,a,b,c,d,i++);
+ G3(d,e,a,b,c,i++);
+ G3(c,d,e,a,b,i++);
+ G3(b,c,d,e,a,i++);
+ }
+ s->h[0] += a;
+ s->h[1] += b;
+ s->h[2] += c;
+ s->h[3] += d;
+ s->h[4] += e;
+}
+
+static void
+pad(struct sha1 *s)
+{
+ unsigned r = s->len % 64;
+
+ s->buf[r++] = 0x80;
+ if (r > 56) {
+ memset(s->buf + r, 0, 64 - r);
+ r = 0;
+ processblock(s, s->buf);
+ }
+ memset(s->buf + r, 0, 56 - r);
+ s->len *= 8;
+ s->buf[56] = s->len >> 56;
+ s->buf[57] = s->len >> 48;
+ s->buf[58] = s->len >> 40;
+ s->buf[59] = s->len >> 32;
+ s->buf[60] = s->len >> 24;
+ s->buf[61] = s->len >> 16;
+ s->buf[62] = s->len >> 8;
+ s->buf[63] = s->len;
+ processblock(s, s->buf);
+}
+
+void
+sha1_init(void *ctx)
+{
+ struct sha1 *s = ctx;
+
+ s->len = 0;
+ s->h[0] = 0x67452301;
+ s->h[1] = 0xEFCDAB89;
+ s->h[2] = 0x98BADCFE;
+ s->h[3] = 0x10325476;
+ s->h[4] = 0xC3D2E1F0;
+}
+
+void
+sha1_sum(void *ctx, uint8_t md[SHA1_DIGEST_LENGTH])
+{
+ struct sha1 *s = ctx;
+ int i;
+
+ pad(s);
+ for (i = 0; i < 5; i++) {
+ md[4*i] = s->h[i] >> 24;
+ md[4*i+1] = s->h[i] >> 16;
+ md[4*i+2] = s->h[i] >> 8;
+ md[4*i+3] = s->h[i];
+ }
+}
+
+void
+sha1_update(void *ctx, const void *m, unsigned long len)
+{
+ struct sha1 *s = ctx;
+ const uint8_t *p = m;
+ unsigned r = s->len % 64;
+
+ s->len += len;
+ if (r) {
+ if (len < 64 - r) {
+ memcpy(s->buf + r, p, len);
+ return;
+ }
+ memcpy(s->buf + r, p, 64 - r);
+ len -= 64 - r;
+ p += 64 - r;
+ processblock(s, s->buf);
+ }
+ for (; len >= 64; len -= 64, p += 64)
+ processblock(s, p);
+ memcpy(s->buf, p, len);
+}
diff --git a/sbase/libutil/sha256.c b/sbase/libutil/sha256.c
@@ -0,0 +1,148 @@
+/* public domain sha256 implementation based on fips180-3 */
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../sha256.h"
+
+static uint32_t ror(uint32_t n, int k) { return (n >> k) | (n << (32-k)); }
+#define Ch(x,y,z) (z ^ (x & (y ^ z)))
+#define Maj(x,y,z) ((x & y) | (z & (x | y)))
+#define S0(x) (ror(x,2) ^ ror(x,13) ^ ror(x,22))
+#define S1(x) (ror(x,6) ^ ror(x,11) ^ ror(x,25))
+#define R0(x) (ror(x,7) ^ ror(x,18) ^ (x>>3))
+#define R1(x) (ror(x,17) ^ ror(x,19) ^ (x>>10))
+
+static const uint32_t K[64] = {
+0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+};
+
+static void
+processblock(struct sha256 *s, const uint8_t *buf)
+{
+ uint32_t W[64], t1, t2, a, b, c, d, e, f, g, h;
+ int i;
+
+ for (i = 0; i < 16; i++) {
+ W[i] = (uint32_t)buf[4*i]<<24;
+ W[i] |= (uint32_t)buf[4*i+1]<<16;
+ W[i] |= (uint32_t)buf[4*i+2]<<8;
+ W[i] |= buf[4*i+3];
+ }
+ for (; i < 64; i++)
+ W[i] = R1(W[i-2]) + W[i-7] + R0(W[i-15]) + W[i-16];
+ a = s->h[0];
+ b = s->h[1];
+ c = s->h[2];
+ d = s->h[3];
+ e = s->h[4];
+ f = s->h[5];
+ g = s->h[6];
+ h = s->h[7];
+ for (i = 0; i < 64; i++) {
+ t1 = h + S1(e) + Ch(e,f,g) + K[i] + W[i];
+ t2 = S0(a) + Maj(a,b,c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + t1;
+ d = c;
+ c = b;
+ b = a;
+ a = t1 + t2;
+ }
+ s->h[0] += a;
+ s->h[1] += b;
+ s->h[2] += c;
+ s->h[3] += d;
+ s->h[4] += e;
+ s->h[5] += f;
+ s->h[6] += g;
+ s->h[7] += h;
+}
+
+static void
+pad(struct sha256 *s)
+{
+ unsigned r = s->len % 64;
+
+ s->buf[r++] = 0x80;
+ if (r > 56) {
+ memset(s->buf + r, 0, 64 - r);
+ r = 0;
+ processblock(s, s->buf);
+ }
+ memset(s->buf + r, 0, 56 - r);
+ s->len *= 8;
+ s->buf[56] = s->len >> 56;
+ s->buf[57] = s->len >> 48;
+ s->buf[58] = s->len >> 40;
+ s->buf[59] = s->len >> 32;
+ s->buf[60] = s->len >> 24;
+ s->buf[61] = s->len >> 16;
+ s->buf[62] = s->len >> 8;
+ s->buf[63] = s->len;
+ processblock(s, s->buf);
+}
+
+void
+sha256_init(void *ctx)
+{
+ struct sha256 *s = ctx;
+ s->len = 0;
+ s->h[0] = 0x6a09e667;
+ s->h[1] = 0xbb67ae85;
+ s->h[2] = 0x3c6ef372;
+ s->h[3] = 0xa54ff53a;
+ s->h[4] = 0x510e527f;
+ s->h[5] = 0x9b05688c;
+ s->h[6] = 0x1f83d9ab;
+ s->h[7] = 0x5be0cd19;
+}
+
+void
+sha256_sum(void *ctx, uint8_t md[SHA256_DIGEST_LENGTH])
+{
+ struct sha256 *s = ctx;
+ int i;
+
+ pad(s);
+ for (i = 0; i < 8; i++) {
+ md[4*i] = s->h[i] >> 24;
+ md[4*i+1] = s->h[i] >> 16;
+ md[4*i+2] = s->h[i] >> 8;
+ md[4*i+3] = s->h[i];
+ }
+}
+
+void
+sha256_update(void *ctx, const void *m, unsigned long len)
+{
+ struct sha256 *s = ctx;
+ const uint8_t *p = m;
+ unsigned r = s->len % 64;
+
+ s->len += len;
+ if (r) {
+ if (len < 64 - r) {
+ memcpy(s->buf + r, p, len);
+ return;
+ }
+ memcpy(s->buf + r, p, 64 - r);
+ len -= 64 - r;
+ p += 64 - r;
+ processblock(s, s->buf);
+ }
+ for (; len >= 64; len -= 64, p += 64)
+ processblock(s, p);
+ memcpy(s->buf, p, len);
+}
diff --git a/sbase/libutil/sha512.c b/sbase/libutil/sha512.c
@@ -0,0 +1,169 @@
+/* public domain sha256 implementation based on fips180-3 */
+
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../sha512.h"
+
+static uint64_t ror(uint64_t n, int k) { return (n >> k) | (n << (64-k)); }
+#define Ch(x,y,z) (z ^ (x & (y ^ z)))
+#define Maj(x,y,z) ((x & y) | (z & (x | y)))
+#define S0(x) (ror(x,28) ^ ror(x,34) ^ ror(x,39))
+#define S1(x) (ror(x,14) ^ ror(x,18) ^ ror(x,41))
+#define R0(x) (ror(x,1) ^ ror(x,8) ^ (x>>7))
+#define R1(x) (ror(x,19) ^ ror(x,61) ^ (x>>6))
+
+static const uint64_t K[80] = {
+0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL,
+0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL,
+0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
+0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL,
+0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL,
+0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
+0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL,
+0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL,
+0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
+0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL,
+0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL,
+0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
+0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL,
+0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL,
+0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
+0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL,
+0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL,
+0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
+0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL,
+0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL
+};
+
+static void
+processblock(struct sha512 *s, const uint8_t *buf)
+{
+ uint64_t W[80], t1, t2, a, b, c, d, e, f, g, h;
+ int i;
+
+ for (i = 0; i < 16; i++) {
+ W[i] = (uint64_t)buf[8*i]<<56;
+ W[i] |= (uint64_t)buf[8*i+1]<<48;
+ W[i] |= (uint64_t)buf[8*i+2]<<40;
+ W[i] |= (uint64_t)buf[8*i+3]<<32;
+ W[i] |= (uint64_t)buf[8*i+4]<<24;
+ W[i] |= (uint64_t)buf[8*i+5]<<16;
+ W[i] |= (uint64_t)buf[8*i+6]<<8;
+ W[i] |= buf[8*i+7];
+ }
+ for (; i < 80; i++)
+ W[i] = R1(W[i-2]) + W[i-7] + R0(W[i-15]) + W[i-16];
+ a = s->h[0];
+ b = s->h[1];
+ c = s->h[2];
+ d = s->h[3];
+ e = s->h[4];
+ f = s->h[5];
+ g = s->h[6];
+ h = s->h[7];
+ for (i = 0; i < 80; i++) {
+ t1 = h + S1(e) + Ch(e,f,g) + K[i] + W[i];
+ t2 = S0(a) + Maj(a,b,c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + t1;
+ d = c;
+ c = b;
+ b = a;
+ a = t1 + t2;
+ }
+ s->h[0] += a;
+ s->h[1] += b;
+ s->h[2] += c;
+ s->h[3] += d;
+ s->h[4] += e;
+ s->h[5] += f;
+ s->h[6] += g;
+ s->h[7] += h;
+}
+
+static void
+pad(struct sha512 *s)
+{
+ unsigned r = s->len % 128;
+
+ s->buf[r++] = 0x80;
+ if (r > 112) {
+ memset(s->buf + r, 0, 128 - r);
+ r = 0;
+ processblock(s, s->buf);
+ }
+ memset(s->buf + r, 0, 120 - r);
+ s->len *= 8;
+ s->buf[120] = s->len >> 56;
+ s->buf[121] = s->len >> 48;
+ s->buf[122] = s->len >> 40;
+ s->buf[123] = s->len >> 32;
+ s->buf[124] = s->len >> 24;
+ s->buf[125] = s->len >> 16;
+ s->buf[126] = s->len >> 8;
+ s->buf[127] = s->len;
+ processblock(s, s->buf);
+}
+
+void
+sha512_init(void *ctx)
+{
+ struct sha512 *s = ctx;
+ s->len = 0;
+ s->h[0] = 0x6a09e667f3bcc908ULL;
+ s->h[1] = 0xbb67ae8584caa73bULL;
+ s->h[2] = 0x3c6ef372fe94f82bULL;
+ s->h[3] = 0xa54ff53a5f1d36f1ULL;
+ s->h[4] = 0x510e527fade682d1ULL;
+ s->h[5] = 0x9b05688c2b3e6c1fULL;
+ s->h[6] = 0x1f83d9abfb41bd6bULL;
+ s->h[7] = 0x5be0cd19137e2179ULL;
+}
+
+void
+sha512_sum(void *ctx, uint8_t md[SHA512_DIGEST_LENGTH])
+{
+ struct sha512 *s = ctx;
+ int i;
+
+ pad(s);
+ for (i = 0; i < 8; i++) {
+ md[8*i] = s->h[i] >> 56;
+ md[8*i+1] = s->h[i] >> 48;
+ md[8*i+2] = s->h[i] >> 40;
+ md[8*i+3] = s->h[i] >> 32;
+ md[8*i+4] = s->h[i] >> 24;
+ md[8*i+5] = s->h[i] >> 16;
+ md[8*i+6] = s->h[i] >> 8;
+ md[8*i+7] = s->h[i];
+ }
+}
+
+void
+sha512_update(void *ctx, const void *m, unsigned long len)
+{
+ struct sha512 *s = ctx;
+ const uint8_t *p = m;
+ unsigned r = s->len % 128;
+
+ s->len += len;
+ if (r) {
+ if (len < 128 - r) {
+ memcpy(s->buf + r, p, len);
+ return;
+ }
+ memcpy(s->buf + r, p, 128 - r);
+ len -= 128 - r;
+ p += 128 - r;
+ processblock(s, s->buf);
+ }
+ for (; len >= 128; len -= 128, p += 128)
+ processblock(s, p);
+ memcpy(s->buf, p, len);
+}
diff --git a/sbase/libutil/strcasestr.c b/sbase/libutil/strcasestr.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2005-2014 Rich Felker, et al.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+#include <string.h>
+#include <strings.h>
+
+#include "../util.h"
+
+char *
+strcasestr(const char *h, const char *n)
+{
+ size_t l = strlen(n);
+ for (; *h; h++) if (!strncasecmp(h, n, l)) return (char *)h;
+ return 0;
+}
diff --git a/sbase/libutil/strlcat.c b/sbase/libutil/strlcat.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+#include <sys/types.h>
+
+#include "../util.h"
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+ return(dlen + (s - src)); /* count does not include NUL */
+}
diff --git a/sbase/libutil/strlcpy.c b/sbase/libutil/strlcpy.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+#include <sys/types.h>
+
+#include "../util.h"
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0')
+ break;
+ }
+ }
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+ return(s - src - 1); /* count does not include NUL */
+}
diff --git a/sbase/link.1 b/sbase/link.1
@@ -0,0 +1,14 @@
+.TH LN 1 sbase\-VERSION
+.SH NAME
+link \- create a hard link by calling the link function
+.SH SYNOPSIS
+.B link
+.I target
+.I linkname
+.P
+.SH DESCRIPTION
+.B link
+creates a hard link to a given file, with the given name.
+.SH SEE ALSO
+.IR ln (1),
+.IR link (2)
diff --git a/sbase/link.c b/sbase/link.c
@@ -0,0 +1,17 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <unistd.h>
+
+#include "util.h"
+
+int
+main(int argc, char *argv[])
+{
+ argv0 = argv[0];
+
+ if (argc != 3)
+ eprintf("usage: %s target linkname\n", argv0);
+ if (link(argv[1], argv[2]) < 0)
+ eprintf("link:");
+ return 0;
+}
diff --git a/sbase/ln.1 b/sbase/ln.1
@@ -0,0 +1,36 @@
+.TH LN 1 sbase\-VERSION
+.SH NAME
+ln \- make links between files
+.SH SYNOPSIS
+.B ln
+.RB [ \-LPfs ]
+.I file
+.RI [ name ]
+.P
+.B ln
+.RB [ \-LPfs ]
+.RI [ file ...]
+.RI [ directory ]
+.SH DESCRIPTION
+.B ln
+creates a hard link to a given file, with the given name. If no name is given
+it is linked into the current directory. If multiple files are listed they will
+be linked into the given directory.
+.SH OPTIONS
+.TP
+.B \-L
+create links to the files referenced by symbolic link source files (default
+behavior).
+.TP
+.B \-P
+create links to symbolic link source files themselves.
+.TP
+.B \-f
+remove existing destinations.
+.TP
+.B \-s
+create a symlink.
+.SH SEE ALSO
+.IR cp (1),
+.IR link (2),
+.IR symlink (2)
diff --git a/sbase/ln.c b/sbase/ln.c
@@ -0,0 +1,78 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %1$s [-LPfs] target [linkname]\n"
+ " %1$s [-LPfs] target... directory\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *fname, *to;
+ int sflag = 0;
+ int fflag = 0;
+ int hasto = 0;
+ int dirfd = AT_FDCWD;
+ int flags = AT_SYMLINK_FOLLOW;
+ struct stat st;
+
+ ARGBEGIN {
+ case 'f':
+ fflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 'L':
+ flags |= AT_SYMLINK_FOLLOW;
+ break;
+ case 'P':
+ flags &= ~AT_SYMLINK_FOLLOW;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0)
+ usage();
+
+ fname = sflag ? "symlink" : "link";
+
+ if (argc >= 2) {
+ if (stat(argv[argc - 1], &st) == 0 && S_ISDIR(st.st_mode)) {
+ if ((dirfd = open(argv[argc - 1], O_RDONLY)) < 0)
+ eprintf("open:");
+ } else if (argc == 2) {
+ to = argv[1];
+ hasto = 1;
+ } else {
+ eprintf("destination is not a directory\n");
+ }
+ argc--;
+ }
+
+ for (; argc > 0; argc--, argv++) {
+ if (!hasto)
+ to = basename(argv[0]);
+ if (fflag)
+ remove(to);
+ if ((!sflag ? linkat(AT_FDCWD, argv[0], dirfd, to, flags)
+ : symlinkat(argv[0], dirfd, to)) < 0) {
+ eprintf("%s %s <- %s:", fname, argv[0], to);
+ }
+ }
+
+ return 0;
+}
diff --git a/sbase/logger.1 b/sbase/logger.1
@@ -0,0 +1,50 @@
+.Dd December 4, 2014
+.Dt LOGGER 1 sbase\-VERSION
+.Os
+.Sh NAME
+.Nm logger
+.Nd make entries in the system log
+.Sh SYNOPSIS
+.Nm logger
+.Op Fl is
+.Op Fl p Ar priority
+.Op Fl t Ar tag
+.Op Ar message ...
+.Sh DESCRIPTION
+.Nm
+provides a shell command interface to the
+.Xr syslog 3
+system log module.
+.Pp
+.Sh OPTIONS
+.Bl -tag -width xxxxxxxxxxxx
+.It Fl i
+Log the process ID of the logger process with each line.
+.It Fl p Ar priority
+Enter the message with the specified priority. They priority
+has to be specified symbolically as
+.Dq facility.level
+pair. The default is
+.Dq user.notice .
+.It Fl s
+Log the message to standard error, as well as the system log.
+.It Fl t Ar tag
+Mark every line in the log with the specified
+.Ar tag .
+.It Ar message
+Write the message to the log; if not specified, standard input
+is logged.
+.El
+.Sh SEE ALSO
+.Xr syslog 3 ,
+.Xr syslogd 1
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
+.Pp
+The flags
+.Op Fl ipst
+are extensions to that specification.
diff --git a/sbase/logger.c b/sbase/logger.c
@@ -0,0 +1,91 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#define SYSLOG_NAMES
+#include <syslog.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static int
+decodetable(CODE *table, char *name)
+{
+ CODE *c;
+
+ for (c = table; c->c_name; c++)
+ if (!strcasecmp(name, c->c_name))
+ return c->c_val;
+ eprintf("invalid priority name: %s\n", name);
+ /* NOTREACHED */
+ return -1;
+}
+
+static int
+decodepri(char *pri)
+{
+ char *lev, *fac = pri;
+
+ if (!(lev = strchr(pri, '.')))
+ eprintf("invalid priority name: %s\n", pri);
+ *lev++ = '\0';
+ if (!*lev)
+ eprintf("invalid priority name: %s\n", pri);
+ return (decodetable(facilitynames, fac) & LOG_FACMASK) |
+ (decodetable(prioritynames, lev) & LOG_PRIMASK);
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-is] [-p priority] [-t tag] [message ...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *buf = NULL, *tag = NULL;
+ size_t sz = 0;
+ int logflags = 0, priority = LOG_NOTICE;
+ int i;
+
+ ARGBEGIN {
+ case 'i':
+ logflags |= LOG_PID;
+ break;
+ case 'p':
+ priority = decodepri(EARGF(usage()));
+ break;
+ case 's':
+ logflags |= LOG_PERROR;
+ break;
+ case 't':
+ tag = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ openlog(tag ? tag : getlogin(), logflags, 0);
+
+ if (argc == 0) {
+ while(getline(&buf, &sz, stdin) != -1)
+ syslog(priority, "%s", buf);
+ if (ferror(stdin))
+ eprintf("%s: read error:", "<stdin>");
+ } else {
+ for (i = 0; i < argc; i++)
+ sz += strlen(argv[i]);
+ sz += argc;
+ buf = ecalloc(1, sz);
+ for (i = 0; i < argc; i++) {
+ strlcat(buf, argv[i], sz);
+ if (i + 1 < argc)
+ strlcat(buf, " ", sz);
+ }
+ syslog(priority, "%s", buf);
+ }
+ closelog();
+ return 0;
+}
diff --git a/sbase/logname.1 b/sbase/logname.1
@@ -0,0 +1,8 @@
+.TH LOGNAME 1 sbase\-VERSION
+.SH NAME
+logname \- return the user's login name
+.SH SYNOPSIS
+.B logname
+.SH DESCRIPTION
+.B logname
+prints the login name of the current user.
diff --git a/sbase/logname.c b/sbase/logname.c
@@ -0,0 +1,20 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <unistd.h>
+
+#include "util.h"
+
+int
+main(int argc, char *argv[])
+{
+ char *login;
+
+ argv0 = argv[0];
+ if (argc != 1)
+ eprintf("usage: %s\n", argv0);
+ if ((login = getlogin()))
+ puts(login);
+ else
+ eprintf("%s: no login name\n", argv0);
+ return 0;
+}
diff --git a/sbase/ls.1 b/sbase/ls.1
@@ -0,0 +1,46 @@
+.Dd January 20, 2015
+.Dt LS 1 sbase\-VERSION
+.Sh NAME
+.Nm ls
+.Nd list directory contents
+.Sh SYNOPSIS
+.Nm ls
+.Op Fl 1acdFHhiLlrtU
+.Op Ar file ...
+.Sh DESCRIPTION
+.Nm
+lists each given file, and the contents of each given directory. If no files
+are given the current directory is listed.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl a
+Show hidden files (those beginning with '.').
+.It Fl c
+Use time file's status was last changed instead of last
+modification time for sorting or printing.
+.It Fl d
+List directories themselves, not their contents.
+.It Fl F
+Append a file type indicator to files.
+.It Fl H
+List information about the targets of symbolic links specified on the command
+line instead of the links themselves.
+.It Fl h
+Show filesizes in human\-readable format.
+.It Fl i
+Print the index number of each file.
+.It Fl L
+List information about the targets of symbolic links instead of the links
+themselves.
+.It Fl l
+List detailed information about each file, including their type, permissions,
+links, owner, group, size, and last file status/modification time.
+.It Fl r
+Reverse the sort order.
+.It Fl t
+Sort files by last file status/modification time instead of by name.
+.It Fl U
+Keep the list unsorted.
+.El
+.Sh SEE ALSO
+.Xr stat 2
diff --git a/sbase/ls.c b/sbase/ls.c
@@ -0,0 +1,310 @@
+/* See LICENSE file for copyright and license details. */
+#include <dirent.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "util.h"
+
+typedef struct {
+ char *name;
+ mode_t mode, tmode;
+ nlink_t nlink;
+ uid_t uid;
+ gid_t gid;
+ off_t size;
+ time_t t;
+ ino_t ino;
+} Entry;
+
+static int entcmp(const void *, const void *);
+static void ls(Entry *);
+static void lsdir(const char *);
+static void mkent(Entry *, char *, int, int);
+static void output(Entry *);
+
+static int aflag = 0;
+static int cflag = 0;
+static int dflag = 0;
+static int Fflag = 0;
+static int Hflag = 0;
+static int hflag = 0;
+static int iflag = 0;
+static int Lflag = 0;
+static int lflag = 0;
+static int rflag = 0;
+static int tflag = 0;
+static int Uflag = 0;
+static int first = 1;
+static int many;
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-1acdFHhiLlrtU] [file ...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i;
+ Entry *ents;
+
+ ARGBEGIN {
+ case '1':
+ /* ignore */
+ break;
+ case 'a':
+ aflag = 1;
+ break;
+ case 'c':
+ cflag = 1;
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'F':
+ Fflag = 1;
+ break;
+ case 'H':
+ Hflag = 1;
+ break;
+ case 'h':
+ hflag = 1;
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+ case 'L':
+ Lflag = 1;
+ break;
+ case 'l':
+ lflag = 1;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 't':
+ tflag = 1;
+ break;
+ case 'U':
+ Uflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ many = (argc > 1);
+ if (argc == 0)
+ *--argv = ".", argc++;
+
+ ents = emalloc(argc * sizeof(*ents));
+
+ for (i = 0; i < argc; i++)
+ mkent(&ents[i], argv[i], 1, Hflag || Lflag);
+ qsort(ents, argc, sizeof *ents, entcmp);
+ for (i = 0; i < argc; i++)
+ ls(&ents[rflag ? argc-i-1 : i]);
+
+ return 0;
+}
+
+static int
+entcmp(const void *va, const void *vb)
+{
+ const Entry *a = va, *b = vb;
+
+ if (tflag)
+ return b->t - a->t;
+ else
+ return strcmp(a->name, b->name);
+}
+
+static void
+ls(Entry *ent)
+{
+ if ((S_ISDIR(ent->mode) || (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode) && !Fflag && !lflag)) && !dflag) {
+ lsdir(ent->name);
+ } else {
+ output(ent);
+ }
+}
+
+static void
+lsdir(const char *path)
+{
+ char *cwd, *p;
+ long i, n = 0;
+ struct dirent *d;
+ DIR *dp;
+ Entry ent, *ents = NULL;
+ size_t sz;
+
+ cwd = agetcwd();
+ if (!(dp = opendir(path)))
+ eprintf("opendir %s:", path);
+ if (chdir(path) < 0)
+ eprintf("chdir %s:", path);
+
+ if (many) {
+ if (!first)
+ putchar('\n');
+ printf("%s:\n", path);
+ first = 0;
+ }
+
+ while ((d = readdir(dp))) {
+ if (d->d_name[0] == '.' && !aflag)
+ continue;
+ if (Uflag){
+ mkent(&ent, d->d_name, Fflag || lflag || iflag, Lflag);
+ output(&ent);
+ } else {
+ ents = erealloc(ents, ++n * sizeof *ents);
+ p = emalloc((sz = strlen(d->d_name)+1));
+ memcpy(p, d->d_name, sz);
+ mkent(&ents[n-1], p, tflag || Fflag || lflag || iflag, Lflag);
+ }
+ }
+ closedir(dp);
+ if (!Uflag){
+ qsort(ents, n, sizeof *ents, entcmp);
+ for (i = 0; i < n; i++) {
+ output(&ents[rflag ? n-i-1 : i]);
+ free(ents[rflag ? n-i-1 : i].name);
+ }
+ }
+ if (chdir(cwd) < 0)
+ eprintf("chdir %s:", cwd);
+ free(ents);
+ free(cwd);
+}
+
+static void
+mkent(Entry *ent, char *path, int dostat, int follow)
+{
+ struct stat st;
+
+ ent->name = path;
+ if (!dostat)
+ return;
+ if ((follow ? stat : lstat)(path, &st) < 0)
+ eprintf("%s %s:", follow ? "stat" : "lstat", path);
+ ent->mode = st.st_mode;
+ ent->nlink = st.st_nlink;
+ ent->uid = st.st_uid;
+ ent->gid = st.st_gid;
+ ent->size = st.st_size;
+ ent->t = cflag ? st.st_ctime : st.st_mtime;
+ ent->ino = st.st_ino;
+ if (S_ISLNK(ent->mode))
+ ent->tmode = stat(path, &st) == 0 ? st.st_mode : 0;
+}
+
+static char *
+indicator(mode_t mode)
+{
+ if (!Fflag)
+ return "";
+
+ if (S_ISLNK(mode))
+ return "@";
+ else if (S_ISDIR(mode))
+ return "/";
+ else if (S_ISFIFO(mode))
+ return "|";
+ else if (S_ISSOCK(mode))
+ return "=";
+ else if (mode & S_IXUSR ||
+ mode & S_IXGRP ||
+ mode & S_IXOTH)
+ return "*";
+ else
+ return "";
+}
+
+static void
+output(Entry *ent)
+{
+ char buf[BUFSIZ], *fmt;
+ char mode[] = "----------";
+ ssize_t len;
+ struct group *gr;
+ struct passwd *pw;
+ char pwname[_SC_LOGIN_NAME_MAX];
+ char grname[_SC_LOGIN_NAME_MAX];
+
+ if (iflag)
+ printf("%lu ", (unsigned long)ent->ino);
+ if (!lflag) {
+ printf("%s%s\n", ent->name, indicator(ent->mode));
+ return;
+ }
+ if (S_ISREG(ent->mode))
+ mode[0] = '-';
+ else if (S_ISBLK(ent->mode))
+ mode[0] = 'b';
+ else if (S_ISCHR(ent->mode))
+ mode[0] = 'c';
+ else if (S_ISDIR(ent->mode))
+ mode[0] = 'd';
+ else if (S_ISFIFO(ent->mode))
+ mode[0] = 'p';
+ else if (S_ISLNK(ent->mode))
+ mode[0] = 'l';
+ else if (S_ISSOCK(ent->mode))
+ mode[0] = 's';
+ else
+ mode[0] = '?';
+
+ if (ent->mode & S_IRUSR) mode[1] = 'r';
+ if (ent->mode & S_IWUSR) mode[2] = 'w';
+ if (ent->mode & S_IXUSR) mode[3] = 'x';
+ if (ent->mode & S_IRGRP) mode[4] = 'r';
+ if (ent->mode & S_IWGRP) mode[5] = 'w';
+ if (ent->mode & S_IXGRP) mode[6] = 'x';
+ if (ent->mode & S_IROTH) mode[7] = 'r';
+ if (ent->mode & S_IWOTH) mode[8] = 'w';
+ if (ent->mode & S_IXOTH) mode[9] = 'x';
+
+ if (ent->mode & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
+ if (ent->mode & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
+ if (ent->mode & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T';
+
+ pw = getpwuid(ent->uid);
+ if (pw)
+ snprintf(pwname, sizeof(pwname), "%s", pw->pw_name);
+ else
+ snprintf(pwname, sizeof(pwname), "%d", ent->uid);
+
+ gr = getgrgid(ent->gid);
+ if (gr)
+ snprintf(grname, sizeof(grname), "%s", gr->gr_name);
+ else
+ snprintf(grname, sizeof(grname), "%d", ent->gid);
+
+ if (time(NULL) > ent->t + (180*24*60*60)) /* 6 months ago? */
+ fmt = "%b %d %Y";
+ else
+ fmt = "%b %d %H:%M";
+
+ strftime(buf, sizeof buf, fmt, localtime(&ent->t));
+ printf("%s %4ld %-8.8s %-8.8s ", mode, (long)ent->nlink, pwname, grname);
+ if (hflag)
+ printf("%10s ", humansize((unsigned long)ent->size));
+ else
+ printf("%10lu ", (unsigned long)ent->size);
+ printf("%s %s%s", buf, ent->name, indicator(ent->mode));
+ if (S_ISLNK(ent->mode)) {
+ if ((len = readlink(ent->name, buf, sizeof buf - 1)) < 0)
+ eprintf("readlink %s:", ent->name);
+ buf[len] = '\0';
+ printf(" -> %s%s", buf, indicator(ent->tmode));
+ }
+ putchar('\n');
+}
diff --git a/sbase/md5.h b/sbase/md5.h
@@ -0,0 +1,18 @@
+/* public domain md5 implementation based on rfc1321 and libtomcrypt */
+
+struct md5 {
+ uint64_t len; /* processed message length */
+ uint32_t h[4]; /* hash state */
+ uint8_t buf[64]; /* message block buffer */
+};
+
+enum { MD5_DIGEST_LENGTH = 16 };
+
+/* reset state */
+void md5_init(void *ctx);
+/* process message */
+void md5_update(void *ctx, const void *m, unsigned long len);
+/* get message digest */
+/* state is ruined after sum, keep a copy if multiple sum is needed */
+/* part of the message might be left in s, zero it if secrecy is needed */
+void md5_sum(void *ctx, uint8_t md[MD5_DIGEST_LENGTH]);
diff --git a/sbase/md5sum.1 b/sbase/md5sum.1
@@ -0,0 +1,12 @@
+.TH MD5SUM 1 sbase\-VERSION
+.SH NAME
+md5sum \- compute MD5 message digest
+.SH SYNOPSIS
+.B md5sum
+.RB [\-c]
+.RI [ file ...]
+.TP
+.B \-c
+read list of MD5 checksums from file and check them
+.SH DESCRIPTION
+Print MD5 (128-bit) checksums. With no file, read standard input.
diff --git a/sbase/md5sum.c b/sbase/md5sum.c
@@ -0,0 +1,43 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "crypt.h"
+#include "md5.h"
+#include "util.h"
+
+static struct md5 s;
+struct crypt_ops md5_ops = {
+ md5_init,
+ md5_update,
+ md5_sum,
+ &s,
+};
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-c] [file...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ uint8_t md[MD5_DIGEST_LENGTH];
+ char *checkfile = NULL;
+ int cflag = 0;
+
+ ARGBEGIN {
+ case 'c':
+ cflag = 1;
+ checkfile = ARGF();
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (cflag)
+ return cryptcheck(checkfile, argc, argv, &md5_ops, md, sizeof(md));
+ return cryptmain(argc, argv, &md5_ops, md, sizeof(md));
+}
diff --git a/sbase/mkdir.1 b/sbase/mkdir.1
@@ -0,0 +1,19 @@
+.TH MKDIR 1 sbase\-VERSION
+.SH NAME
+mkdir \- make directory
+.SH SYNOPSIS
+.B mkdir
+.RB [ \-pm ]
+.RI [ name ...]
+.SH DESCRIPTION
+.B mkdir
+creates the specified directories.
+.SH OPTIONS
+.TP
+.B \-p
+creates any necessary parent directories, and does not fail if the target
+already exists.
+.B \-m
+set the file permission bits of the newly created directory.
+.SH SEE ALSO
+.IR mkdir (2)
diff --git a/sbase/mkdir.c b/sbase/mkdir.c
@@ -0,0 +1,68 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void mkdirp(char *);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-pm] directory...\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int pflag = 0;
+ int mflag = 0;
+ int mode;
+
+ ARGBEGIN {
+ case 'p':
+ pflag = 1;
+ break;
+ case 'm':
+ mflag = 1;
+ mode = estrtol(EARGF(usage()), 8);
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ for (; argc > 0; argc--, argv++) {
+ if (pflag) {
+ mkdirp(argv[0]);
+ } else if (mkdir(argv[0], S_IRWXU|S_IRWXG|S_IRWXO) < 0) {
+ eprintf("mkdir %s:", argv[0]);
+ }
+ if (mflag)
+ if (chmod(argv[0], mode) < 0)
+ eprintf("chmod %s:", argv[0]);
+ }
+
+ return 0;
+}
+
+static void
+mkdirp(char *path)
+{
+ char *p = path;
+
+ do {
+ if (*p && (p = strchr(&p[1], '/')))
+ *p = '\0';
+ if (mkdir(path, S_IRWXU|S_IRWXG|S_IRWXO) < 0 && errno != EEXIST)
+ eprintf("mkdir %s:", path);
+ if (p)
+ *p = '/';
+ } while (p);
+}
diff --git a/sbase/mkfifo.1 b/sbase/mkfifo.1
@@ -0,0 +1,19 @@
+.TH MKFIFO 1 sbase\-VERSION
+.SH NAME
+mkfifo \- make named pipe
+.SH SYNOPSIS
+.B mkfifo
+.RB [ \-m
+.IR mode ]
+.I name ...
+.SH DESCRIPTION
+.B mkfifo
+creates named pipes (FIFOs) with the given names.
+.SH OPTIONS
+.TP
+.B \-m
+Set the file permission bits of newly created FIFOs to mode. The mode
+is specified in octal as we do not currently support all the formats that
+the chmod(1) utility supports.
+.SH SEE ALSO
+.IR mkfifo (3)
diff --git a/sbase/mkfifo.c b/sbase/mkfifo.c
@@ -0,0 +1,40 @@
+/* See LICENSE file for copyright and license details. */
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-m mode] name...\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP |
+ S_IWGRP | S_IROTH | S_IWOTH;
+ int ret = 0;
+
+ ARGBEGIN {
+ case 'm':
+ mode = estrtol(EARGF(usage()), 8);
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ for (; argc > 0; argc--, argv++) {
+ if (mkfifo(argv[0], mode) < 0) {
+ weprintf("mkfifo %s:", argv[0]);
+ ret = 1;
+ }
+ }
+ return ret;
+}
diff --git a/sbase/mktemp.1 b/sbase/mktemp.1
@@ -0,0 +1,25 @@
+.TH MKTEMP 1 sbase\-VERSION
+.SH NAME
+mktemp \- make temporary filename
+.SH SYNOPSIS
+.B mktemp
+.RB [ \-dq ]
+.RB [ template ]
+.SH DESCRIPTION
+.B mktemp
+takes the given filename template and overwrites a portion of it
+to create a unique filename. The template may be any filename with at least
+six `Xs' appended to it. If no template is specified a default of
+`tmp.XXXXXXXXXX' is used and the tmpdir is set to `/tmp' unless the
+TMPDIR envrionment variable has been set.
+.SH OPTIONS
+.TP
+.B \-d
+Make a directory instead of a file
+.TP
+.B \-q
+Fail silently if an error occurs. This is useful if a script
+does not want error output to go to standard error.
+.SH SEE ALSO
+.IR mkdtemp (3),
+.IR mkstemp (3)
diff --git a/sbase/mktemp.c b/sbase/mktemp.c
@@ -0,0 +1,78 @@
+/* See LICENSE file for copyright and license details. */
+#include <libgen.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-dq] [template]\n", argv0);
+}
+
+static int dflag = 0;
+static int qflag = 0;
+
+int
+main(int argc, char *argv[])
+{
+ char *template = "tmp.XXXXXXXXXX";
+ char *tmpdir = "/tmp", *p;
+ char path[PATH_MAX], tmp[PATH_MAX];
+ int fd;
+
+ ARGBEGIN {
+ case 'd':
+ dflag = 1;
+ break;
+ case 'q':
+ qflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc > 1)
+ usage();
+ else if (argc == 1)
+ template = argv[0];
+
+ if ((p = getenv("TMPDIR")))
+ tmpdir = p;
+
+ if (strlcpy(tmp, template, sizeof(tmp)) >= sizeof(tmp))
+ eprintf("path too long\n");
+ p = dirname(tmp);
+ if (p[0] != '.') {
+ if (strlcpy(path, template, sizeof(path)) >= sizeof(path))
+ eprintf("path too long\n");
+ } else {
+ if (strlcpy(path, tmpdir, sizeof(path)) >= sizeof(path))
+ eprintf("path too long\n");
+ if (strlcat(path, "/", sizeof(path)) >= sizeof(path))
+ eprintf("path too long\n");
+ if (strlcat(path, template, sizeof(path)) >= sizeof(path))
+ eprintf("path too long\n");
+ }
+
+ if (dflag) {
+ if (!mkdtemp(path)) {
+ if (!qflag)
+ eprintf("mkdtemp %s:", path);
+ exit(1);
+ }
+ } else {
+ if ((fd = mkstemp(path)) < 0) {
+ if (!qflag)
+ eprintf("mkstemp %s:", path);
+ exit(1);
+ }
+ close(fd);
+ }
+ puts(path);
+ return 0;
+}
diff --git a/sbase/mv.1 b/sbase/mv.1
@@ -0,0 +1,22 @@
+.TH MV 1 sbase\-VERSION
+.SH NAME
+mv \- move files and directories
+.SH SYNOPSIS
+.B mv
+.RB [ \-f ]
+.I file
+.RI [ name ]
+.P
+.B mv
+.RB [ \-f ]
+.RI [ file ...]
+.RI [ directory ]
+.SH DESCRIPTION
+.B mv
+moves or renames a given file or directory, naming it the given name. If
+multiple files and directories are listed they will be moved into the given
+directory.
+.SH OPTIONS
+.TP
+.B \-f
+do not prompt for confirmation before overwriting the destination path.
diff --git a/sbase/mv.c b/sbase/mv.c
@@ -0,0 +1,54 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "fs.h"
+#include "util.h"
+
+static int mv(const char *, const char *);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-f] source... dest\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct stat st;
+
+ ARGBEGIN {
+ case 'f':
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 2)
+ usage();
+
+ if (argc > 3 && !(stat(argv[argc-1], &st) == 0 && S_ISDIR(st.st_mode)))
+ eprintf("%s: not a directory\n", argv[argc-1]);
+ enmasse(argc, &argv[0], mv);
+
+ return 0;
+}
+
+static int
+mv(const char *s1, const char *s2)
+{
+ if (rename(s1, s2) == 0)
+ return 0;
+ if (errno == EXDEV) {
+ cp_rflag = 1;
+ rm_rflag = 1;
+ cp(s1, s2);
+ rm(s1);
+ return 0;
+ }
+ return -1;
+}
diff --git a/sbase/nice.1 b/sbase/nice.1
@@ -0,0 +1,18 @@
+.TH NICE 1 sbase\-VERSION
+.SH NAME
+nice \- invoke a utility with an altered nice value
+.SH SYNOPSIS
+.B nice
+.RB [ \-n inc ]
+.I command
+.RI [ options ...]
+.SH DESCRIPTION
+.B nice
+invokes
+.IR command
+with a nice value equal to the current nice value plus
+.IR inc,
+which defaults to 10 if unspecified.
+
+.SH SEE ALSO
+.IR renice (1) nice (2)
diff --git a/sbase/nice.c b/sbase/nice.c
@@ -0,0 +1,49 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: nice [-n inc] command [options ...]\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ long val = 10;
+ int savederrno;
+
+ ARGBEGIN {
+ case 'n':
+ val = estrtol(EARGF(usage()), 10);
+ break;
+ default:
+ usage();
+ break;
+ } ARGEND;
+
+ if (argc == 0)
+ usage();
+
+ errno = 0;
+ val += getpriority(PRIO_PROCESS, 0);
+ if (errno != 0)
+ weprintf("getpriority:");
+ val = MAX(PRIO_MIN, MIN(val, PRIO_MAX));
+ if (setpriority(PRIO_PROCESS, 0, val) != 0)
+ weprintf("setpriority:");
+
+ /* POSIX specifies the nice failure still invokes the command */
+ execvp(argv[0], argv);
+ savederrno = errno;
+ weprintf("execvp %s:", argv[0]);
+ return (savederrno == ENOENT)? 127 : 126;
+}
diff --git a/sbase/nl.1 b/sbase/nl.1
@@ -0,0 +1,46 @@
+.Dd December 4, 2014
+.Dt NL 1 sbase\-VERSION
+.Os
+.Sh NAME
+.Nm nl
+.Nd line numbering filter
+.Sh SYNOPSIS
+.Nm nl
+.Op Fl b Ar type
+.Op Fl i Ar incr
+.Op Fl s Ar sep
+.Op Ar file
+.Sh DESCRIPTION
+.Nm
+reads lines from the named
+.Ar file
+and writes them to stdout with non-empty lines
+numbered. If no file is given
+.Nm
+reads from stdin.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl b Ar type
+Defines which lines will be numbered:
+.Bl -tag -width pstringXX
+.It a
+All lines.
+.It n
+No lines.
+.It t
+Only non-empty lines (default).
+.It p Ns Ar expr
+Only lines which match
+.Ar expr ,
+a regular expression as defined in
+.IR regex (7).
+.TP
+.El
+.It Fl i Ar incr
+Defines the increment between numbered lines.
+.It Fl s Ar sep
+Defines the string used to separate line numbers and lines. By default this is
+a tab.
+.El
+.Sh SEE ALSO
+.Xr pr 1
diff --git a/sbase/nl.c b/sbase/nl.c
@@ -0,0 +1,81 @@
+/* See LICENSE file for copyright and license details. */
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "text.h"
+#include "util.h"
+
+static void nl(const char *, FILE *);
+
+static char mode = 't';
+static const char *sep = "\t";
+static long incr = 1;
+static regex_t preg;
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-b type] [-i incr] [-s sep] [file]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+ char *r;
+
+ ARGBEGIN {
+ case 'b':
+ r = EARGF(usage());
+ mode = r[0];
+ if (r[0] == 'p')
+ eregcomp(&preg, &r[1], REG_NOSUB);
+ else if (!strchr("ant", mode))
+ usage();
+ break;
+ case 'i':
+ incr = estrtol(EARGF(usage()), 0);
+ break;
+ case 's':
+ sep = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc > 1)
+ usage();
+
+ if (argc == 0) {
+ nl("<stdin>", stdin);
+ } else {
+ if (!(fp = fopen(argv[0], "r")))
+ eprintf("fopen %s:", argv[0]);
+ nl(argv[0], fp);
+ fclose(fp);
+ }
+ return 0;
+}
+
+void
+nl(const char *name, FILE *fp)
+{
+ char *buf = NULL;
+ long n = 0;
+ size_t size = 0;
+
+ while (getline(&buf, &size, fp) != -1) {
+ if ((mode == 'a')
+ || (mode == 'p' && !regexec(&preg, buf, 0, NULL, 0))
+ || (mode == 't' && buf[0] != '\n'))
+ printf("%6ld%s%s", n += incr, sep, buf);
+ else
+ printf(" %s", buf);
+ }
+ free(buf);
+ if (ferror(fp))
+ eprintf("%s: read error:", name);
+}
diff --git a/sbase/nohup.1 b/sbase/nohup.1
@@ -0,0 +1,16 @@
+.TH NOHUP 1 sbase\-VERSION
+.SH NAME
+nohup \- run a command immune to hangups
+.SH SYNOPSIS
+.B nohup
+.IR command ...
+.SH DESCRIPTION
+.B nohup
+runs the given command with the
+.I HUP
+signal set to be ignored. If stdout is a terminal, it is appended to
+.I nohup.out
+in the current working directory; if stderr is a terminal, it is redirected to
+stdout.
+.SH SEE ALSO
+.IR signal (7)
diff --git a/sbase/nohup.c b/sbase/nohup.c
@@ -0,0 +1,53 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "util.h"
+
+enum { Error = 127, Found = 126 };
+
+static void
+usage(void)
+{
+ eprintf("usage: %s command [argument...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0)
+ usage();
+
+ if (signal(SIGHUP, SIG_IGN) == SIG_ERR)
+ enprintf(Error, "signal HUP:");
+
+ if (isatty(STDOUT_FILENO)) {
+ if ((fd = open("nohup.out", O_APPEND|O_CREAT,
+ S_IRUSR|S_IWUSR)) < 0) {
+ enprintf(Error, "open nohup.out:");
+ }
+ if (dup2(fd, STDOUT_FILENO) < 0)
+ enprintf(Error, "dup2:");
+ close(fd);
+ }
+ if (isatty(STDERR_FILENO))
+ if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0)
+ enprintf(Error, "dup2:");
+
+ execvp(argv[0], &argv[0]);
+ enprintf(errno == ENOENT ? Error : Found, "exec %s:", argv[0]);
+ _exit(Error);
+ /* unreachable */
+ return 0;
+}
diff --git a/sbase/paste.1 b/sbase/paste.1
@@ -0,0 +1,122 @@
+.TH PASTE 1 paste-VERSION "Apr 2013"
+.SH NAME
+paste \- merge corresponding or subsequent lines of files
+.SH "SYNOPSIS"
+.PP
+.B paste
+[
+.B \-s
+]
+[
+.B \-d
+.I list
+]
+.I file...
+.SH DESCRIPTION
+The
+.B paste
+utility concatenates the corresponding lines of the given input files,
+and writes the resulting lines to standard output. The default operation
+of
+.B paste
+concatenates the corresponding lines of the input files.
+The newline of every line except the line from the last input file is
+replaced with a tab.
+If an end-of-file condition is detected on one or more input files,
+but not all input files,
+.B paste
+behaves as though empty lines were read from the files on which
+end-of-file was detected, unless the
+.B \-s
+option is specified.
+.SH OPTIONS
+.TP
+.B \-d list
+unless a backslash character appears in
+.I list
+each character is an element specifying a delimiter.
+If a backslash character appears, that and one or more characters
+following it are an element specifying a delimiter.
+These elements specify one or more characters to use,
+instead of the default tab, to replace the newline of the input
+lines. The elements in
+.I list
+are used circularly; that is, when the
+.I list
+is exhausted the first element from the list is reused.
+When the
+.B \-s
+option is specified, the last newline in a file is not be modified.
+The delimiter is reset to the first element of list after each file
+operand is processed.
+If a backslash character appears in list, it and the character following
+it represents the following delimiters:
+.RS
+.TP
+.I \en
+newline character
+.TP
+.I \et
+tab character
+.TP
+.I \e\e
+backslash character
+.TP
+.I \e0
+empty string (not a null character)
+.TP
+If Any other characters follow the backslash, results are unspecified.
+.RE
+.TP
+.B \-s
+concatenate all of the lines of each separate input file in command line
+order. The newline of every line except the last line in each input file
+are replaced with the tab, unless otherwise specified by the
+.B \-d
+option.
+.PP
+If '\-' is specified for one or more input files, the standard input is
+used; standard input is read one line at a time, circularly for each
+instance of '\-'.
+.SH EXIT VALUES
+The
+.B paste
+utility exits 0 on successful completion, and >0 if an error
+occurs.
+.SH ENVIRONMENT VARIABLES
+The following environment variables affect the execution:
+.TP
+.B LANG
+provide a default value for the internationalization variables
+that are unset or null.
+.TP
+.B LC_ALL
+if set to a non-empty string value, override the values of all the
+other internationalization variables.
+.TP
+.B LC_CTYPE
+determine the locale for the interpretation of sequences of bytes
+of text data as characters (for example, single-byte as opposed to
+multi-byte characters in arguments and input files).
+.TP
+.B LC_MESSAGES
+determine the locale that should be used to affect the format and
+contents of diagnostic messages written to standard error.
+.SH CONFORMING TO
+The
+.B paste
+utility is IEEE Std 1003.2 (POSIX.2) compatible.
+.SH EXAMPLES
+.TP
+.I "ls | paste - - - -"
+.PP
+Write out a directory in four columns.
+.TP
+.I "paste -s -d '\et\en' file"
+.PP
+Combine pairs of lines from a file into single lines.
+.SH AUTHOR
+Written by Lorenzo Cogotti.
+.SH SEE ALSO
+.BR cut(1)
+.BR lam(1)
diff --git a/sbase/paste.c b/sbase/paste.c
@@ -0,0 +1,225 @@
+/* See LICENSE file for copyright and license details. */
+#include <locale.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#include "util.h"
+
+typedef struct {
+ FILE *fp;
+ const char *name;
+} Fdescr;
+
+static size_t unescape(wchar_t *);
+static wint_t in(Fdescr *);
+static void out(wchar_t);
+static void sequential(Fdescr *, int, const wchar_t *, size_t);
+static void parallel(Fdescr *, int, const wchar_t *, size_t);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-s] [-d list] file...\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *adelim = NULL;
+ int seq = 0;
+ wchar_t *delim = NULL;
+ size_t len;
+ Fdescr *dsc = NULL;
+ int i;
+
+ setlocale(LC_CTYPE, "");
+
+ ARGBEGIN {
+ case 's':
+ seq = 1;
+ break;
+ case 'd':
+ adelim = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0)
+ usage();
+
+ /* populate delimeters */
+ if (!adelim)
+ adelim = "\t";
+
+ len = mbstowcs(NULL, adelim, 0);
+ if (len == (size_t) - 1)
+ eprintf("invalid delimiter\n");
+
+ delim = emalloc((len + 1) * sizeof(*delim));
+
+ mbstowcs(delim, adelim, len);
+ len = unescape(delim);
+ if (len == 0)
+ eprintf("no delimiters specified\n");
+
+ /* populate file list */
+ dsc = emalloc(argc * sizeof(*dsc));
+
+ for (i = 0; i < argc; i++) {
+ if (strcmp(argv[i], "-") == 0)
+ dsc[i].fp = stdin;
+ else
+ dsc[i].fp = fopen(argv[i], "r");
+
+ if (!dsc[i].fp)
+ eprintf("can't open '%s':", argv[i]);
+
+ dsc[i].name = argv[i];
+ }
+
+ if (seq)
+ sequential(dsc, argc, delim, len);
+ else
+ parallel(dsc, argc, delim, len);
+
+ for (i = 0; i < argc; i++) {
+ if (dsc[i].fp != stdin)
+ (void)fclose(dsc[i].fp);
+ }
+
+ free(delim);
+ free(dsc);
+
+ return 0;
+}
+
+static size_t
+unescape(wchar_t *delim)
+{
+ wchar_t c;
+ size_t i;
+ size_t len;
+
+ for (i = 0, len = 0; (c = delim[i++]) != '\0'; len++) {
+ if (c == '\\') {
+ switch (delim[i++]) {
+ case 'n':
+ delim[len] = '\n';
+ break;
+ case 't':
+ delim[len] = '\t';
+ break;
+ case '0':
+ delim[len] = '\0';
+ break;
+ case '\\':
+ delim[len] = '\\';
+ break;
+ case '\0':
+ default:
+ /* POSIX: unspecified results */
+ return len;
+ }
+ } else
+ delim[len] = c;
+ }
+
+ return len;
+}
+
+static wint_t
+in(Fdescr *f)
+{
+ wint_t c = fgetwc(f->fp);
+
+ if (c == WEOF && ferror(f->fp))
+ eprintf("'%s' read error:", f->name);
+
+ return c;
+}
+
+static void
+out(wchar_t c)
+{
+ putwchar(c);
+ if (ferror(stdout))
+ eprintf("write error:");
+}
+
+static void
+sequential(Fdescr *dsc, int len, const wchar_t *delim, size_t cnt)
+{
+ int i;
+ size_t d;
+ wint_t c, last;
+
+ for (i = 0; i < len; i++) {
+ d = 0;
+ last = WEOF;
+
+ while ((c = in(&dsc[i])) != WEOF) {
+ if (last == '\n') {
+ if (delim[d] != '\0')
+ out(delim[d]);
+
+ d++;
+ d %= cnt;
+ }
+
+ if (c != '\n')
+ out((wchar_t)c);
+
+ last = c;
+ }
+
+ if (last == '\n')
+ out((wchar_t)last);
+ }
+}
+
+static void
+parallel(Fdescr *dsc, int len, const wchar_t *delim, size_t cnt)
+{
+ int last, i;
+ wint_t c, o;
+ wchar_t d;
+
+ do {
+ last = 0;
+ for (i = 0; i < len; i++) {
+ d = delim[i % cnt];
+
+ do {
+ o = in(&dsc[i]);
+ c = o;
+ switch (c) {
+ case WEOF:
+ if (last == 0)
+ break;
+
+ o = '\n';
+ /* fallthrough */
+ case '\n':
+ if (i != len - 1)
+ o = d;
+ break;
+ default:
+ break;
+ }
+
+ if (o != WEOF) {
+ /* pad with delimiters up to this point */
+ while (++last < i) {
+ if (d != '\0')
+ out(d);
+ }
+ out((wchar_t)o);
+ }
+ } while (c != '\n' && c != WEOF);
+ }
+ } while (last > 0);
+}
diff --git a/sbase/printenv.1 b/sbase/printenv.1
@@ -0,0 +1,19 @@
+.TH PRINTENV 1 sbase\-VERSION
+.SH NAME
+printenv \- print out the environment or the values of specific variables.
+.SH SYNOPSIS
+.B printenv
+.RB [ var... ]
+.SH DESCRIPTION
+.B printenv
+prints the entire environment as key=values pairs when
+no
+.IR var
+is specified. Otherwise, in the order specified,
+.B printenv
+prints the value only of each
+.IR var,
+one per line.
+
+.SH SEE ALSO
+.IR env (1)
diff --git a/sbase/printenv.c b/sbase/printenv.c
@@ -0,0 +1,38 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "util.h"
+
+extern char **environ;
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [variable...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *var;
+ int ret = 0;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0) {
+ while (*environ)
+ printf("%s\n", *environ++);
+ } else {
+ while (*argv) {
+ if ((var = getenv(*argv++)))
+ printf("%s\n", var);
+ else
+ ret = 1;
+ }
+ }
+ return ret;
+}
diff --git a/sbase/printf.1 b/sbase/printf.1
@@ -0,0 +1,386 @@
+.\" $OpenBSD: src/usr.bin/printf/printf.1,v 1.27 2014/05/25 07:36:36 jmc Exp $
+.\"
+.\" Copyright (c) 1989, 1990 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)printf.1 5.11 (Berkeley) 7/24/91
+.\"
+.Dd $Mdocdate: May 13 2014 $
+.Dt PRINTF 1
+.Os
+.Sh NAME
+.Nm printf
+.Nd formatted output
+.Sh SYNOPSIS
+.Nm printf
+.Ar format
+.Op Ar argument ...
+.Sh DESCRIPTION
+.Nm printf
+formats and prints its arguments, after the first, under control
+of the
+.Ar format .
+The
+.Ar format
+is a character string which contains three types of objects: plain characters,
+which are simply copied to standard output, character escape sequences which
+are converted and copied to the standard output, and format specifications,
+each of which causes printing of the next successive
+.Ar argument .
+.Pp
+The arguments after the first are treated as strings
+if the corresponding format is
+.Cm b ,
+.Cm c
+or
+.Cm s ;
+otherwise it is evaluated as a C constant, with the following extensions:
+.Bl -bullet -offset indent
+.It
+A leading plus or minus sign is allowed.
+.It
+If the leading character is a single or double quote, the value is the
+.Tn ASCII
+code of the next character.
+.El
+.Pp
+The format string is reused as often as necessary to satisfy the arguments.
+Any extra format specifications are evaluated with zero or the null
+string.
+.Pp
+Character escape sequences are in backslash notation as defined in
+.St -ansiC .
+The characters and their meanings are as follows:
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It Cm \ee
+Write an <escape> character.
+.It Cm \ea
+Write a <bell> character.
+.It Cm \eb
+Write a <backspace> character.
+.It Cm \ef
+Write a <form-feed> character.
+.It Cm \en
+Write a <new-line> character.
+.It Cm \er
+Write a <carriage return> character.
+.It Cm \et
+Write a <tab> character.
+.It Cm \ev
+Write a <vertical tab> character.
+.It Cm \e\'
+Write a <single quote> character.
+.It Cm \e\e
+Write a backslash character.
+.It Cm \e Ns Ar num
+Write an 8-bit character whose
+.Tn ASCII
+value is the 1-, 2-, or 3-digit
+octal number
+.Ar num .
+.El
+.Pp
+Each format specification is introduced by the percent
+.Pq Sq \&%
+character.
+The remainder of the format specifiers include,
+in the following order:
+.Bl -tag -width Ds
+.It "Zero or more of the following flags:"
+.Bl -tag -width Ds
+.It Cm #
+Specifies that the value should be printed in an
+.Dq alternate form .
+For the
+.Cm o
+format the precision of the number is increased to force the first
+character of the output string to a zero.
+For the
+.Cm x
+.Pq Cm X
+format, a non-zero result has the string
+.Li 0x
+.Pq Li 0X
+prepended to it.
+For
+.Cm a ,
+.Cm A ,
+.Cm e ,
+.Cm E ,
+.Cm f ,
+.Cm F ,
+.Cm g ,
+and
+.Cm G
+formats, the result will always contain a decimal point, even if no
+digits follow the point (normally, a decimal point only appears in the
+results of those formats if a digit follows the decimal point).
+For
+.Cm g
+and
+.Cm G
+formats, trailing zeros are not removed from the result as they
+would otherwise be.
+For all other formats, behaviour is undefined.
+.It Cm \&\-
+Specifies the
+.Em left adjustment
+of the output in the indicated field.
+.It Cm \&+
+Specifies that there should always be
+a sign placed before the number when using signed formats.
+.It Sq \&\ \&
+A space specifies that a blank should be left before a positive number
+for a signed format.
+A
+.Ql +
+overrides a space if both are used.
+.It Cm \&0
+A zero character specifies that zero-padding should be used
+rather than blank-padding.
+This flag is ignored if used with a precision
+specifier and any of the
+.Cm d , i , o , u ,
+or
+.Cm x
+.Pq Cm X
+formats.
+A
+.Ql \&-
+overrides a
+.Ql \&0
+if both are used.
+.El
+.It "Field Width:"
+An optional digit string specifying a
+.Em field width ;
+if the output string has fewer characters than the field width it will
+be blank-padded on the left (or right, if the left-adjustment indicator
+has been given) to make up the field width (note that a leading zero
+is a flag, but an embedded zero is part of a field width).
+.It Precision:
+An optional period
+.Pq Sq \&. ,
+followed by an optional digit string giving a
+.Em precision
+which specifies the number of digits to appear after the decimal point,
+for
+.Cm e
+and
+.Cm f
+formats, or the maximum number of characters to be printed
+from a string; if the digit string is missing, the precision is treated
+as zero.
+.It Format:
+A character which indicates the type of format to use (one of
+.Cm diouxXfFeEgGaAbcs ) .
+.El
+.Pp
+A field width or precision may be
+.Ql \&*
+instead of a digit string.
+In this case an
+.Ar argument
+supplies the field width or precision.
+.Pp
+The format characters and their meanings are:
+.Bl -tag -width Fl
+.It Cm diouXx
+The
+.Ar argument
+is printed as a signed decimal
+.Pq Cm d No or Cm i ,
+unsigned octal, unsigned decimal,
+or unsigned hexadecimal
+.Pq Cm x No or Cm X ,
+respectively.
+.It Cm fF
+The
+.Ar argument
+is printed in the style
+.Sm off
+.Pf [\-]ddd Cm \&. No ddd
+.Sm on
+where the number of d's
+after the decimal point is equal to the precision specification for
+the argument.
+If the precision is missing, 6 digits are given; if the precision
+is explicitly 0, no digits and no decimal point are printed.
+.Pp
+If the argument is infinity, it will be converted to [-]inf
+.Pq Cm f
+or [-]INF
+.Pq Cm F ,
+respectively.
+If the argument is not-a-number (NaN), it will be converted to
+[-]nan
+.Pq Cm f
+or [-]NAN
+.Pq Cm F ,
+respectively.
+.It Cm eE
+The
+.Ar argument
+is printed in the style
+.Sm off
+.Pf [\-]d Cm \&. No ddd Cm e No \*(Pmdd
+.Sm on
+where there
+is one digit before the decimal point and the number after is equal to
+the precision specification for the argument; when the precision is
+missing, 6 digits are produced.
+An upper-case
+.Sq E
+is used for an
+.Cm E
+format.
+.Pp
+If the argument is infinity, it will be converted to [-]inf
+.Pq Cm e
+or [-]INF
+.Pq Cm E ,
+respectively.
+If the argument is not-a-number (NaN), it will be converted to
+[-]nan
+.Pq Cm e
+or [-]NAN
+.Pq Cm E ,
+respectively.
+.It Cm gG
+The
+.Ar argument
+is printed in style
+.Cm f
+or in style
+.Cm e
+.Pq Cm E
+whichever gives full precision in minimum space.
+.Pp
+If the argument is infinity, it will be converted to [-]inf
+.Pq Cm g
+or [-]INF
+.Pq Cm G ,
+respectively.
+If the argument is not-a-number (NaN), it will be converted to
+[-]nan
+.Pq Cm g
+or [-]NAN
+.Pq Cm G ,
+respectively.
+.It Cm aA
+The
+.Ar argument
+is printed in style
+.Sm off
+.Pf [\-]0xh Cm \&. No hhh Cm p No [\*(Pm]d
+.Sm on
+where there is one digit before the hexadecimal point and the number
+after is equal to the precision specification for the argument.
+When the precision is missing, enough digits are produced to convey
+the argument's exact double-precision floating-point representation.
+.Pp
+If the argument is infinity, it will be converted to [-]inf
+.Pq Cm a
+or [-]INF
+.Pq Cm A ,
+respectively.
+If the argument is not-a-number (NaN), it will be converted to
+[-]nan
+.Pq Cm a
+or [-]NAN
+.Pq Cm A ,
+respectively.
+.It Cm b
+Characters from the string
+.Ar argument
+are printed with backslash-escape sequences expanded.
+.It Cm c
+The first character of
+.Ar argument
+is printed.
+.It Cm s
+Characters from the string
+.Ar argument
+are printed until the end is reached or until the number of characters
+indicated by the precision specification is reached; however if the
+precision is 0 or missing, all characters in the string are printed.
+.It Cm \&%
+Print a
+.Ql \&% ;
+no argument is used.
+.El
+.Pp
+In no case does a non-existent or small field width cause truncation of
+a field; padding takes place only if the specified field width exceeds
+the actual width.
+.Sh EXIT STATUS
+.Ex -std printf
+.Sh EXAMPLES
+Convert a hexadecimal value to decimal and print it out:
+.Pp
+.D1 Ic $ printf \&"%d\en\&" 0x20
+.Pp
+Print the decimal representation of the character 'a' (see
+.Xr ascii 7 ) :
+.Pp
+.D1 Ic $ printf \&"%d\en\&" \e'a
+.Sh SEE ALSO
+.Xr echo 1 ,
+.Xr printf 3
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
+.Pp
+The escape sequences \ee and \e' are extensions to that specification.
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.3 Reno .
+.Sh CAVEATS
+It is important never to pass a string with user-supplied data as a
+format without using
+.Ql %s .
+An attacker can put format specifiers in the string to mangle your stack,
+leading to a possible security hole.
+.Pp
+Always be sure to use the proper secure idiom:
+.Bd -literal -offset indent
+printf "%s" "$STRING"
+.Ed
+.Sh BUGS
+Since arguments are translated from
+.Tn ASCII
+to floating-point, and
+then back again, floating-point precision may be lost.
diff --git a/sbase/printf.c b/sbase/printf.c
@@ -0,0 +1,497 @@
+/* $OpenBSD: printf.c,v 1.22 2014/05/25 07:36:36 jmc Exp $ */
+
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <locale.h>
+#include <errno.h>
+#include <err.h>
+
+static int print_escape_str(const char *);
+static int print_escape(const char *);
+
+static int getchr(void);
+static double getdouble(void);
+static int getint(void);
+static long getlong(void);
+static unsigned long getulong(void);
+static char *getstr(void);
+static char *mklong(const char *, int);
+static void check_conversion(const char *, const char *);
+static void usage(void);
+
+static int rval;
+static char **gargv;
+
+#define isodigit(c) ((c) >= '0' && (c) <= '7')
+#define octtobin(c) ((c) - '0')
+#define hextobin(c) ((c) >= 'A' && (c) <= 'F' ? c - 'A' + 10 : (c) >= 'a' && (c) <= 'f' ? c - 'a' + 10 : c - '0')
+
+#define PF(f, func) { \
+ if (havefieldwidth) \
+ if (haveprecision) \
+ (void)printf(f, fieldwidth, precision, func); \
+ else \
+ (void)printf(f, fieldwidth, func); \
+ else if (haveprecision) \
+ (void)printf(f, precision, func); \
+ else \
+ (void)printf(f, func); \
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *fmt, *start;
+ int havefieldwidth, haveprecision;
+ int fieldwidth, precision;
+ char convch, nextch;
+ char *format;
+
+ setlocale (LC_ALL, "");
+
+ /* Need to accept/ignore "--" option. */
+ if (argc > 1 && strcmp(argv[1], "--") == 0) {
+ argc--;
+ argv++;
+ }
+
+ if (argc < 2) {
+ usage();
+ return (1);
+ }
+
+ format = *++argv;
+ gargv = ++argv;
+
+#define SKIP1 "#-+ 0"
+#define SKIP2 "0123456789"
+ do {
+ /*
+ * Basic algorithm is to scan the format string for conversion
+ * specifications -- once one is found, find out if the field
+ * width or precision is a '*'; if it is, gather up value.
+ * Note, format strings are reused as necessary to use up the
+ * provided arguments, arguments of zero/null string are
+ * provided to use up the format string.
+ */
+
+ /* find next format specification */
+ for (fmt = format; *fmt; fmt++) {
+ switch (*fmt) {
+ case '%':
+ start = fmt++;
+
+ if (*fmt == '%') {
+ putchar ('%');
+ break;
+ } else if (*fmt == 'b') {
+ char *p = getstr();
+ if (print_escape_str(p)) {
+ return (rval);
+ }
+ break;
+ }
+
+ /* skip to field width */
+ for (; strchr(SKIP1, *fmt); ++fmt)
+ ;
+ if (*fmt == '*') {
+ ++fmt;
+ havefieldwidth = 1;
+ fieldwidth = getint();
+ } else
+ havefieldwidth = 0;
+
+ /* skip to field precision */
+ for (; strchr(SKIP2, *fmt); ++fmt)
+ ;
+ haveprecision = 0;
+ if (*fmt == '.') {
+ ++fmt;
+ if (*fmt == '*') {
+ ++fmt;
+ haveprecision = 1;
+ precision = getint();
+ }
+ for (; strchr(SKIP2, *fmt); ++fmt)
+ ;
+ }
+
+ if (!*fmt) {
+ warnx ("missing format character");
+ return(1);
+ }
+
+ convch = *fmt;
+ nextch = *(fmt + 1);
+ *(fmt + 1) = '\0';
+ switch(convch) {
+ case 'c': {
+ char p = getchr();
+ PF(start, p);
+ break;
+ }
+ case 's': {
+ char *p = getstr();
+ PF(start, p);
+ break;
+ }
+ case 'd':
+ case 'i': {
+ long p;
+ char *f = mklong(start, convch);
+ if (!f) {
+ warnx("out of memory");
+ return (1);
+ }
+ p = getlong();
+ PF(f, p);
+ break;
+ }
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X': {
+ unsigned long p;
+ char *f = mklong(start, convch);
+ if (!f) {
+ warnx("out of memory");
+ return (1);
+ }
+ p = getulong();
+ PF(f, p);
+ break;
+ }
+ case 'a':
+ case 'A':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G': {
+ double p = getdouble();
+ PF(start, p);
+ break;
+ }
+ default:
+ warnx ("%s: invalid directive", start);
+ return(1);
+ }
+ *(fmt + 1) = nextch;
+ break;
+
+ case '\\':
+ fmt += print_escape(fmt);
+ break;
+
+ default:
+ putchar (*fmt);
+ break;
+ }
+ }
+ } while (gargv > argv && *gargv);
+
+ return (rval);
+}
+
+
+/*
+ * Print SysV echo(1) style escape string
+ * Halts processing string and returns 1 if a \c escape is encountered.
+ */
+static int
+print_escape_str(const char *str)
+{
+ int value;
+ int c;
+
+ while (*str) {
+ if (*str == '\\') {
+ str++;
+ /*
+ * %b string octal constants are not like those in C.
+ * They start with a \0, and are followed by 0, 1, 2,
+ * or 3 octal digits.
+ */
+ if (*str == '0') {
+ str++;
+ for (c = 3, value = 0; c-- && isodigit(*str); str++) {
+ value <<= 3;
+ value += octtobin(*str);
+ }
+ putchar (value);
+ str--;
+ } else if (*str == 'c') {
+ return 1;
+ } else {
+ str--;
+ str += print_escape(str);
+ }
+ } else {
+ putchar (*str);
+ }
+ str++;
+ }
+
+ return 0;
+}
+
+/*
+ * Print "standard" escape characters
+ */
+static int
+print_escape(const char *str)
+{
+ const char *start = str;
+ int value;
+ int c;
+
+ str++;
+
+ switch (*str) {
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ for (c = 3, value = 0; c-- && isodigit(*str); str++) {
+ value <<= 3;
+ value += octtobin(*str);
+ }
+ putchar(value);
+ return str - start - 1;
+ /* NOTREACHED */
+
+ case 'x':
+ str++;
+ for (value = 0; isxdigit((unsigned char)*str); str++) {
+ value <<= 4;
+ value += hextobin(*str);
+ }
+ if (value > UCHAR_MAX) {
+ warnx ("escape sequence out of range for character");
+ rval = 1;
+ }
+ putchar (value);
+ return str - start - 1;
+ /* NOTREACHED */
+
+ case '\\': /* backslash */
+ putchar('\\');
+ break;
+
+ case '\'': /* single quote */
+ putchar('\'');
+ break;
+
+ case '"': /* double quote */
+ putchar('"');
+ break;
+
+ case 'a': /* alert */
+ putchar('\a');
+ break;
+
+ case 'b': /* backspace */
+ putchar('\b');
+ break;
+
+ case 'e': /* escape */
+ putchar(033);
+ break;
+
+ case 'f': /* form-feed */
+ putchar('\f');
+ break;
+
+ case 'n': /* newline */
+ putchar('\n');
+ break;
+
+ case 'r': /* carriage-return */
+ putchar('\r');
+ break;
+
+ case 't': /* tab */
+ putchar('\t');
+ break;
+
+ case 'v': /* vertical-tab */
+ putchar('\v');
+ break;
+
+ case '\0':
+ warnx("null escape sequence");
+ rval = 1;
+ return 0;
+
+ default:
+ putchar(*str);
+ warnx("unknown escape sequence `\\%c'", *str);
+ rval = 1;
+ }
+
+ return 1;
+}
+
+static char *
+mklong(const char *str, int ch)
+{
+ static char *copy;
+ static int copysize;
+ int len;
+
+ len = strlen(str) + 2;
+ if (copysize < len) {
+ char *newcopy;
+ copysize = len + 256;
+
+ newcopy = realloc(copy, copysize);
+ if (newcopy == NULL) {
+ copysize = 0;
+ free(copy);
+ copy = NULL;
+ return (NULL);
+ }
+ copy = newcopy;
+ }
+ (void) memmove(copy, str, len - 3);
+ copy[len - 3] = 'l';
+ copy[len - 2] = ch;
+ copy[len - 1] = '\0';
+ return (copy);
+}
+
+static int
+getchr(void)
+{
+ if (!*gargv)
+ return((int)'\0');
+ return((int)**gargv++);
+}
+
+static char *
+getstr(void)
+{
+ if (!*gargv)
+ return("");
+ return(*gargv++);
+}
+
+static char *number = "+-.0123456789";
+static int
+getint(void)
+{
+ if (!*gargv)
+ return(0);
+
+ if (strchr(number, **gargv))
+ return(atoi(*gargv++));
+
+ return 0;
+}
+
+static long
+getlong(void)
+{
+ long val;
+ char *ep;
+
+ if (!*gargv)
+ return(0L);
+
+ if (**gargv == '\"' || **gargv == '\'')
+ return (long) *((*gargv++)+1);
+
+ errno = 0;
+ val = strtol (*gargv, &ep, 0);
+ check_conversion(*gargv++, ep);
+ return val;
+}
+
+static unsigned long
+getulong(void)
+{
+ unsigned long val;
+ char *ep;
+
+ if (!*gargv)
+ return(0UL);
+
+ if (**gargv == '\"' || **gargv == '\'')
+ return (unsigned long) *((*gargv++)+1);
+
+ errno = 0;
+ val = strtoul (*gargv, &ep, 0);
+ check_conversion(*gargv++, ep);
+ return val;
+}
+
+static double
+getdouble(void)
+{
+ double val;
+ char *ep;
+
+ if (!*gargv)
+ return(0.0);
+
+ if (**gargv == '\"' || **gargv == '\'')
+ return (double) *((*gargv++)+1);
+
+ errno = 0;
+ val = strtod (*gargv, &ep);
+ check_conversion(*gargv++, ep);
+ return val;
+}
+
+static void
+check_conversion(const char *s, const char *ep)
+{
+ if (*ep) {
+ if (ep == s)
+ warnx ("%s: expected numeric value", s);
+ else
+ warnx ("%s: not completely converted", s);
+ rval = 1;
+ } else if (errno == ERANGE) {
+ (void)fprintf(stderr, "%s: %s\n", s, strerror(ERANGE));
+ rval = 1;
+ }
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: printf format [argument ...]\n");
+}
diff --git a/sbase/pwd.1 b/sbase/pwd.1
@@ -0,0 +1,32 @@
+.Dd November 23, 2014
+.Dt PWD 1 sbase\-VERSION
+.Os
+.Sh NAME
+.Nm pwd
+.Nd print working directory
+.Sh SYNOPSIS
+.Nm pwd
+.Op Fl LP
+.Sh DESCRIPTION
+.Nm
+prints the path of the current working directory.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl L
+Logical path, uses $PWD (default).
+.It Fl P
+Physical path, avoids all symlinks.
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width PWD
+.It Ev PWD
+The logical path to the current working directory.
+.El
+.Sh SEE ALSO
+.Xr getcwd 3
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
diff --git a/sbase/pwd.c b/sbase/pwd.c
@@ -0,0 +1,52 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "util.h"
+
+static const char *getpwd(const char *cwd);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-LP]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *cwd;
+ char mode = 'L';
+
+ ARGBEGIN {
+ case 'L':
+ case 'P':
+ mode = ARGC();
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ cwd = agetcwd();
+ puts((mode == 'L') ? getpwd(cwd) : cwd);
+
+ return 0;
+}
+
+static const char *
+getpwd(const char *cwd)
+{
+ const char *pwd;
+ struct stat cst, pst;
+
+ if (!(pwd = getenv("PWD")) || pwd[0] != '/' || stat(pwd, &pst) < 0)
+ return cwd;
+ if (stat(cwd, &cst) < 0)
+ eprintf("stat %s:", cwd);
+ if (pst.st_dev == cst.st_dev && pst.st_ino == cst.st_ino)
+ return pwd;
+ else
+ return cwd;
+}
diff --git a/sbase/queue.h b/sbase/queue.h
@@ -0,0 +1,648 @@
+/* $OpenBSD: queue.h,v 1.38 2013/07/03 15:05:21 fgsch Exp $ */
+/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */
+
+/*
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)queue.h 8.5 (Berkeley) 8/20/94
+ */
+
+#ifndef _SYS_QUEUE_H_
+#define _SYS_QUEUE_H_
+
+/*
+ * This file defines five types of data structures: singly-linked lists,
+ * lists, simple queues, tail queues, and circular queues.
+ *
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction. Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A simple queue is headed by a pair of pointers, one the head of the
+ * list and the other to the tail of the list. The elements are singly
+ * linked to save space, so elements can only be removed from the
+ * head of the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the
+ * list. A simple queue may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * A circle queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the list.
+ * A circle queue may be traversed in either direction, but has a more
+ * complex end of list detection.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC))
+#define _Q_INVALIDATE(a) (a) = ((void *)-1)
+#else
+#define _Q_INVALIDATE(a)
+#endif
+
+/*
+ * Singly-linked List definitions.
+ */
+#define SLIST_HEAD(name, type) \
+struct name { \
+ struct type *slh_first; /* first element */ \
+}
+
+#define SLIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define SLIST_ENTRY(type) \
+struct { \
+ struct type *sle_next; /* next element */ \
+}
+
+/*
+ * Singly-linked List access methods.
+ */
+#define SLIST_FIRST(head) ((head)->slh_first)
+#define SLIST_END(head) NULL
+#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head))
+#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
+
+#define SLIST_FOREACH(var, head, field) \
+ for((var) = SLIST_FIRST(head); \
+ (var) != SLIST_END(head); \
+ (var) = SLIST_NEXT(var, field))
+
+#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SLIST_FIRST(head); \
+ (var) && ((tvar) = SLIST_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Singly-linked List functions.
+ */
+#define SLIST_INIT(head) { \
+ SLIST_FIRST(head) = SLIST_END(head); \
+}
+
+#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
+ (elm)->field.sle_next = (slistelm)->field.sle_next; \
+ (slistelm)->field.sle_next = (elm); \
+} while (0)
+
+#define SLIST_INSERT_HEAD(head, elm, field) do { \
+ (elm)->field.sle_next = (head)->slh_first; \
+ (head)->slh_first = (elm); \
+} while (0)
+
+#define SLIST_REMOVE_AFTER(elm, field) do { \
+ (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \
+} while (0)
+
+#define SLIST_REMOVE_HEAD(head, field) do { \
+ (head)->slh_first = (head)->slh_first->field.sle_next; \
+} while (0)
+
+#define SLIST_REMOVE(head, elm, type, field) do { \
+ if ((head)->slh_first == (elm)) { \
+ SLIST_REMOVE_HEAD((head), field); \
+ } else { \
+ struct type *curelm = (head)->slh_first; \
+ \
+ while (curelm->field.sle_next != (elm)) \
+ curelm = curelm->field.sle_next; \
+ curelm->field.sle_next = \
+ curelm->field.sle_next->field.sle_next; \
+ _Q_INVALIDATE((elm)->field.sle_next); \
+ } \
+} while (0)
+
+/*
+ * List definitions.
+ */
+#define LIST_HEAD(name, type) \
+struct name { \
+ struct type *lh_first; /* first element */ \
+}
+
+#define LIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define LIST_ENTRY(type) \
+struct { \
+ struct type *le_next; /* next element */ \
+ struct type **le_prev; /* address of previous next element */ \
+}
+
+/*
+ * List access methods
+ */
+#define LIST_FIRST(head) ((head)->lh_first)
+#define LIST_END(head) NULL
+#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head))
+#define LIST_NEXT(elm, field) ((elm)->field.le_next)
+
+#define LIST_FOREACH(var, head, field) \
+ for((var) = LIST_FIRST(head); \
+ (var)!= LIST_END(head); \
+ (var) = LIST_NEXT(var, field))
+
+#define LIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = LIST_FIRST(head); \
+ (var) && ((tvar) = LIST_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * List functions.
+ */
+#define LIST_INIT(head) do { \
+ LIST_FIRST(head) = LIST_END(head); \
+} while (0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do { \
+ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \
+ (listelm)->field.le_next->field.le_prev = \
+ &(elm)->field.le_next; \
+ (listelm)->field.le_next = (elm); \
+ (elm)->field.le_prev = &(listelm)->field.le_next; \
+} while (0)
+
+#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
+ (elm)->field.le_prev = (listelm)->field.le_prev; \
+ (elm)->field.le_next = (listelm); \
+ *(listelm)->field.le_prev = (elm); \
+ (listelm)->field.le_prev = &(elm)->field.le_next; \
+} while (0)
+
+#define LIST_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.le_next = (head)->lh_first) != NULL) \
+ (head)->lh_first->field.le_prev = &(elm)->field.le_next;\
+ (head)->lh_first = (elm); \
+ (elm)->field.le_prev = &(head)->lh_first; \
+} while (0)
+
+#define LIST_REMOVE(elm, field) do { \
+ if ((elm)->field.le_next != NULL) \
+ (elm)->field.le_next->field.le_prev = \
+ (elm)->field.le_prev; \
+ *(elm)->field.le_prev = (elm)->field.le_next; \
+ _Q_INVALIDATE((elm)->field.le_prev); \
+ _Q_INVALIDATE((elm)->field.le_next); \
+} while (0)
+
+#define LIST_REPLACE(elm, elm2, field) do { \
+ if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \
+ (elm2)->field.le_next->field.le_prev = \
+ &(elm2)->field.le_next; \
+ (elm2)->field.le_prev = (elm)->field.le_prev; \
+ *(elm2)->field.le_prev = (elm2); \
+ _Q_INVALIDATE((elm)->field.le_prev); \
+ _Q_INVALIDATE((elm)->field.le_next); \
+} while (0)
+
+/*
+ * Simple queue definitions.
+ */
+#define SIMPLEQ_HEAD(name, type) \
+struct name { \
+ struct type *sqh_first; /* first element */ \
+ struct type **sqh_last; /* addr of last next element */ \
+}
+
+#define SIMPLEQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).sqh_first }
+
+#define SIMPLEQ_ENTRY(type) \
+struct { \
+ struct type *sqe_next; /* next element */ \
+}
+
+/*
+ * Simple queue access methods.
+ */
+#define SIMPLEQ_FIRST(head) ((head)->sqh_first)
+#define SIMPLEQ_END(head) NULL
+#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
+#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next)
+
+#define SIMPLEQ_FOREACH(var, head, field) \
+ for((var) = SIMPLEQ_FIRST(head); \
+ (var) != SIMPLEQ_END(head); \
+ (var) = SIMPLEQ_NEXT(var, field))
+
+#define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SIMPLEQ_FIRST(head); \
+ (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Simple queue functions.
+ */
+#define SIMPLEQ_INIT(head) do { \
+ (head)->sqh_first = NULL; \
+ (head)->sqh_last = &(head)->sqh_first; \
+} while (0)
+
+#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+ (head)->sqh_first = (elm); \
+} while (0)
+
+#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.sqe_next = NULL; \
+ *(head)->sqh_last = (elm); \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+} while (0)
+
+#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+ (listelm)->field.sqe_next = (elm); \
+} while (0)
+
+#define SIMPLEQ_REMOVE_HEAD(head, field) do { \
+ if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
+ (head)->sqh_last = &(head)->sqh_first; \
+} while (0)
+
+#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \
+ if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \
+ == NULL) \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+} while (0)
+
+/*
+ * XOR Simple queue definitions.
+ */
+#define XSIMPLEQ_HEAD(name, type) \
+struct name { \
+ struct type *sqx_first; /* first element */ \
+ struct type **sqx_last; /* addr of last next element */ \
+ unsigned long sqx_cookie; \
+}
+
+#define XSIMPLEQ_ENTRY(type) \
+struct { \
+ struct type *sqx_next; /* next element */ \
+}
+
+/*
+ * XOR Simple queue access methods.
+ */
+#define XSIMPLEQ_XOR(head, ptr) ((__typeof(ptr))((head)->sqx_cookie ^ \
+ (unsigned long)(ptr)))
+#define XSIMPLEQ_FIRST(head) XSIMPLEQ_XOR(head, ((head)->sqx_first))
+#define XSIMPLEQ_END(head) NULL
+#define XSIMPLEQ_EMPTY(head) (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head))
+#define XSIMPLEQ_NEXT(head, elm, field) XSIMPLEQ_XOR(head, ((elm)->field.sqx_next))
+
+
+#define XSIMPLEQ_FOREACH(var, head, field) \
+ for ((var) = XSIMPLEQ_FIRST(head); \
+ (var) != XSIMPLEQ_END(head); \
+ (var) = XSIMPLEQ_NEXT(head, var, field))
+
+#define XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = XSIMPLEQ_FIRST(head); \
+ (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * XOR Simple queue functions.
+ */
+#define XSIMPLEQ_INIT(head) do { \
+ arc4random_buf(&(head)->sqx_cookie, sizeof((head)->sqx_cookie)); \
+ (head)->sqx_first = XSIMPLEQ_XOR(head, NULL); \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \
+} while (0)
+
+#define XSIMPLEQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.sqx_next = (head)->sqx_first) == \
+ XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+ (head)->sqx_first = XSIMPLEQ_XOR(head, (elm)); \
+} while (0)
+
+#define XSIMPLEQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL); \
+ *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = XSIMPLEQ_XOR(head, (elm)); \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+} while (0)
+
+#define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.sqx_next = (listelm)->field.sqx_next) == \
+ XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+ (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm)); \
+} while (0)
+
+#define XSIMPLEQ_REMOVE_HEAD(head, field) do { \
+ if (((head)->sqx_first = XSIMPLEQ_XOR(head, \
+ (head)->sqx_first)->field.sqx_next) == XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \
+} while (0)
+
+#define XSIMPLEQ_REMOVE_AFTER(head, elm, field) do { \
+ if (((elm)->field.sqx_next = XSIMPLEQ_XOR(head, \
+ (elm)->field.sqx_next)->field.sqx_next) \
+ == XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = \
+ XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+} while (0)
+
+
+/*
+ * Tail queue definitions.
+ */
+#define TAILQ_HEAD(name, type) \
+struct name { \
+ struct type *tqh_first; /* first element */ \
+ struct type **tqh_last; /* addr of last next element */ \
+}
+
+#define TAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).tqh_first }
+
+#define TAILQ_ENTRY(type) \
+struct { \
+ struct type *tqe_next; /* next element */ \
+ struct type **tqe_prev; /* address of previous next element */ \
+}
+
+/*
+ * tail queue access methods
+ */
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+#define TAILQ_END(head) NULL
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+#define TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+/* XXX */
+#define TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+#define TAILQ_EMPTY(head) \
+ (TAILQ_FIRST(head) == TAILQ_END(head))
+
+#define TAILQ_FOREACH(var, head, field) \
+ for((var) = TAILQ_FIRST(head); \
+ (var) != TAILQ_END(head); \
+ (var) = TAILQ_NEXT(var, field))
+
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = TAILQ_FIRST(head); \
+ (var) != TAILQ_END(head) && \
+ ((tvar) = TAILQ_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
+ for((var) = TAILQ_LAST(head, headname); \
+ (var) != TAILQ_END(head); \
+ (var) = TAILQ_PREV(var, headname, field))
+
+#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
+ for ((var) = TAILQ_LAST(head, headname); \
+ (var) != TAILQ_END(head) && \
+ ((tvar) = TAILQ_PREV(var, headname, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Tail queue functions.
+ */
+#define TAILQ_INIT(head) do { \
+ (head)->tqh_first = NULL; \
+ (head)->tqh_last = &(head)->tqh_first; \
+} while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \
+ (head)->tqh_first->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (head)->tqh_first = (elm); \
+ (elm)->field.tqe_prev = &(head)->tqh_first; \
+} while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.tqe_next = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
+ (elm)->field.tqe_next->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (listelm)->field.tqe_next = (elm); \
+ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
+ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
+ (elm)->field.tqe_next = (listelm); \
+ *(listelm)->field.tqe_prev = (elm); \
+ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_REMOVE(head, elm, field) do { \
+ if (((elm)->field.tqe_next) != NULL) \
+ (elm)->field.tqe_next->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \
+ _Q_INVALIDATE((elm)->field.tqe_prev); \
+ _Q_INVALIDATE((elm)->field.tqe_next); \
+} while (0)
+
+#define TAILQ_REPLACE(head, elm, elm2, field) do { \
+ if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \
+ (elm2)->field.tqe_next->field.tqe_prev = \
+ &(elm2)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm2)->field.tqe_next; \
+ (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \
+ *(elm2)->field.tqe_prev = (elm2); \
+ _Q_INVALIDATE((elm)->field.tqe_prev); \
+ _Q_INVALIDATE((elm)->field.tqe_next); \
+} while (0)
+
+/*
+ * Circular queue definitions.
+ */
+#define CIRCLEQ_HEAD(name, type) \
+struct name { \
+ struct type *cqh_first; /* first element */ \
+ struct type *cqh_last; /* last element */ \
+}
+
+#define CIRCLEQ_HEAD_INITIALIZER(head) \
+ { CIRCLEQ_END(&head), CIRCLEQ_END(&head) }
+
+#define CIRCLEQ_ENTRY(type) \
+struct { \
+ struct type *cqe_next; /* next element */ \
+ struct type *cqe_prev; /* previous element */ \
+}
+
+/*
+ * Circular queue access methods
+ */
+#define CIRCLEQ_FIRST(head) ((head)->cqh_first)
+#define CIRCLEQ_LAST(head) ((head)->cqh_last)
+#define CIRCLEQ_END(head) ((void *)(head))
+#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next)
+#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev)
+#define CIRCLEQ_EMPTY(head) \
+ (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head))
+
+#define CIRCLEQ_FOREACH(var, head, field) \
+ for((var) = CIRCLEQ_FIRST(head); \
+ (var) != CIRCLEQ_END(head); \
+ (var) = CIRCLEQ_NEXT(var, field))
+
+#define CIRCLEQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = CIRCLEQ_FIRST(head); \
+ (var) != CIRCLEQ_END(head) && \
+ ((tvar) = CIRCLEQ_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \
+ for((var) = CIRCLEQ_LAST(head); \
+ (var) != CIRCLEQ_END(head); \
+ (var) = CIRCLEQ_PREV(var, field))
+
+#define CIRCLEQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
+ for ((var) = CIRCLEQ_LAST(head, headname); \
+ (var) != CIRCLEQ_END(head) && \
+ ((tvar) = CIRCLEQ_PREV(var, headname, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Circular queue functions.
+ */
+#define CIRCLEQ_INIT(head) do { \
+ (head)->cqh_first = CIRCLEQ_END(head); \
+ (head)->cqh_last = CIRCLEQ_END(head); \
+} while (0)
+
+#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ (elm)->field.cqe_next = (listelm)->field.cqe_next; \
+ (elm)->field.cqe_prev = (listelm); \
+ if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \
+ (head)->cqh_last = (elm); \
+ else \
+ (listelm)->field.cqe_next->field.cqe_prev = (elm); \
+ (listelm)->field.cqe_next = (elm); \
+} while (0)
+
+#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \
+ (elm)->field.cqe_next = (listelm); \
+ (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \
+ if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \
+ (head)->cqh_first = (elm); \
+ else \
+ (listelm)->field.cqe_prev->field.cqe_next = (elm); \
+ (listelm)->field.cqe_prev = (elm); \
+} while (0)
+
+#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \
+ (elm)->field.cqe_next = (head)->cqh_first; \
+ (elm)->field.cqe_prev = CIRCLEQ_END(head); \
+ if ((head)->cqh_last == CIRCLEQ_END(head)) \
+ (head)->cqh_last = (elm); \
+ else \
+ (head)->cqh_first->field.cqe_prev = (elm); \
+ (head)->cqh_first = (elm); \
+} while (0)
+
+#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.cqe_next = CIRCLEQ_END(head); \
+ (elm)->field.cqe_prev = (head)->cqh_last; \
+ if ((head)->cqh_first == CIRCLEQ_END(head)) \
+ (head)->cqh_first = (elm); \
+ else \
+ (head)->cqh_last->field.cqe_next = (elm); \
+ (head)->cqh_last = (elm); \
+} while (0)
+
+#define CIRCLEQ_REMOVE(head, elm, field) do { \
+ if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \
+ (head)->cqh_last = (elm)->field.cqe_prev; \
+ else \
+ (elm)->field.cqe_next->field.cqe_prev = \
+ (elm)->field.cqe_prev; \
+ if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \
+ (head)->cqh_first = (elm)->field.cqe_next; \
+ else \
+ (elm)->field.cqe_prev->field.cqe_next = \
+ (elm)->field.cqe_next; \
+ _Q_INVALIDATE((elm)->field.cqe_prev); \
+ _Q_INVALIDATE((elm)->field.cqe_next); \
+} while (0)
+
+#define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \
+ if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \
+ CIRCLEQ_END(head)) \
+ (head)->cqh_last = (elm2); \
+ else \
+ (elm2)->field.cqe_next->field.cqe_prev = (elm2); \
+ if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \
+ CIRCLEQ_END(head)) \
+ (head)->cqh_first = (elm2); \
+ else \
+ (elm2)->field.cqe_prev->field.cqe_next = (elm2); \
+ _Q_INVALIDATE((elm)->field.cqe_prev); \
+ _Q_INVALIDATE((elm)->field.cqe_next); \
+} while (0)
+
+#endif /* !_SYS_QUEUE_H_ */
diff --git a/sbase/readlink.1 b/sbase/readlink.1
@@ -0,0 +1,25 @@
+.TH READLINK 1 sbase\-VERSION
+.SH NAME
+readlink \- print value of a symbolic link or canonical file name
+.SH SYNOPSIS
+.B readlink
+.RB [ \-fn ]
+.IR file
+.SH DESCRIPTION
+The readlink utility when invoked with the pathname of a symbolic link as
+its argument dereferences the symbolic link and prints the name of target
+on standard output. If the -f option is not specified and readlink is
+invoked with an argument other than the pathname of a symbolic link, it
+exits with a nonzero exit code without printing anything.
+.SH OPTIONS
+.TP
+.B \-f
+Canonicalize by following every symlink in every component of the
+given path recursively. The argument does not need to be a symbolic
+link.
+.TP
+.B \-n
+Do not output the trailing newline.
+.SH SEE ALSO
+.IR readlink (2),
+.IR realpath (3)
diff --git a/sbase/readlink.c b/sbase/readlink.c
@@ -0,0 +1,59 @@
+/* See LICENSE file for copyright and license details. */
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-efmn] file\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char buf[PATH_MAX];
+ int nflag = 0;
+ int fflag = 0;
+ ssize_t n;
+
+ ARGBEGIN {
+ case 'e':
+ case 'm':
+ eprintf("not implemented\n");
+ case 'f':
+ fflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc != 1)
+ usage();
+
+ if (strlen(argv[0]) > PATH_MAX - 1)
+ eprintf("path too long\n");
+
+ if (fflag) {
+ if (!realpath(argv[0], buf))
+ exit(1);
+ } else {
+ if ((n = readlink(argv[0], buf, sizeof(buf) - 1)) < 0)
+ exit(1);
+ buf[n] = '\0';
+ }
+
+ printf("%s", buf);
+ if (!nflag)
+ putchar('\n');
+
+ return 0;
+}
diff --git a/sbase/renice.1 b/sbase/renice.1
@@ -0,0 +1,97 @@
+.TH RENICE 1 renice-VERSION "Jun 2013"
+.SH NAME
+renice \- set nice values of running processes
+.SH "SYNOPSIS"
+.PP
+.B renice
+.B \-n
+.I increment
+[
+.B \-g
+|
+.B \-p
+|
+.B \-u
+]
+.I ID...
+.SH DESCRIPTION
+The
+.B renice
+utility requests that the nice values of one or more
+running processes be changed. By default, the applicable processes
+are specified by their process IDs. When a process group is specified
+(see
+.B -g
+), the request applies to all processes in the process group. If the
+requested increment would raise or lower the nice value of the
+executed utility beyond its limits, then the limit whose value was
+exceeded is used. When a user is reniced, the request applies to all
+processes whose saved set-user-ID matches the user ID corresponding to
+the user. Regardless of which options are supplied or any other factor,
+renice does not alter the nice values of any process unless the user
+requesting such a change has appropriate privileges to do so for the
+specified process. If the user lacks appropriate privileges to perform
+the requested action, the utility returns an error status.
+The saved set-user-ID of the user's process is checked instead of its
+effective user ID when renice attempts to determine the user ID of the
+process in order to determine whether the user has appropriate privileges.
+.SH OPTIONS
+.TP
+.B \-g
+interpret all operands as unsigned decimal integer process group IDs.
+.TP
+.B \-n
+.I increment
+specify how the nice value of the specified process or processes
+is to be adjusted. The increment option-argument is a positive or
+negative decimal integer used to modify the nice value of the
+specified process or processes. positive increment values cause a
+lower nice value. Negative increment values may require appropriate
+privileges and cause a higher nice value.
+.TP
+.B \-p
+interpret all operands as unsigned decimal integer process IDs.
+The
+.B \-p
+option is the default if no options are specified.
+.TP
+.B \-u
+interpret all operands as users. If a user exists with a user name
+equal to the operand, then the user ID of that user is used in further
+processing. Otherwise, if the operand represents an unsigned decimal
+integer, used as the numeric user ID of the user.
+.SH EXIT VALUES
+On successful completion 0 is returned, a value which is >0 is
+returned on error.
+.SH FILES
+.TP
+.I /etc/passwd
+used to map user names to user ID's.
+.SH CONFORMING TO
+The
+.B renice
+utility is IEEE Std 1003.1-2001 (POSIX.1) compatible.
+.SH EXAMPLES
+.TP
+.I "renice -n 5 -p 987 32"
+.PP
+Adjust the nice value so that process IDs 987 and 32 would have a
+lower nice value.
+.TP
+.I "renice -n -4 -g 324 76"
+.PP
+Adjust the nice value so that group IDs 324 and 76 would have a
+higher nice value, if the user has the appropriate privileges to do so.
+.TP
+.I "renice -n 4 -u 8 sas"
+.PP
+Adjust the nice value so that numeric user ID 8 and user sas would
+have a lower nice value.
+Useful nice value increments on historical systems include
+19 or 20 (the affected processes run only when nothing else in the
+system attempts to run) and any negative number
+(to make processes run faster).
+.SH AUTHOR
+Written by Lorenzo Cogotti.
+.SH SEE ALSO
+.BR nice(1)
diff --git a/sbase/renice.c b/sbase/renice.c
@@ -0,0 +1,114 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+
+#include "util.h"
+
+static int strtop(const char *);
+static int renice(int, int, long);
+
+static void
+usage(void)
+{
+ eprintf("renice -n inc [-g | -p | -u] ID ...\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *adj = NULL;
+ long val;
+ int i, which = PRIO_PROCESS, status = 0;
+ struct passwd *pw;
+ int who;
+
+ ARGBEGIN {
+ case 'n':
+ adj = EARGF(usage());
+ break;
+ case 'g':
+ which = PRIO_PGRP;
+ break;
+ case 'p':
+ which = PRIO_PROCESS;
+ break;
+ case 'u':
+ which = PRIO_USER;
+ break;
+ default:
+ usage();
+ break;
+ } ARGEND;
+
+ if (argc == 0 || !adj)
+ usage();
+
+ val = estrtol(adj, 10);
+ for (i = 0; i < argc; i++) {
+ who = -1;
+ if (which == PRIO_USER) {
+ errno = 0;
+ pw = getpwnam(argv[i]);
+ if (!pw) {
+ if (errno != 0)
+ weprintf("getpwnam %s:", argv[i]);
+ else
+ weprintf("getpwnam %s: no user found\n", argv[i]);
+ status = 1;
+ continue;
+ }
+ who = pw->pw_uid;
+ }
+ if (who < 0)
+ who = strtop(argv[i]);
+
+ if (who < 0 || !renice(which, who, val))
+ status = 1;
+ }
+
+ return status;
+}
+
+static int
+strtop(const char *s)
+{
+ char *end;
+ long n;
+
+ errno = 0;
+ n = strtol(s, &end, 10);
+ if (*end != '\0') {
+ weprintf("%s: not an integer\n", s);
+ return -1;
+ }
+ if (errno != 0 || n <= 0 || n > INT_MAX) {
+ weprintf("%s: invalid value\n", s);
+ return -1;
+ }
+
+ return (int)n;
+}
+
+static int
+renice(int which, int who, long adj)
+{
+ errno = 0;
+ adj += getpriority(which, who);
+ if (errno != 0) {
+ weprintf("getpriority %d:", who);
+ return 0;
+ }
+
+ adj = MAX(PRIO_MIN, MIN(adj, PRIO_MAX));
+ if (setpriority(which, who, (int)adj) < 0) {
+ weprintf("setpriority %d:", who);
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/sbase/rm.1 b/sbase/rm.1
@@ -0,0 +1,22 @@
+.TH RM 1 sbase\-VERSION
+.SH NAME
+rm \- remove files and directories
+.SH SYNOPSIS
+.B rm
+.RB [ \-fRr ]
+.RI [ file ...]
+.SH DESCRIPTION
+.B rm
+removes the given files and directories.
+.SH OPTIONS
+.TP
+.B \-f
+ignore files that cannot be removed.
+.TP
+.B \-R
+equivalent to -r.
+.TP
+.B \-r
+remove directories recursively.
+.SH SEE ALSO
+.IR remove (3)
diff --git a/sbase/rm.c b/sbase/rm.c
@@ -0,0 +1,42 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "fs.h"
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-fRr] FILE...\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ ARGBEGIN {
+ case 'f':
+ rm_fflag = 1;
+ break;
+ case 'R':
+ case 'r':
+ rm_rflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1) {
+ if (!rm_fflag)
+ usage();
+ else
+ return 0;
+ }
+
+ for (; argc > 0; argc--, argv++)
+ rm(argv[0]);
+
+ return 0;
+}
diff --git a/sbase/rmdir.1 b/sbase/rmdir.1
@@ -0,0 +1,25 @@
+.TH RMDIR 1 sbase\-VERSION
+.SH NAME
+rmdir \- remove a directory
+.SH SYNOPSIS
+.B rmdir
+.I directory...
+.SH DESCRIPTION
+.B rmdir
+attempts to remove all non-full directories specified
+by
+.IR directory.
+.SH BUGS
+Subdirectories are removed in the order specified, so
+.nf
+ rmdir foo/bar foo
+.fi
+will be successful, but
+.nf
+ rmdir foo foo/bar
+.fi
+will only succeed in removing
+.BR foo/bar.
+
+.SH SEE ALSO
+.IR rm (1) rmdir (2) unlink (1) unlink (2)
diff --git a/sbase/rmdir.c b/sbase/rmdir.c
@@ -0,0 +1,29 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: rmdir dir...\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ for (; argc > 0; argc--, argv++)
+ if (rmdir(argv[0]) < 0)
+ weprintf("rmdir %s:", argv[0]);
+ return 0;
+}
diff --git a/sbase/runetypebody.h b/sbase/runetypebody.h
@@ -0,0 +1,1886 @@
+/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/* Automatically generated from UnicodeData-6.1.0.txt by mkrunetype.awk */
+
+static Rune alpha2[][2] = {
+ { 0x0041, 0x005A },
+ { 0x0061, 0x007A },
+ { 0x00C0, 0x00D6 },
+ { 0x00D8, 0x00F6 },
+ { 0x00F8, 0x02C1 },
+ { 0x02C6, 0x02D1 },
+ { 0x02E0, 0x02E4 },
+ { 0x0370, 0x0374 },
+ { 0x0376, 0x0377 },
+ { 0x037A, 0x037D },
+ { 0x0388, 0x038A },
+ { 0x038E, 0x03A1 },
+ { 0x03A3, 0x03F5 },
+ { 0x03F7, 0x0481 },
+ { 0x048A, 0x0527 },
+ { 0x0531, 0x0556 },
+ { 0x0561, 0x0587 },
+ { 0x05D0, 0x05EA },
+ { 0x05F0, 0x05F2 },
+ { 0x0620, 0x064A },
+ { 0x066E, 0x066F },
+ { 0x0671, 0x06D3 },
+ { 0x06E5, 0x06E6 },
+ { 0x06EE, 0x06EF },
+ { 0x06FA, 0x06FC },
+ { 0x0712, 0x072F },
+ { 0x074D, 0x07A5 },
+ { 0x07CA, 0x07EA },
+ { 0x07F4, 0x07F5 },
+ { 0x0800, 0x0815 },
+ { 0x0840, 0x0858 },
+ { 0x08A2, 0x08AC },
+ { 0x0904, 0x0939 },
+ { 0x0958, 0x0961 },
+ { 0x0971, 0x0977 },
+ { 0x0979, 0x097F },
+ { 0x0985, 0x098C },
+ { 0x098F, 0x0990 },
+ { 0x0993, 0x09A8 },
+ { 0x09AA, 0x09B0 },
+ { 0x09B6, 0x09B9 },
+ { 0x09DC, 0x09DD },
+ { 0x09DF, 0x09E1 },
+ { 0x09F0, 0x09F1 },
+ { 0x0A05, 0x0A0A },
+ { 0x0A0F, 0x0A10 },
+ { 0x0A13, 0x0A28 },
+ { 0x0A2A, 0x0A30 },
+ { 0x0A32, 0x0A33 },
+ { 0x0A35, 0x0A36 },
+ { 0x0A38, 0x0A39 },
+ { 0x0A59, 0x0A5C },
+ { 0x0A72, 0x0A74 },
+ { 0x0A85, 0x0A8D },
+ { 0x0A8F, 0x0A91 },
+ { 0x0A93, 0x0AA8 },
+ { 0x0AAA, 0x0AB0 },
+ { 0x0AB2, 0x0AB3 },
+ { 0x0AB5, 0x0AB9 },
+ { 0x0AE0, 0x0AE1 },
+ { 0x0B05, 0x0B0C },
+ { 0x0B0F, 0x0B10 },
+ { 0x0B13, 0x0B28 },
+ { 0x0B2A, 0x0B30 },
+ { 0x0B32, 0x0B33 },
+ { 0x0B35, 0x0B39 },
+ { 0x0B5C, 0x0B5D },
+ { 0x0B5F, 0x0B61 },
+ { 0x0B85, 0x0B8A },
+ { 0x0B8E, 0x0B90 },
+ { 0x0B92, 0x0B95 },
+ { 0x0B99, 0x0B9A },
+ { 0x0B9E, 0x0B9F },
+ { 0x0BA3, 0x0BA4 },
+ { 0x0BA8, 0x0BAA },
+ { 0x0BAE, 0x0BB9 },
+ { 0x0C05, 0x0C0C },
+ { 0x0C0E, 0x0C10 },
+ { 0x0C12, 0x0C28 },
+ { 0x0C2A, 0x0C33 },
+ { 0x0C35, 0x0C39 },
+ { 0x0C58, 0x0C59 },
+ { 0x0C60, 0x0C61 },
+ { 0x0C85, 0x0C8C },
+ { 0x0C8E, 0x0C90 },
+ { 0x0C92, 0x0CA8 },
+ { 0x0CAA, 0x0CB3 },
+ { 0x0CB5, 0x0CB9 },
+ { 0x0CE0, 0x0CE1 },
+ { 0x0CF1, 0x0CF2 },
+ { 0x0D05, 0x0D0C },
+ { 0x0D0E, 0x0D10 },
+ { 0x0D12, 0x0D3A },
+ { 0x0D60, 0x0D61 },
+ { 0x0D7A, 0x0D7F },
+ { 0x0D85, 0x0D96 },
+ { 0x0D9A, 0x0DB1 },
+ { 0x0DB3, 0x0DBB },
+ { 0x0DC0, 0x0DC6 },
+ { 0x0E01, 0x0E30 },
+ { 0x0E32, 0x0E33 },
+ { 0x0E40, 0x0E46 },
+ { 0x0E81, 0x0E82 },
+ { 0x0E87, 0x0E88 },
+ { 0x0E94, 0x0E97 },
+ { 0x0E99, 0x0E9F },
+ { 0x0EA1, 0x0EA3 },
+ { 0x0EAA, 0x0EAB },
+ { 0x0EAD, 0x0EB0 },
+ { 0x0EB2, 0x0EB3 },
+ { 0x0EC0, 0x0EC4 },
+ { 0x0EDC, 0x0EDF },
+ { 0x0F40, 0x0F47 },
+ { 0x0F49, 0x0F6C },
+ { 0x0F88, 0x0F8C },
+ { 0x1000, 0x102A },
+ { 0x1050, 0x1055 },
+ { 0x105A, 0x105D },
+ { 0x1065, 0x1066 },
+ { 0x106E, 0x1070 },
+ { 0x1075, 0x1081 },
+ { 0x10A0, 0x10C5 },
+ { 0x10D0, 0x10FA },
+ { 0x10FC, 0x1248 },
+ { 0x124A, 0x124D },
+ { 0x1250, 0x1256 },
+ { 0x125A, 0x125D },
+ { 0x1260, 0x1288 },
+ { 0x128A, 0x128D },
+ { 0x1290, 0x12B0 },
+ { 0x12B2, 0x12B5 },
+ { 0x12B8, 0x12BE },
+ { 0x12C2, 0x12C5 },
+ { 0x12C8, 0x12D6 },
+ { 0x12D8, 0x1310 },
+ { 0x1312, 0x1315 },
+ { 0x1318, 0x135A },
+ { 0x1380, 0x138F },
+ { 0x13A0, 0x13F4 },
+ { 0x1401, 0x166C },
+ { 0x166F, 0x167F },
+ { 0x1681, 0x169A },
+ { 0x16A0, 0x16EA },
+ { 0x1700, 0x170C },
+ { 0x170E, 0x1711 },
+ { 0x1720, 0x1731 },
+ { 0x1740, 0x1751 },
+ { 0x1760, 0x176C },
+ { 0x176E, 0x1770 },
+ { 0x1780, 0x17B3 },
+ { 0x1820, 0x1877 },
+ { 0x1880, 0x18A8 },
+ { 0x18B0, 0x18F5 },
+ { 0x1900, 0x191C },
+ { 0x1950, 0x196D },
+ { 0x1970, 0x1974 },
+ { 0x1980, 0x19AB },
+ { 0x19C1, 0x19C7 },
+ { 0x1A00, 0x1A16 },
+ { 0x1A20, 0x1A54 },
+ { 0x1B05, 0x1B33 },
+ { 0x1B45, 0x1B4B },
+ { 0x1B83, 0x1BA0 },
+ { 0x1BAE, 0x1BAF },
+ { 0x1BBA, 0x1BE5 },
+ { 0x1C00, 0x1C23 },
+ { 0x1C4D, 0x1C4F },
+ { 0x1C5A, 0x1C7D },
+ { 0x1CE9, 0x1CEC },
+ { 0x1CEE, 0x1CF1 },
+ { 0x1CF5, 0x1CF6 },
+ { 0x1D00, 0x1DBF },
+ { 0x1E00, 0x1F15 },
+ { 0x1F18, 0x1F1D },
+ { 0x1F20, 0x1F45 },
+ { 0x1F48, 0x1F4D },
+ { 0x1F50, 0x1F57 },
+ { 0x1F5F, 0x1F7D },
+ { 0x1F80, 0x1FB4 },
+ { 0x1FB6, 0x1FBC },
+ { 0x1FC2, 0x1FC4 },
+ { 0x1FC6, 0x1FCC },
+ { 0x1FD0, 0x1FD3 },
+ { 0x1FD6, 0x1FDB },
+ { 0x1FE0, 0x1FEC },
+ { 0x1FF2, 0x1FF4 },
+ { 0x1FF6, 0x1FFC },
+ { 0x2090, 0x209C },
+ { 0x210A, 0x2113 },
+ { 0x2119, 0x211D },
+ { 0x212A, 0x212D },
+ { 0x212F, 0x2139 },
+ { 0x213C, 0x213F },
+ { 0x2145, 0x2149 },
+ { 0x2183, 0x2184 },
+ { 0x2C00, 0x2C2E },
+ { 0x2C30, 0x2C5E },
+ { 0x2C60, 0x2CE4 },
+ { 0x2CEB, 0x2CEE },
+ { 0x2CF2, 0x2CF3 },
+ { 0x2D00, 0x2D25 },
+ { 0x2D30, 0x2D67 },
+ { 0x2D80, 0x2D96 },
+ { 0x2DA0, 0x2DA6 },
+ { 0x2DA8, 0x2DAE },
+ { 0x2DB0, 0x2DB6 },
+ { 0x2DB8, 0x2DBE },
+ { 0x2DC0, 0x2DC6 },
+ { 0x2DC8, 0x2DCE },
+ { 0x2DD0, 0x2DD6 },
+ { 0x2DD8, 0x2DDE },
+ { 0x3005, 0x3006 },
+ { 0x3031, 0x3035 },
+ { 0x303B, 0x303C },
+ { 0x3041, 0x3096 },
+ { 0x309D, 0x309F },
+ { 0x30A1, 0x30FA },
+ { 0x30FC, 0x30FF },
+ { 0x3105, 0x312D },
+ { 0x3131, 0x318E },
+ { 0x31A0, 0x31BA },
+ { 0x31F0, 0x31FF },
+ { 0xA000, 0xA48C },
+ { 0xA4D0, 0xA4FD },
+ { 0xA500, 0xA60C },
+ { 0xA610, 0xA61F },
+ { 0xA62A, 0xA62B },
+ { 0xA640, 0xA66E },
+ { 0xA67F, 0xA697 },
+ { 0xA6A0, 0xA6E5 },
+ { 0xA717, 0xA71F },
+ { 0xA722, 0xA788 },
+ { 0xA78B, 0xA78E },
+ { 0xA790, 0xA793 },
+ { 0xA7A0, 0xA7AA },
+ { 0xA7F8, 0xA801 },
+ { 0xA803, 0xA805 },
+ { 0xA807, 0xA80A },
+ { 0xA80C, 0xA822 },
+ { 0xA840, 0xA873 },
+ { 0xA882, 0xA8B3 },
+ { 0xA8F2, 0xA8F7 },
+ { 0xA90A, 0xA925 },
+ { 0xA930, 0xA946 },
+ { 0xA960, 0xA97C },
+ { 0xA984, 0xA9B2 },
+ { 0xAA00, 0xAA28 },
+ { 0xAA40, 0xAA42 },
+ { 0xAA44, 0xAA4B },
+ { 0xAA60, 0xAA76 },
+ { 0xAA80, 0xAAAF },
+ { 0xAAB5, 0xAAB6 },
+ { 0xAAB9, 0xAABD },
+ { 0xAADB, 0xAADD },
+ { 0xAAE0, 0xAAEA },
+ { 0xAAF2, 0xAAF4 },
+ { 0xAB01, 0xAB06 },
+ { 0xAB09, 0xAB0E },
+ { 0xAB11, 0xAB16 },
+ { 0xAB20, 0xAB26 },
+ { 0xAB28, 0xAB2E },
+ { 0xABC0, 0xABE2 },
+ { 0xD7B0, 0xD7C6 },
+ { 0xD7CB, 0xD7FB },
+ { 0xF900, 0xFA6D },
+ { 0xFA70, 0xFAD9 },
+ { 0xFB00, 0xFB06 },
+ { 0xFB13, 0xFB17 },
+ { 0xFB1F, 0xFB28 },
+ { 0xFB2A, 0xFB36 },
+ { 0xFB38, 0xFB3C },
+ { 0xFB40, 0xFB41 },
+ { 0xFB43, 0xFB44 },
+ { 0xFB46, 0xFBB1 },
+ { 0xFBD3, 0xFD3D },
+ { 0xFD50, 0xFD8F },
+ { 0xFD92, 0xFDC7 },
+ { 0xFDF0, 0xFDFB },
+ { 0xFE70, 0xFE74 },
+ { 0xFE76, 0xFEFC },
+ { 0xFF21, 0xFF3A },
+ { 0xFF41, 0xFF5A },
+ { 0xFF66, 0xFFBE },
+ { 0xFFC2, 0xFFC7 },
+ { 0xFFCA, 0xFFCF },
+ { 0xFFD2, 0xFFD7 },
+ { 0xFFDA, 0xFFDC },
+ { 0x10000, 0x1000B },
+ { 0x1000D, 0x10026 },
+ { 0x10028, 0x1003A },
+ { 0x1003C, 0x1003D },
+ { 0x1003F, 0x1004D },
+ { 0x10050, 0x1005D },
+ { 0x10080, 0x100FA },
+ { 0x10280, 0x1029C },
+ { 0x102A0, 0x102D0 },
+ { 0x10300, 0x1031E },
+ { 0x10330, 0x10340 },
+ { 0x10342, 0x10349 },
+ { 0x10380, 0x1039D },
+ { 0x103A0, 0x103C3 },
+ { 0x103C8, 0x103CF },
+ { 0x10400, 0x1049D },
+ { 0x10800, 0x10805 },
+ { 0x1080A, 0x10835 },
+ { 0x10837, 0x10838 },
+ { 0x1083F, 0x10855 },
+ { 0x10900, 0x10915 },
+ { 0x10920, 0x10939 },
+ { 0x10980, 0x109B7 },
+ { 0x109BE, 0x109BF },
+ { 0x10A10, 0x10A13 },
+ { 0x10A15, 0x10A17 },
+ { 0x10A19, 0x10A33 },
+ { 0x10A60, 0x10A7C },
+ { 0x10B00, 0x10B35 },
+ { 0x10B40, 0x10B55 },
+ { 0x10B60, 0x10B72 },
+ { 0x10C00, 0x10C48 },
+ { 0x11003, 0x11037 },
+ { 0x11083, 0x110AF },
+ { 0x110D0, 0x110E8 },
+ { 0x11103, 0x11126 },
+ { 0x11183, 0x111B2 },
+ { 0x111C1, 0x111C4 },
+ { 0x11680, 0x116AA },
+ { 0x12000, 0x1236E },
+ { 0x13000, 0x1342E },
+ { 0x16800, 0x16A38 },
+ { 0x16F00, 0x16F44 },
+ { 0x16F93, 0x16F9F },
+ { 0x1B000, 0x1B001 },
+ { 0x1D400, 0x1D454 },
+ { 0x1D456, 0x1D49C },
+ { 0x1D49E, 0x1D49F },
+ { 0x1D4A5, 0x1D4A6 },
+ { 0x1D4A9, 0x1D4AC },
+ { 0x1D4AE, 0x1D4B9 },
+ { 0x1D4BD, 0x1D4C3 },
+ { 0x1D4C5, 0x1D505 },
+ { 0x1D507, 0x1D50A },
+ { 0x1D50D, 0x1D514 },
+ { 0x1D516, 0x1D51C },
+ { 0x1D51E, 0x1D539 },
+ { 0x1D53B, 0x1D53E },
+ { 0x1D540, 0x1D544 },
+ { 0x1D54A, 0x1D550 },
+ { 0x1D552, 0x1D6A5 },
+ { 0x1D6A8, 0x1D6C0 },
+ { 0x1D6C2, 0x1D6DA },
+ { 0x1D6DC, 0x1D6FA },
+ { 0x1D6FC, 0x1D714 },
+ { 0x1D716, 0x1D734 },
+ { 0x1D736, 0x1D74E },
+ { 0x1D750, 0x1D76E },
+ { 0x1D770, 0x1D788 },
+ { 0x1D78A, 0x1D7A8 },
+ { 0x1D7AA, 0x1D7C2 },
+ { 0x1D7C4, 0x1D7CB },
+ { 0x1EE00, 0x1EE03 },
+ { 0x1EE05, 0x1EE1F },
+ { 0x1EE21, 0x1EE22 },
+ { 0x1EE29, 0x1EE32 },
+ { 0x1EE34, 0x1EE37 },
+ { 0x1EE4D, 0x1EE4F },
+ { 0x1EE51, 0x1EE52 },
+ { 0x1EE61, 0x1EE62 },
+ { 0x1EE67, 0x1EE6A },
+ { 0x1EE6C, 0x1EE72 },
+ { 0x1EE74, 0x1EE77 },
+ { 0x1EE79, 0x1EE7C },
+ { 0x1EE80, 0x1EE89 },
+ { 0x1EE8B, 0x1EE9B },
+ { 0x1EEA1, 0x1EEA3 },
+ { 0x1EEA5, 0x1EEA9 },
+ { 0x1EEAB, 0x1EEBB },
+ { 0x2F800, 0x2FA1D },
+};
+
+static Rune alpha1[] = {
+ 0x00AA,
+ 0x00B5,
+ 0x00BA,
+ 0x02EC,
+ 0x02EE,
+ 0x0386,
+ 0x038C,
+ 0x0559,
+ 0x06D5,
+ 0x06FF,
+ 0x0710,
+ 0x07B1,
+ 0x07FA,
+ 0x081A,
+ 0x0824,
+ 0x0828,
+ 0x08A0,
+ 0x093D,
+ 0x0950,
+ 0x09B2,
+ 0x09BD,
+ 0x09CE,
+ 0x0A5E,
+ 0x0ABD,
+ 0x0AD0,
+ 0x0B3D,
+ 0x0B71,
+ 0x0B83,
+ 0x0B9C,
+ 0x0BD0,
+ 0x0C3D,
+ 0x0CBD,
+ 0x0CDE,
+ 0x0D3D,
+ 0x0D4E,
+ 0x0DBD,
+ 0x0E84,
+ 0x0E8A,
+ 0x0E8D,
+ 0x0EA5,
+ 0x0EA7,
+ 0x0EBD,
+ 0x0EC6,
+ 0x0F00,
+ 0x103F,
+ 0x1061,
+ 0x108E,
+ 0x10C7,
+ 0x10CD,
+ 0x1258,
+ 0x12C0,
+ 0x17D7,
+ 0x17DC,
+ 0x18AA,
+ 0x1AA7,
+ 0x1F59,
+ 0x1F5B,
+ 0x1F5D,
+ 0x1FBE,
+ 0x2071,
+ 0x207F,
+ 0x2102,
+ 0x2107,
+ 0x2115,
+ 0x2124,
+ 0x2126,
+ 0x2128,
+ 0x214E,
+ 0x2D27,
+ 0x2D2D,
+ 0x2D6F,
+ 0x2E2F,
+ 0x3400,
+ 0x4DB5,
+ 0x4E00,
+ 0x9FCC,
+ 0xA8FB,
+ 0xA9CF,
+ 0xAA7A,
+ 0xAAB1,
+ 0xAAC0,
+ 0xAAC2,
+ 0xAC00,
+ 0xD7A3,
+ 0xFB1D,
+ 0xFB3E,
+ 0x10808,
+ 0x1083C,
+ 0x10A00,
+ 0x16F50,
+ 0x1D4A2,
+ 0x1D4BB,
+ 0x1D546,
+ 0x1EE24,
+ 0x1EE27,
+ 0x1EE39,
+ 0x1EE3B,
+ 0x1EE42,
+ 0x1EE47,
+ 0x1EE49,
+ 0x1EE4B,
+ 0x1EE54,
+ 0x1EE57,
+ 0x1EE59,
+ 0x1EE5B,
+ 0x1EE5D,
+ 0x1EE5F,
+ 0x1EE64,
+ 0x1EE7E,
+ 0x20000,
+ 0x2A6D6,
+ 0x2A700,
+ 0x2B734,
+ 0x2B740,
+ 0x2B81D,
+};
+
+int
+isalpharune(Rune r)
+{
+ if(bsearch(&r, alpha2, nelem(alpha2), sizeof *alpha2, &rune2cmp))
+ return 1;
+ if(bsearch(&r, alpha1, nelem(alpha1), sizeof *alpha1, &rune1cmp))
+ return 1;
+ return 0;
+}
+
+static Rune space2[][2] = {
+ { 0x2000, 0x200A },
+ { 0x2028, 0x2029 },
+};
+
+static Rune space1[] = {
+ 0x0020,
+ 0x00A0,
+ 0x1680,
+ 0x180E,
+ 0x202F,
+ 0x205F,
+ 0x3000,
+};
+
+int
+isspacerune(Rune r)
+{
+ if(bsearch(&r, space2, nelem(space2), sizeof *space2, &rune2cmp))
+ return 1;
+ if(bsearch(&r, space1, nelem(space1), sizeof *space1, &rune1cmp))
+ return 1;
+ return 0;
+}
+
+static Rune upper2[][2] = {
+ { 0x0041, 0x005A },
+ { 0x00C0, 0x00D6 },
+ { 0x00D8, 0x00DE },
+ { 0x0178, 0x0179 },
+ { 0x0181, 0x0182 },
+ { 0x0186, 0x0187 },
+ { 0x0189, 0x018B },
+ { 0x018E, 0x0191 },
+ { 0x0193, 0x0194 },
+ { 0x0196, 0x0198 },
+ { 0x019C, 0x019D },
+ { 0x019F, 0x01A0 },
+ { 0x01A6, 0x01A7 },
+ { 0x01AE, 0x01AF },
+ { 0x01B1, 0x01B3 },
+ { 0x01B7, 0x01B8 },
+ { 0x01F6, 0x01F8 },
+ { 0x023A, 0x023B },
+ { 0x023D, 0x023E },
+ { 0x0243, 0x0246 },
+ { 0x0388, 0x038A },
+ { 0x038E, 0x038F },
+ { 0x0391, 0x03A1 },
+ { 0x03A3, 0x03AB },
+ { 0x03D2, 0x03D4 },
+ { 0x03F9, 0x03FA },
+ { 0x03FD, 0x042F },
+ { 0x04C0, 0x04C1 },
+ { 0x0531, 0x0556 },
+ { 0x10A0, 0x10C5 },
+ { 0x1F08, 0x1F0F },
+ { 0x1F18, 0x1F1D },
+ { 0x1F28, 0x1F2F },
+ { 0x1F38, 0x1F3F },
+ { 0x1F48, 0x1F4D },
+ { 0x1F68, 0x1F6F },
+ { 0x1FB8, 0x1FBB },
+ { 0x1FC8, 0x1FCB },
+ { 0x1FD8, 0x1FDB },
+ { 0x1FE8, 0x1FEC },
+ { 0x1FF8, 0x1FFB },
+ { 0x210B, 0x210D },
+ { 0x2110, 0x2112 },
+ { 0x2119, 0x211D },
+ { 0x212A, 0x212D },
+ { 0x2130, 0x2133 },
+ { 0x213E, 0x213F },
+ { 0x2C00, 0x2C2E },
+ { 0x2C62, 0x2C64 },
+ { 0x2C6D, 0x2C70 },
+ { 0x2C7E, 0x2C80 },
+ { 0xA77D, 0xA77E },
+ { 0xFF21, 0xFF3A },
+ { 0x10400, 0x10427 },
+ { 0x1D400, 0x1D419 },
+ { 0x1D434, 0x1D44D },
+ { 0x1D468, 0x1D481 },
+ { 0x1D49E, 0x1D49F },
+ { 0x1D4A5, 0x1D4A6 },
+ { 0x1D4A9, 0x1D4AC },
+ { 0x1D4AE, 0x1D4B5 },
+ { 0x1D4D0, 0x1D4E9 },
+ { 0x1D504, 0x1D505 },
+ { 0x1D507, 0x1D50A },
+ { 0x1D50D, 0x1D514 },
+ { 0x1D516, 0x1D51C },
+ { 0x1D538, 0x1D539 },
+ { 0x1D53B, 0x1D53E },
+ { 0x1D540, 0x1D544 },
+ { 0x1D54A, 0x1D550 },
+ { 0x1D56C, 0x1D585 },
+ { 0x1D5A0, 0x1D5B9 },
+ { 0x1D5D4, 0x1D5ED },
+ { 0x1D608, 0x1D621 },
+ { 0x1D63C, 0x1D655 },
+ { 0x1D670, 0x1D689 },
+ { 0x1D6A8, 0x1D6C0 },
+ { 0x1D6E2, 0x1D6FA },
+ { 0x1D71C, 0x1D734 },
+ { 0x1D756, 0x1D76E },
+ { 0x1D790, 0x1D7A8 },
+};
+
+static Rune upper1[] = {
+ 0x0100,
+ 0x0102,
+ 0x0104,
+ 0x0106,
+ 0x0108,
+ 0x010A,
+ 0x010C,
+ 0x010E,
+ 0x0110,
+ 0x0112,
+ 0x0114,
+ 0x0116,
+ 0x0118,
+ 0x011A,
+ 0x011C,
+ 0x011E,
+ 0x0120,
+ 0x0122,
+ 0x0124,
+ 0x0126,
+ 0x0128,
+ 0x012A,
+ 0x012C,
+ 0x012E,
+ 0x0130,
+ 0x0132,
+ 0x0134,
+ 0x0136,
+ 0x0139,
+ 0x013B,
+ 0x013D,
+ 0x013F,
+ 0x0141,
+ 0x0143,
+ 0x0145,
+ 0x0147,
+ 0x014A,
+ 0x014C,
+ 0x014E,
+ 0x0150,
+ 0x0152,
+ 0x0154,
+ 0x0156,
+ 0x0158,
+ 0x015A,
+ 0x015C,
+ 0x015E,
+ 0x0160,
+ 0x0162,
+ 0x0164,
+ 0x0166,
+ 0x0168,
+ 0x016A,
+ 0x016C,
+ 0x016E,
+ 0x0170,
+ 0x0172,
+ 0x0174,
+ 0x0176,
+ 0x017B,
+ 0x017D,
+ 0x0184,
+ 0x01A2,
+ 0x01A4,
+ 0x01A9,
+ 0x01AC,
+ 0x01B5,
+ 0x01BC,
+ 0x01C4,
+ 0x01C7,
+ 0x01CA,
+ 0x01CD,
+ 0x01CF,
+ 0x01D1,
+ 0x01D3,
+ 0x01D5,
+ 0x01D7,
+ 0x01D9,
+ 0x01DB,
+ 0x01DE,
+ 0x01E0,
+ 0x01E2,
+ 0x01E4,
+ 0x01E6,
+ 0x01E8,
+ 0x01EA,
+ 0x01EC,
+ 0x01EE,
+ 0x01F1,
+ 0x01F4,
+ 0x01FA,
+ 0x01FC,
+ 0x01FE,
+ 0x0200,
+ 0x0202,
+ 0x0204,
+ 0x0206,
+ 0x0208,
+ 0x020A,
+ 0x020C,
+ 0x020E,
+ 0x0210,
+ 0x0212,
+ 0x0214,
+ 0x0216,
+ 0x0218,
+ 0x021A,
+ 0x021C,
+ 0x021E,
+ 0x0220,
+ 0x0222,
+ 0x0224,
+ 0x0226,
+ 0x0228,
+ 0x022A,
+ 0x022C,
+ 0x022E,
+ 0x0230,
+ 0x0232,
+ 0x0241,
+ 0x0248,
+ 0x024A,
+ 0x024C,
+ 0x024E,
+ 0x0370,
+ 0x0372,
+ 0x0376,
+ 0x0386,
+ 0x038C,
+ 0x03CF,
+ 0x03D8,
+ 0x03DA,
+ 0x03DC,
+ 0x03DE,
+ 0x03E0,
+ 0x03E2,
+ 0x03E4,
+ 0x03E6,
+ 0x03E8,
+ 0x03EA,
+ 0x03EC,
+ 0x03EE,
+ 0x03F4,
+ 0x03F7,
+ 0x0460,
+ 0x0462,
+ 0x0464,
+ 0x0466,
+ 0x0468,
+ 0x046A,
+ 0x046C,
+ 0x046E,
+ 0x0470,
+ 0x0472,
+ 0x0474,
+ 0x0476,
+ 0x0478,
+ 0x047A,
+ 0x047C,
+ 0x047E,
+ 0x0480,
+ 0x048A,
+ 0x048C,
+ 0x048E,
+ 0x0490,
+ 0x0492,
+ 0x0494,
+ 0x0496,
+ 0x0498,
+ 0x049A,
+ 0x049C,
+ 0x049E,
+ 0x04A0,
+ 0x04A2,
+ 0x04A4,
+ 0x04A6,
+ 0x04A8,
+ 0x04AA,
+ 0x04AC,
+ 0x04AE,
+ 0x04B0,
+ 0x04B2,
+ 0x04B4,
+ 0x04B6,
+ 0x04B8,
+ 0x04BA,
+ 0x04BC,
+ 0x04BE,
+ 0x04C3,
+ 0x04C5,
+ 0x04C7,
+ 0x04C9,
+ 0x04CB,
+ 0x04CD,
+ 0x04D0,
+ 0x04D2,
+ 0x04D4,
+ 0x04D6,
+ 0x04D8,
+ 0x04DA,
+ 0x04DC,
+ 0x04DE,
+ 0x04E0,
+ 0x04E2,
+ 0x04E4,
+ 0x04E6,
+ 0x04E8,
+ 0x04EA,
+ 0x04EC,
+ 0x04EE,
+ 0x04F0,
+ 0x04F2,
+ 0x04F4,
+ 0x04F6,
+ 0x04F8,
+ 0x04FA,
+ 0x04FC,
+ 0x04FE,
+ 0x0500,
+ 0x0502,
+ 0x0504,
+ 0x0506,
+ 0x0508,
+ 0x050A,
+ 0x050C,
+ 0x050E,
+ 0x0510,
+ 0x0512,
+ 0x0514,
+ 0x0516,
+ 0x0518,
+ 0x051A,
+ 0x051C,
+ 0x051E,
+ 0x0520,
+ 0x0522,
+ 0x0524,
+ 0x0526,
+ 0x10C7,
+ 0x10CD,
+ 0x1E00,
+ 0x1E02,
+ 0x1E04,
+ 0x1E06,
+ 0x1E08,
+ 0x1E0A,
+ 0x1E0C,
+ 0x1E0E,
+ 0x1E10,
+ 0x1E12,
+ 0x1E14,
+ 0x1E16,
+ 0x1E18,
+ 0x1E1A,
+ 0x1E1C,
+ 0x1E1E,
+ 0x1E20,
+ 0x1E22,
+ 0x1E24,
+ 0x1E26,
+ 0x1E28,
+ 0x1E2A,
+ 0x1E2C,
+ 0x1E2E,
+ 0x1E30,
+ 0x1E32,
+ 0x1E34,
+ 0x1E36,
+ 0x1E38,
+ 0x1E3A,
+ 0x1E3C,
+ 0x1E3E,
+ 0x1E40,
+ 0x1E42,
+ 0x1E44,
+ 0x1E46,
+ 0x1E48,
+ 0x1E4A,
+ 0x1E4C,
+ 0x1E4E,
+ 0x1E50,
+ 0x1E52,
+ 0x1E54,
+ 0x1E56,
+ 0x1E58,
+ 0x1E5A,
+ 0x1E5C,
+ 0x1E5E,
+ 0x1E60,
+ 0x1E62,
+ 0x1E64,
+ 0x1E66,
+ 0x1E68,
+ 0x1E6A,
+ 0x1E6C,
+ 0x1E6E,
+ 0x1E70,
+ 0x1E72,
+ 0x1E74,
+ 0x1E76,
+ 0x1E78,
+ 0x1E7A,
+ 0x1E7C,
+ 0x1E7E,
+ 0x1E80,
+ 0x1E82,
+ 0x1E84,
+ 0x1E86,
+ 0x1E88,
+ 0x1E8A,
+ 0x1E8C,
+ 0x1E8E,
+ 0x1E90,
+ 0x1E92,
+ 0x1E94,
+ 0x1E9E,
+ 0x1EA0,
+ 0x1EA2,
+ 0x1EA4,
+ 0x1EA6,
+ 0x1EA8,
+ 0x1EAA,
+ 0x1EAC,
+ 0x1EAE,
+ 0x1EB0,
+ 0x1EB2,
+ 0x1EB4,
+ 0x1EB6,
+ 0x1EB8,
+ 0x1EBA,
+ 0x1EBC,
+ 0x1EBE,
+ 0x1EC0,
+ 0x1EC2,
+ 0x1EC4,
+ 0x1EC6,
+ 0x1EC8,
+ 0x1ECA,
+ 0x1ECC,
+ 0x1ECE,
+ 0x1ED0,
+ 0x1ED2,
+ 0x1ED4,
+ 0x1ED6,
+ 0x1ED8,
+ 0x1EDA,
+ 0x1EDC,
+ 0x1EDE,
+ 0x1EE0,
+ 0x1EE2,
+ 0x1EE4,
+ 0x1EE6,
+ 0x1EE8,
+ 0x1EEA,
+ 0x1EEC,
+ 0x1EEE,
+ 0x1EF0,
+ 0x1EF2,
+ 0x1EF4,
+ 0x1EF6,
+ 0x1EF8,
+ 0x1EFA,
+ 0x1EFC,
+ 0x1EFE,
+ 0x1F59,
+ 0x1F5B,
+ 0x1F5D,
+ 0x1F5F,
+ 0x2102,
+ 0x2107,
+ 0x2115,
+ 0x2124,
+ 0x2126,
+ 0x2128,
+ 0x2145,
+ 0x2183,
+ 0x2C60,
+ 0x2C67,
+ 0x2C69,
+ 0x2C6B,
+ 0x2C72,
+ 0x2C75,
+ 0x2C82,
+ 0x2C84,
+ 0x2C86,
+ 0x2C88,
+ 0x2C8A,
+ 0x2C8C,
+ 0x2C8E,
+ 0x2C90,
+ 0x2C92,
+ 0x2C94,
+ 0x2C96,
+ 0x2C98,
+ 0x2C9A,
+ 0x2C9C,
+ 0x2C9E,
+ 0x2CA0,
+ 0x2CA2,
+ 0x2CA4,
+ 0x2CA6,
+ 0x2CA8,
+ 0x2CAA,
+ 0x2CAC,
+ 0x2CAE,
+ 0x2CB0,
+ 0x2CB2,
+ 0x2CB4,
+ 0x2CB6,
+ 0x2CB8,
+ 0x2CBA,
+ 0x2CBC,
+ 0x2CBE,
+ 0x2CC0,
+ 0x2CC2,
+ 0x2CC4,
+ 0x2CC6,
+ 0x2CC8,
+ 0x2CCA,
+ 0x2CCC,
+ 0x2CCE,
+ 0x2CD0,
+ 0x2CD2,
+ 0x2CD4,
+ 0x2CD6,
+ 0x2CD8,
+ 0x2CDA,
+ 0x2CDC,
+ 0x2CDE,
+ 0x2CE0,
+ 0x2CE2,
+ 0x2CEB,
+ 0x2CED,
+ 0x2CF2,
+ 0xA640,
+ 0xA642,
+ 0xA644,
+ 0xA646,
+ 0xA648,
+ 0xA64A,
+ 0xA64C,
+ 0xA64E,
+ 0xA650,
+ 0xA652,
+ 0xA654,
+ 0xA656,
+ 0xA658,
+ 0xA65A,
+ 0xA65C,
+ 0xA65E,
+ 0xA660,
+ 0xA662,
+ 0xA664,
+ 0xA666,
+ 0xA668,
+ 0xA66A,
+ 0xA66C,
+ 0xA680,
+ 0xA682,
+ 0xA684,
+ 0xA686,
+ 0xA688,
+ 0xA68A,
+ 0xA68C,
+ 0xA68E,
+ 0xA690,
+ 0xA692,
+ 0xA694,
+ 0xA696,
+ 0xA722,
+ 0xA724,
+ 0xA726,
+ 0xA728,
+ 0xA72A,
+ 0xA72C,
+ 0xA72E,
+ 0xA732,
+ 0xA734,
+ 0xA736,
+ 0xA738,
+ 0xA73A,
+ 0xA73C,
+ 0xA73E,
+ 0xA740,
+ 0xA742,
+ 0xA744,
+ 0xA746,
+ 0xA748,
+ 0xA74A,
+ 0xA74C,
+ 0xA74E,
+ 0xA750,
+ 0xA752,
+ 0xA754,
+ 0xA756,
+ 0xA758,
+ 0xA75A,
+ 0xA75C,
+ 0xA75E,
+ 0xA760,
+ 0xA762,
+ 0xA764,
+ 0xA766,
+ 0xA768,
+ 0xA76A,
+ 0xA76C,
+ 0xA76E,
+ 0xA779,
+ 0xA77B,
+ 0xA780,
+ 0xA782,
+ 0xA784,
+ 0xA786,
+ 0xA78B,
+ 0xA78D,
+ 0xA790,
+ 0xA792,
+ 0xA7A0,
+ 0xA7A2,
+ 0xA7A4,
+ 0xA7A6,
+ 0xA7A8,
+ 0xA7AA,
+ 0x1D49C,
+ 0x1D4A2,
+ 0x1D546,
+ 0x1D7CA,
+};
+
+int
+isupperrune(Rune r)
+{
+ if(bsearch(&r, upper2, nelem(upper2), sizeof *upper2, &rune2cmp))
+ return 1;
+ if(bsearch(&r, upper1, nelem(upper1), sizeof *upper1, &rune1cmp))
+ return 1;
+ return 0;
+}
+
+static Rune lower2[][2] = {
+ { 0x0061, 0x007A },
+ { 0x00DF, 0x00F6 },
+ { 0x00F8, 0x00FF },
+ { 0x0137, 0x0138 },
+ { 0x0148, 0x0149 },
+ { 0x017E, 0x0180 },
+ { 0x018C, 0x018D },
+ { 0x0199, 0x019B },
+ { 0x01AA, 0x01AB },
+ { 0x01B9, 0x01BA },
+ { 0x01BD, 0x01BF },
+ { 0x01DC, 0x01DD },
+ { 0x01EF, 0x01F0 },
+ { 0x0233, 0x0239 },
+ { 0x023F, 0x0240 },
+ { 0x024F, 0x0293 },
+ { 0x0295, 0x02AF },
+ { 0x037B, 0x037D },
+ { 0x03AC, 0x03CE },
+ { 0x03D0, 0x03D1 },
+ { 0x03D5, 0x03D7 },
+ { 0x03EF, 0x03F3 },
+ { 0x03FB, 0x03FC },
+ { 0x0430, 0x045F },
+ { 0x04CE, 0x04CF },
+ { 0x0561, 0x0587 },
+ { 0x1D00, 0x1D2B },
+ { 0x1D6B, 0x1D77 },
+ { 0x1D79, 0x1D9A },
+ { 0x1E95, 0x1E9D },
+ { 0x1EFF, 0x1F07 },
+ { 0x1F10, 0x1F15 },
+ { 0x1F20, 0x1F27 },
+ { 0x1F30, 0x1F37 },
+ { 0x1F40, 0x1F45 },
+ { 0x1F50, 0x1F57 },
+ { 0x1F60, 0x1F67 },
+ { 0x1F70, 0x1F7D },
+ { 0x1F80, 0x1F87 },
+ { 0x1F90, 0x1F97 },
+ { 0x1FA0, 0x1FA7 },
+ { 0x1FB0, 0x1FB4 },
+ { 0x1FB6, 0x1FB7 },
+ { 0x1FC2, 0x1FC4 },
+ { 0x1FC6, 0x1FC7 },
+ { 0x1FD0, 0x1FD3 },
+ { 0x1FD6, 0x1FD7 },
+ { 0x1FE0, 0x1FE7 },
+ { 0x1FF2, 0x1FF4 },
+ { 0x1FF6, 0x1FF7 },
+ { 0x210E, 0x210F },
+ { 0x213C, 0x213D },
+ { 0x2146, 0x2149 },
+ { 0x2C30, 0x2C5E },
+ { 0x2C65, 0x2C66 },
+ { 0x2C73, 0x2C74 },
+ { 0x2C76, 0x2C7B },
+ { 0x2CE3, 0x2CE4 },
+ { 0x2D00, 0x2D25 },
+ { 0xA72F, 0xA731 },
+ { 0xA771, 0xA778 },
+ { 0xFB00, 0xFB06 },
+ { 0xFB13, 0xFB17 },
+ { 0xFF41, 0xFF5A },
+ { 0x10428, 0x1044F },
+ { 0x1D41A, 0x1D433 },
+ { 0x1D44E, 0x1D454 },
+ { 0x1D456, 0x1D467 },
+ { 0x1D482, 0x1D49B },
+ { 0x1D4B6, 0x1D4B9 },
+ { 0x1D4BD, 0x1D4C3 },
+ { 0x1D4C5, 0x1D4CF },
+ { 0x1D4EA, 0x1D503 },
+ { 0x1D51E, 0x1D537 },
+ { 0x1D552, 0x1D56B },
+ { 0x1D586, 0x1D59F },
+ { 0x1D5BA, 0x1D5D3 },
+ { 0x1D5EE, 0x1D607 },
+ { 0x1D622, 0x1D63B },
+ { 0x1D656, 0x1D66F },
+ { 0x1D68A, 0x1D6A5 },
+ { 0x1D6C2, 0x1D6DA },
+ { 0x1D6DC, 0x1D6E1 },
+ { 0x1D6FC, 0x1D714 },
+ { 0x1D716, 0x1D71B },
+ { 0x1D736, 0x1D74E },
+ { 0x1D750, 0x1D755 },
+ { 0x1D770, 0x1D788 },
+ { 0x1D78A, 0x1D78F },
+ { 0x1D7AA, 0x1D7C2 },
+ { 0x1D7C4, 0x1D7C9 },
+};
+
+static Rune lower1[] = {
+ 0x00B5,
+ 0x0101,
+ 0x0103,
+ 0x0105,
+ 0x0107,
+ 0x0109,
+ 0x010B,
+ 0x010D,
+ 0x010F,
+ 0x0111,
+ 0x0113,
+ 0x0115,
+ 0x0117,
+ 0x0119,
+ 0x011B,
+ 0x011D,
+ 0x011F,
+ 0x0121,
+ 0x0123,
+ 0x0125,
+ 0x0127,
+ 0x0129,
+ 0x012B,
+ 0x012D,
+ 0x012F,
+ 0x0131,
+ 0x0133,
+ 0x0135,
+ 0x013A,
+ 0x013C,
+ 0x013E,
+ 0x0140,
+ 0x0142,
+ 0x0144,
+ 0x0146,
+ 0x014B,
+ 0x014D,
+ 0x014F,
+ 0x0151,
+ 0x0153,
+ 0x0155,
+ 0x0157,
+ 0x0159,
+ 0x015B,
+ 0x015D,
+ 0x015F,
+ 0x0161,
+ 0x0163,
+ 0x0165,
+ 0x0167,
+ 0x0169,
+ 0x016B,
+ 0x016D,
+ 0x016F,
+ 0x0171,
+ 0x0173,
+ 0x0175,
+ 0x0177,
+ 0x017A,
+ 0x017C,
+ 0x0183,
+ 0x0185,
+ 0x0188,
+ 0x0192,
+ 0x0195,
+ 0x019E,
+ 0x01A1,
+ 0x01A3,
+ 0x01A5,
+ 0x01A8,
+ 0x01AD,
+ 0x01B0,
+ 0x01B4,
+ 0x01B6,
+ 0x01C6,
+ 0x01C9,
+ 0x01CC,
+ 0x01CE,
+ 0x01D0,
+ 0x01D2,
+ 0x01D4,
+ 0x01D6,
+ 0x01D8,
+ 0x01DA,
+ 0x01DF,
+ 0x01E1,
+ 0x01E3,
+ 0x01E5,
+ 0x01E7,
+ 0x01E9,
+ 0x01EB,
+ 0x01ED,
+ 0x01F3,
+ 0x01F5,
+ 0x01F9,
+ 0x01FB,
+ 0x01FD,
+ 0x01FF,
+ 0x0201,
+ 0x0203,
+ 0x0205,
+ 0x0207,
+ 0x0209,
+ 0x020B,
+ 0x020D,
+ 0x020F,
+ 0x0211,
+ 0x0213,
+ 0x0215,
+ 0x0217,
+ 0x0219,
+ 0x021B,
+ 0x021D,
+ 0x021F,
+ 0x0221,
+ 0x0223,
+ 0x0225,
+ 0x0227,
+ 0x0229,
+ 0x022B,
+ 0x022D,
+ 0x022F,
+ 0x0231,
+ 0x023C,
+ 0x0242,
+ 0x0247,
+ 0x0249,
+ 0x024B,
+ 0x024D,
+ 0x0371,
+ 0x0373,
+ 0x0377,
+ 0x0390,
+ 0x03D9,
+ 0x03DB,
+ 0x03DD,
+ 0x03DF,
+ 0x03E1,
+ 0x03E3,
+ 0x03E5,
+ 0x03E7,
+ 0x03E9,
+ 0x03EB,
+ 0x03ED,
+ 0x03F5,
+ 0x03F8,
+ 0x0461,
+ 0x0463,
+ 0x0465,
+ 0x0467,
+ 0x0469,
+ 0x046B,
+ 0x046D,
+ 0x046F,
+ 0x0471,
+ 0x0473,
+ 0x0475,
+ 0x0477,
+ 0x0479,
+ 0x047B,
+ 0x047D,
+ 0x047F,
+ 0x0481,
+ 0x048B,
+ 0x048D,
+ 0x048F,
+ 0x0491,
+ 0x0493,
+ 0x0495,
+ 0x0497,
+ 0x0499,
+ 0x049B,
+ 0x049D,
+ 0x049F,
+ 0x04A1,
+ 0x04A3,
+ 0x04A5,
+ 0x04A7,
+ 0x04A9,
+ 0x04AB,
+ 0x04AD,
+ 0x04AF,
+ 0x04B1,
+ 0x04B3,
+ 0x04B5,
+ 0x04B7,
+ 0x04B9,
+ 0x04BB,
+ 0x04BD,
+ 0x04BF,
+ 0x04C2,
+ 0x04C4,
+ 0x04C6,
+ 0x04C8,
+ 0x04CA,
+ 0x04CC,
+ 0x04D1,
+ 0x04D3,
+ 0x04D5,
+ 0x04D7,
+ 0x04D9,
+ 0x04DB,
+ 0x04DD,
+ 0x04DF,
+ 0x04E1,
+ 0x04E3,
+ 0x04E5,
+ 0x04E7,
+ 0x04E9,
+ 0x04EB,
+ 0x04ED,
+ 0x04EF,
+ 0x04F1,
+ 0x04F3,
+ 0x04F5,
+ 0x04F7,
+ 0x04F9,
+ 0x04FB,
+ 0x04FD,
+ 0x04FF,
+ 0x0501,
+ 0x0503,
+ 0x0505,
+ 0x0507,
+ 0x0509,
+ 0x050B,
+ 0x050D,
+ 0x050F,
+ 0x0511,
+ 0x0513,
+ 0x0515,
+ 0x0517,
+ 0x0519,
+ 0x051B,
+ 0x051D,
+ 0x051F,
+ 0x0521,
+ 0x0523,
+ 0x0525,
+ 0x0527,
+ 0x1E01,
+ 0x1E03,
+ 0x1E05,
+ 0x1E07,
+ 0x1E09,
+ 0x1E0B,
+ 0x1E0D,
+ 0x1E0F,
+ 0x1E11,
+ 0x1E13,
+ 0x1E15,
+ 0x1E17,
+ 0x1E19,
+ 0x1E1B,
+ 0x1E1D,
+ 0x1E1F,
+ 0x1E21,
+ 0x1E23,
+ 0x1E25,
+ 0x1E27,
+ 0x1E29,
+ 0x1E2B,
+ 0x1E2D,
+ 0x1E2F,
+ 0x1E31,
+ 0x1E33,
+ 0x1E35,
+ 0x1E37,
+ 0x1E39,
+ 0x1E3B,
+ 0x1E3D,
+ 0x1E3F,
+ 0x1E41,
+ 0x1E43,
+ 0x1E45,
+ 0x1E47,
+ 0x1E49,
+ 0x1E4B,
+ 0x1E4D,
+ 0x1E4F,
+ 0x1E51,
+ 0x1E53,
+ 0x1E55,
+ 0x1E57,
+ 0x1E59,
+ 0x1E5B,
+ 0x1E5D,
+ 0x1E5F,
+ 0x1E61,
+ 0x1E63,
+ 0x1E65,
+ 0x1E67,
+ 0x1E69,
+ 0x1E6B,
+ 0x1E6D,
+ 0x1E6F,
+ 0x1E71,
+ 0x1E73,
+ 0x1E75,
+ 0x1E77,
+ 0x1E79,
+ 0x1E7B,
+ 0x1E7D,
+ 0x1E7F,
+ 0x1E81,
+ 0x1E83,
+ 0x1E85,
+ 0x1E87,
+ 0x1E89,
+ 0x1E8B,
+ 0x1E8D,
+ 0x1E8F,
+ 0x1E91,
+ 0x1E93,
+ 0x1E9F,
+ 0x1EA1,
+ 0x1EA3,
+ 0x1EA5,
+ 0x1EA7,
+ 0x1EA9,
+ 0x1EAB,
+ 0x1EAD,
+ 0x1EAF,
+ 0x1EB1,
+ 0x1EB3,
+ 0x1EB5,
+ 0x1EB7,
+ 0x1EB9,
+ 0x1EBB,
+ 0x1EBD,
+ 0x1EBF,
+ 0x1EC1,
+ 0x1EC3,
+ 0x1EC5,
+ 0x1EC7,
+ 0x1EC9,
+ 0x1ECB,
+ 0x1ECD,
+ 0x1ECF,
+ 0x1ED1,
+ 0x1ED3,
+ 0x1ED5,
+ 0x1ED7,
+ 0x1ED9,
+ 0x1EDB,
+ 0x1EDD,
+ 0x1EDF,
+ 0x1EE1,
+ 0x1EE3,
+ 0x1EE5,
+ 0x1EE7,
+ 0x1EE9,
+ 0x1EEB,
+ 0x1EED,
+ 0x1EEF,
+ 0x1EF1,
+ 0x1EF3,
+ 0x1EF5,
+ 0x1EF7,
+ 0x1EF9,
+ 0x1EFB,
+ 0x1EFD,
+ 0x1FBE,
+ 0x210A,
+ 0x2113,
+ 0x212F,
+ 0x2134,
+ 0x2139,
+ 0x214E,
+ 0x2184,
+ 0x2C61,
+ 0x2C68,
+ 0x2C6A,
+ 0x2C6C,
+ 0x2C71,
+ 0x2C81,
+ 0x2C83,
+ 0x2C85,
+ 0x2C87,
+ 0x2C89,
+ 0x2C8B,
+ 0x2C8D,
+ 0x2C8F,
+ 0x2C91,
+ 0x2C93,
+ 0x2C95,
+ 0x2C97,
+ 0x2C99,
+ 0x2C9B,
+ 0x2C9D,
+ 0x2C9F,
+ 0x2CA1,
+ 0x2CA3,
+ 0x2CA5,
+ 0x2CA7,
+ 0x2CA9,
+ 0x2CAB,
+ 0x2CAD,
+ 0x2CAF,
+ 0x2CB1,
+ 0x2CB3,
+ 0x2CB5,
+ 0x2CB7,
+ 0x2CB9,
+ 0x2CBB,
+ 0x2CBD,
+ 0x2CBF,
+ 0x2CC1,
+ 0x2CC3,
+ 0x2CC5,
+ 0x2CC7,
+ 0x2CC9,
+ 0x2CCB,
+ 0x2CCD,
+ 0x2CCF,
+ 0x2CD1,
+ 0x2CD3,
+ 0x2CD5,
+ 0x2CD7,
+ 0x2CD9,
+ 0x2CDB,
+ 0x2CDD,
+ 0x2CDF,
+ 0x2CE1,
+ 0x2CEC,
+ 0x2CEE,
+ 0x2CF3,
+ 0x2D27,
+ 0x2D2D,
+ 0xA641,
+ 0xA643,
+ 0xA645,
+ 0xA647,
+ 0xA649,
+ 0xA64B,
+ 0xA64D,
+ 0xA64F,
+ 0xA651,
+ 0xA653,
+ 0xA655,
+ 0xA657,
+ 0xA659,
+ 0xA65B,
+ 0xA65D,
+ 0xA65F,
+ 0xA661,
+ 0xA663,
+ 0xA665,
+ 0xA667,
+ 0xA669,
+ 0xA66B,
+ 0xA66D,
+ 0xA681,
+ 0xA683,
+ 0xA685,
+ 0xA687,
+ 0xA689,
+ 0xA68B,
+ 0xA68D,
+ 0xA68F,
+ 0xA691,
+ 0xA693,
+ 0xA695,
+ 0xA697,
+ 0xA723,
+ 0xA725,
+ 0xA727,
+ 0xA729,
+ 0xA72B,
+ 0xA72D,
+ 0xA733,
+ 0xA735,
+ 0xA737,
+ 0xA739,
+ 0xA73B,
+ 0xA73D,
+ 0xA73F,
+ 0xA741,
+ 0xA743,
+ 0xA745,
+ 0xA747,
+ 0xA749,
+ 0xA74B,
+ 0xA74D,
+ 0xA74F,
+ 0xA751,
+ 0xA753,
+ 0xA755,
+ 0xA757,
+ 0xA759,
+ 0xA75B,
+ 0xA75D,
+ 0xA75F,
+ 0xA761,
+ 0xA763,
+ 0xA765,
+ 0xA767,
+ 0xA769,
+ 0xA76B,
+ 0xA76D,
+ 0xA76F,
+ 0xA77A,
+ 0xA77C,
+ 0xA77F,
+ 0xA781,
+ 0xA783,
+ 0xA785,
+ 0xA787,
+ 0xA78C,
+ 0xA78E,
+ 0xA791,
+ 0xA793,
+ 0xA7A1,
+ 0xA7A3,
+ 0xA7A5,
+ 0xA7A7,
+ 0xA7A9,
+ 0xA7FA,
+ 0x1D4BB,
+ 0x1D7CB,
+};
+
+int
+islowerrune(Rune r)
+{
+ if(bsearch(&r, lower2, nelem(lower2), sizeof *lower2, &rune2cmp))
+ return 1;
+ if(bsearch(&r, lower1, nelem(lower1), sizeof *lower1, &rune1cmp))
+ return 1;
+ return 0;
+}
+
+static Rune title2[][2] = {
+ { 0x1F88, 0x1F8F },
+ { 0x1F98, 0x1F9F },
+ { 0x1FA8, 0x1FAF },
+};
+
+static Rune title1[] = {
+ 0x01C5,
+ 0x01C8,
+ 0x01CB,
+ 0x01F2,
+ 0x1FBC,
+ 0x1FCC,
+ 0x1FFC,
+};
+
+int
+istitlerune(Rune r)
+{
+ if(bsearch(&r, title2, nelem(title2), sizeof *title2, &rune2cmp))
+ return 1;
+ if(bsearch(&r, title1, nelem(title1), sizeof *title1, &rune1cmp))
+ return 1;
+ return 0;
+}
+
+static Rune digit2[][2] = {
+ { 0x0030, 0x0039 },
+ { 0x0660, 0x0669 },
+ { 0x06F0, 0x06F9 },
+ { 0x07C0, 0x07C9 },
+ { 0x0966, 0x096F },
+ { 0x09E6, 0x09EF },
+ { 0x0A66, 0x0A6F },
+ { 0x0AE6, 0x0AEF },
+ { 0x0B66, 0x0B6F },
+ { 0x0BE6, 0x0BEF },
+ { 0x0C66, 0x0C6F },
+ { 0x0CE6, 0x0CEF },
+ { 0x0D66, 0x0D6F },
+ { 0x0E50, 0x0E59 },
+ { 0x0ED0, 0x0ED9 },
+ { 0x0F20, 0x0F29 },
+ { 0x1040, 0x1049 },
+ { 0x1090, 0x1099 },
+ { 0x17E0, 0x17E9 },
+ { 0x1810, 0x1819 },
+ { 0x1946, 0x194F },
+ { 0x19D0, 0x19D9 },
+ { 0x1A80, 0x1A89 },
+ { 0x1A90, 0x1A99 },
+ { 0x1B50, 0x1B59 },
+ { 0x1BB0, 0x1BB9 },
+ { 0x1C40, 0x1C49 },
+ { 0x1C50, 0x1C59 },
+ { 0xA620, 0xA629 },
+ { 0xA8D0, 0xA8D9 },
+ { 0xA900, 0xA909 },
+ { 0xA9D0, 0xA9D9 },
+ { 0xAA50, 0xAA59 },
+ { 0xABF0, 0xABF9 },
+ { 0xFF10, 0xFF19 },
+ { 0x104A0, 0x104A9 },
+ { 0x11066, 0x1106F },
+ { 0x110F0, 0x110F9 },
+ { 0x11136, 0x1113F },
+ { 0x111D0, 0x111D9 },
+ { 0x116C0, 0x116C9 },
+ { 0x1D7CE, 0x1D7FF },
+};
+
+int
+isdigitrune(Rune r)
+{
+ if(bsearch(&r, digit2, nelem(digit2), sizeof *digit2, &rune2cmp))
+ return 1;
+ return 0;
+}
+
diff --git a/sbase/seq.1 b/sbase/seq.1
@@ -0,0 +1,36 @@
+.TH SEQ 1 sbase\-VERSION
+.SH NAME
+seq \- print a sequence of numbers
+.SH SYNOPSIS
+.B seq
+.RB [ \-w ]
+.RB [ \-f
+.IR fmt ]
+.RB [ \-s
+.IR separator ]
+.RI [ start
+.RI [ step ]]
+.IR end
+.SH DESCRIPTION
+.B seq
+will print a sequence of numbers from
+.I start
+(default 1) to
+.IR end ,
+in
+.IR step
+intervals (default 1).
+.SH OPTIONS
+.TP
+.BI \-f " format"
+specifies the format used for output lines, as per
+.IR printf (3).
+.TP
+.BI \-s " separator"
+specifies the separator to print between output lines
+.TP
+.BI \-w
+tells seq to print out lines in equal width
+
+.SH SEE ALSO
+.IR printf (3)
diff --git a/sbase/seq.c b/sbase/seq.c
@@ -0,0 +1,153 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static int digitsleft(const char *);
+static int digitsright(const char *);
+static int validfmt(const char *);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-f fmt] [-s separator] [-w width] [start"
+ " [step]] end\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *starts = "1", *steps = "1", *ends = "1", *sep = "\n";
+ int wflag = 0;
+ char *tmp, ftmp[BUFSIZ], *fmt = ftmp;
+ double start, step, end, out, dir;
+ int left, right;
+
+ ARGBEGIN {
+ case 'f':
+ if(!validfmt(tmp=EARGF(usage())))
+ eprintf("%s: invalid format\n", tmp);
+ fmt = tmp;
+ break;
+ case 's':
+ sep = EARGF(usage());
+ break;
+ case 'w':
+ wflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ switch (argc) {
+ case 3:
+ steps = argv[1];
+ argv[1] = argv[2];
+ /* fallthrough */
+ case 2:
+ starts = argv[0];
+ argv++;
+ /* fallthrough */
+ case 1:
+ ends = argv[0];
+ break;
+ default:
+ usage();
+ }
+ start = estrtod(starts);
+ step = estrtod(steps);
+ end = estrtod(ends);
+
+ dir = (step > 0) ? 1.0 : -1.0;
+ if (step == 0 || start * dir > end * dir)
+ return 1;
+
+ if (fmt == ftmp) {
+ right = MAX(digitsright(starts),
+ MAX(digitsright(ends),
+ digitsright(steps)));
+
+ if (wflag) {
+ left = MAX(digitsleft(starts), digitsleft(ends));
+
+ snprintf(ftmp, sizeof ftmp, "%%0%d.%df",
+ right + left + (right != 0), right);
+ } else
+ snprintf(ftmp, sizeof ftmp, "%%.%df", right);
+ }
+ for (out = start; out * dir <= end * dir; out += step) {
+ if (out != start)
+ fputs(sep, stdout);
+ printf(fmt, out);
+ }
+ printf("\n");
+
+ return 0;
+}
+
+static int
+digitsleft(const char *d)
+{
+ char *exp;
+ int shift;
+
+ if (*d == '+')
+ d++;
+ exp = strpbrk(d, "eE");
+ shift = exp ? estrtol(&exp[1], 10) : 0;
+
+ return MAX(0, strspn(d, "-0123456789") + shift);
+}
+
+static int
+digitsright(const char *d)
+{
+ char *exp;
+ int shift, after;
+
+ exp = strpbrk(d, "eE");
+ shift = exp ? estrtol(&exp[1], 10) : 0;
+ after = (d = strchr(d, '.')) ? strspn(&d[1], "0123456789") : 0;
+
+ return MAX(0, after - shift);
+}
+
+static int
+validfmt(const char *fmt)
+{
+ int occur = 0;
+
+literal:
+ while (*fmt)
+ if (*fmt++ == '%')
+ goto format;
+ return occur == 1;
+
+format:
+ if (*fmt == '%') {
+ fmt++;
+ goto literal;
+ }
+ fmt += strspn(fmt, "-+#0 '");
+ fmt += strspn(fmt, "0123456789");
+ if (*fmt == '.') {
+ fmt++;
+ fmt += strspn(fmt, "0123456789");
+ }
+ if (*fmt == 'L')
+ fmt++;
+
+ switch (*fmt) {
+ case 'f': case 'F':
+ case 'g': case 'G':
+ case 'e': case 'E':
+ case 'a': case 'A':
+ occur++;
+ goto literal;
+ default:
+ return 0;
+ }
+}
diff --git a/sbase/setsid.1 b/sbase/setsid.1
@@ -0,0 +1,7 @@
+.TH SETSID 1 sbase\-VERSION
+.SH NAME
+setsid \- run a program in a new session
+.SH SYNOPSIS
+.B setsid
+.RI program
+.RI [ arg ...]
diff --git a/sbase/setsid.c b/sbase/setsid.c
@@ -0,0 +1,45 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s cmd [arg ...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int savederrno;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ if (getpgrp() == getpid()) {
+ switch (fork()) {
+ case -1:
+ eprintf("fork:");
+ case 0:
+ break;
+ default:
+ return 0;
+ }
+ }
+ if (setsid() < 0)
+ eprintf("setsid:");
+ execvp(argv[0], argv);
+ savederrno = errno;
+ weprintf("execvp %s:", argv[0]);
+ return (savederrno == ENOENT) ? 127 : 126;
+}
diff --git a/sbase/sha1.h b/sbase/sha1.h
@@ -0,0 +1,18 @@
+/* public domain sha1 implementation based on rfc3174 and libtomcrypt */
+
+struct sha1 {
+ uint64_t len; /* processed message length */
+ uint32_t h[5]; /* hash state */
+ uint8_t buf[64]; /* message block buffer */
+};
+
+enum { SHA1_DIGEST_LENGTH = 20 };
+
+/* reset state */
+void sha1_init(void *ctx);
+/* process message */
+void sha1_update(void *ctx, const void *m, unsigned long len);
+/* get message digest */
+/* state is ruined after sum, keep a copy if multiple sum is needed */
+/* part of the message might be left in s, zero it if secrecy is needed */
+void sha1_sum(void *ctx, uint8_t md[SHA1_DIGEST_LENGTH]);
diff --git a/sbase/sha1sum.1 b/sbase/sha1sum.1
@@ -0,0 +1,12 @@
+.TH SHA1SUM 1 sbase\-VERSION
+.SH NAME
+sha1sum \- compute SHA-1 message digest
+.SH SYNOPSIS
+.B sha1sum
+.RB [\-c]
+.RI [ file ...]
+.TP
+.B \-c
+read list of SHA1 checksums from file and check them
+.SH DESCRIPTION
+Print SHA-1 (160-bit) checksums. With no file, read standard input.
diff --git a/sbase/sha1sum.c b/sbase/sha1sum.c
@@ -0,0 +1,43 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "crypt.h"
+#include "sha1.h"
+#include "util.h"
+
+static struct sha1 s;
+struct crypt_ops sha1_ops = {
+ sha1_init,
+ sha1_update,
+ sha1_sum,
+ &s,
+};
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-c] [file...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ uint8_t md[SHA1_DIGEST_LENGTH];
+ char *checkfile = NULL;
+ int cflag = 0;
+
+ ARGBEGIN {
+ case 'c':
+ cflag = 1;
+ checkfile = ARGF();
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (cflag)
+ return cryptcheck(checkfile, argc, argv, &sha1_ops, md, sizeof(md));
+ return cryptmain(argc, argv, &sha1_ops, md, sizeof(md));
+}
diff --git a/sbase/sha256.h b/sbase/sha256.h
@@ -0,0 +1,18 @@
+/* public domain sha256 implementation based on fips180-3 */
+
+struct sha256 {
+ uint64_t len; /* processed message length */
+ uint32_t h[8]; /* hash state */
+ uint8_t buf[64]; /* message block buffer */
+};
+
+enum { SHA256_DIGEST_LENGTH = 32 };
+
+/* reset state */
+void sha256_init(void *ctx);
+/* process message */
+void sha256_update(void *ctx, const void *m, unsigned long len);
+/* get message digest */
+/* state is ruined after sum, keep a copy if multiple sum is needed */
+/* part of the message might be left in s, zero it if secrecy is needed */
+void sha256_sum(void *ctx, uint8_t md[SHA256_DIGEST_LENGTH]);
diff --git a/sbase/sha256sum.1 b/sbase/sha256sum.1
@@ -0,0 +1,12 @@
+.TH SHA256SUM 1 sbase\-VERSION
+.SH NAME
+sha256sum \- compute SHA256 message digest
+.SH SYNOPSIS
+.B sha256sum
+.RB [\-c]
+.RI [ file ...]
+.TP
+.B \-c
+read list of SHA256 checksums from file and check them
+.SH DESCRIPTION
+Print SHA256 (256-bit) checksums. With no file, read standard input.
diff --git a/sbase/sha256sum.c b/sbase/sha256sum.c
@@ -0,0 +1,43 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "crypt.h"
+#include "sha256.h"
+#include "util.h"
+
+static struct sha256 s;
+struct crypt_ops sha256_ops = {
+ sha256_init,
+ sha256_update,
+ sha256_sum,
+ &s,
+};
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-c] [file...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ uint8_t md[SHA256_DIGEST_LENGTH];
+ char *checkfile = NULL;
+ int cflag = 0;
+
+ ARGBEGIN {
+ case 'c':
+ cflag = 1;
+ checkfile = ARGF();
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (cflag)
+ return cryptcheck(checkfile, argc, argv, &sha256_ops, md, sizeof(md));
+ return cryptmain(argc, argv, &sha256_ops, md, sizeof(md));
+}
diff --git a/sbase/sha512.h b/sbase/sha512.h
@@ -0,0 +1,18 @@
+/* public domain sha512 implementation based on fips180-3 */
+
+struct sha512 {
+ uint64_t len; /* processed message length */
+ uint64_t h[8]; /* hash state */
+ uint8_t buf[128]; /* message block buffer */
+};
+
+enum { SHA512_DIGEST_LENGTH = 64 };
+
+/* reset state */
+void sha512_init(void *ctx);
+/* process message */
+void sha512_update(void *ctx, const void *m, unsigned long len);
+/* get message digest */
+/* state is ruined after sum, keep a copy if multiple sum is needed */
+/* part of the message might be left in s, zero it if secrecy is needed */
+void sha512_sum(void *ctx, uint8_t md[SHA512_DIGEST_LENGTH]);
diff --git a/sbase/sha512sum.1 b/sbase/sha512sum.1
@@ -0,0 +1,12 @@
+.TH SHA512SUM 1 sbase\-VERSION
+.SH NAME
+sha512sum \- compute SHA512 message digest
+.SH SYNOPSIS
+.B sha512sum
+.RB [\-c]
+.RI [ file ...]
+.TP
+.B \-c
+read list of SHA512 checksums from file and check them
+.SH DESCRIPTION
+Print SHA512 (512-bit) checksums. With no file, read standard input.
diff --git a/sbase/sha512sum.c b/sbase/sha512sum.c
@@ -0,0 +1,43 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "crypt.h"
+#include "sha512.h"
+#include "util.h"
+
+static struct sha512 s;
+struct crypt_ops sha512_ops = {
+ sha512_init,
+ sha512_update,
+ sha512_sum,
+ &s,
+};
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-c] [file...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ uint8_t md[SHA512_DIGEST_LENGTH];
+ char *checkfile = NULL;
+ int cflag = 0;
+
+ ARGBEGIN {
+ case 'c':
+ cflag = 1;
+ checkfile = ARGF();
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (cflag)
+ return cryptcheck(checkfile, argc, argv, &sha512_ops, md, sizeof(md));
+ return cryptmain(argc, argv, &sha512_ops, md, sizeof(md));
+}
diff --git a/sbase/sleep.1 b/sbase/sleep.1
@@ -0,0 +1,11 @@
+.TH SLEEP 1 sbase\-VERSION
+.SH NAME
+sleep \- wait for a number of seconds
+.SH SYNOPSIS
+.B sleep
+.I seconds
+.SH DESCRIPTION
+.B sleep
+waits until the given number of seconds have elapsed.
+.SH SEE ALSO
+.IR sleep (3)
diff --git a/sbase/sleep.c b/sbase/sleep.c
@@ -0,0 +1,30 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s seconds\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ unsigned int seconds;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ seconds = estrtol(argv[0], 0);
+ while ((seconds = sleep(seconds)) > 0)
+ ;
+ return 0;
+}
diff --git a/sbase/sort.1 b/sbase/sort.1
@@ -0,0 +1,67 @@
+.TH SORT 1 sbase\-VERSION
+.SH NAME
+sort \- sort lines
+.SH SYNOPSIS
+.B sort
+.RB [ \-bnru ]
+.RB [ \-t
+.IR delim ]
+.RB [ \-k
+.IR key ]...
+.RI [ file ...]
+.SH DESCRIPTION
+.B sort
+writes the sorted concatenation of the given files to stdout. If no file is
+given, sort reads from stdin.
+.SH OPTIONS
+.TP
+.B \-C
+check that the concatenation of the given files is sorted rather than sorting
+them. In this mode, no output is printed to stdout, and the exit status
+indicates the result of the check.
+.TP
+.B \-b
+skip leading whitespace of columns when sorting.
+.TP
+.B \-c
+the same as
+.B \-C
+except that when disorder is detected, a message is printed to stderr
+indicating the location of the disorder.
+.TP
+.BI \-k \ key
+specifies a key definition of the form
+.BR S [. s ][ f ][, E [. e ][ f ]]
+where
+.BR S ,
+.BR s ,
+.BR E ,
+and
+.B e
+are the starting column, starting character in that column, ending column and
+the ending character of that column respectively. If they are not specified,
+.B s
+refers to the first character of the specified starting column,
+.B E
+refers to the last column of every line, and
+.B e
+refers to the last character of that last column.
+.B f
+can be used to specify options
+.RB ( n ,
+.BR b )
+that only apply to this key definition.
+.B b
+is special in that it only applies to the column that it was specified after.
+.TP
+.B \-n
+perform a numeric sort.
+.TP
+.B \-r
+reverses the sort.
+.TP
+.BI \-t \ delim
+specifies the field delimiter.
+.TP
+.B \-u
+prints equal lines only once.
diff --git a/sbase/sort.c b/sbase/sort.c
@@ -0,0 +1,307 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "text.h"
+#include "util.h"
+
+struct keydef {
+ int start_column;
+ int end_column;
+ int start_char;
+ int end_char;
+ int flags;
+};
+
+enum {
+ MOD_N = 1 << 1,
+ MOD_STARTB = 1 << 2,
+ MOD_ENDB = 1 << 3,
+ MOD_R = 1 << 4,
+};
+
+struct kdlist {
+ struct keydef keydef;
+ struct kdlist *next;
+};
+
+static struct kdlist *head = NULL;
+static struct kdlist *tail = NULL;
+
+static void addkeydef(char *, int);
+static void check(FILE *);
+static int linecmp(const char **, const char **);
+static char *skipblank(char *);
+static int parse_flags(char **, int *, int);
+static int parse_keydef(struct keydef *, char *, int);
+static char *nextcol(char *);
+static char *columns(char *, const struct keydef *);
+
+static int Cflag = 0, cflag = 0, uflag = 0;
+static char *fieldsep = NULL;
+
+static void
+usage(void)
+{
+ enprintf(2, "usage: %s [-Cbcnru] [-t delim] [-k def]... [file...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ long i;
+ FILE *fp;
+ struct linebuf linebuf = EMPTY_LINEBUF;
+ int global_flags = 0;
+
+ ARGBEGIN {
+ case 'C':
+ Cflag = 1;
+ break;
+ case 'b':
+ global_flags |= MOD_STARTB | MOD_ENDB;
+ break;
+ case 'c':
+ cflag = 1;
+ break;
+ case 'k':
+ addkeydef(EARGF(usage()), global_flags);
+ break;
+ case 'n':
+ global_flags |= MOD_N;
+ break;
+ case 'r':
+ global_flags |= MOD_R;
+ break;
+ case 't':
+ fieldsep = EARGF(usage());
+ if (strlen(fieldsep) != 1)
+ usage();
+ break;
+ case 'u':
+ uflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (!head && global_flags)
+ addkeydef("1", global_flags);
+ addkeydef("1", global_flags & MOD_R);
+
+ if (argc == 0) {
+ if (Cflag || cflag) {
+ check(stdin);
+ } else {
+ getlines(stdin, &linebuf);
+ }
+ } else for (; argc > 0; argc--, argv++) {
+ if (!(fp = fopen(argv[0], "r"))) {
+ enprintf(2, "fopen %s:", argv[0]);
+ continue;
+ }
+ if (Cflag || cflag) {
+ check(fp);
+ } else {
+ getlines(fp, &linebuf);
+ }
+ fclose(fp);
+ }
+
+ if (!Cflag && !cflag) {
+ qsort(linebuf.lines, linebuf.nlines, sizeof *linebuf.lines,
+ (int (*)(const void *, const void *))linecmp);
+
+ for (i = 0; i < linebuf.nlines; i++) {
+ if (!uflag || i == 0 || linecmp((const char **)&linebuf.lines[i],
+ (const char **)&linebuf.lines[i-1])) {
+ fputs(linebuf.lines[i], stdout);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void
+addkeydef(char *def, int flags)
+{
+ struct kdlist *node;
+
+ node = malloc(sizeof(*node));
+ if (!node)
+ enprintf(2, "malloc:");
+ if (!head)
+ head = node;
+ if (parse_keydef(&node->keydef, def, flags))
+ enprintf(2, "faulty key definition\n");
+ if (tail)
+ tail->next = node;
+ node->next = NULL;
+ tail = node;
+}
+
+static void
+check(FILE *fp)
+{
+ static struct { char *buf; size_t size; } prev, cur, tmp;
+
+ if (!prev.buf)
+ getline(&prev.buf, &prev.size, fp);
+ while (getline(&cur.buf, &cur.size, fp) != -1) {
+ if (uflag > linecmp((const char **) &cur.buf, (const char **) &prev.buf)) {
+ if (!Cflag)
+ weprintf("disorder: %s", cur.buf);
+ exit(1);
+ }
+ tmp = cur;
+ cur = prev;
+ prev = tmp;
+ }
+}
+
+static int
+linecmp(const char **a, const char **b)
+{
+ char *s1, *s2;
+ int res = 0;
+ struct kdlist *node;
+
+ for (node = head; node && res == 0; node = node->next) {
+ s1 = columns((char *)*a, &node->keydef);
+ s2 = columns((char *)*b, &node->keydef);
+
+ /* if -u is given, don't use default key definition
+ * unless it is the only one */
+ if (uflag && node == tail && head != tail)
+ res = 0;
+ else if (node->keydef.flags & MOD_N)
+ res = strtol(s1, 0, 10) - strtol(s2, 0, 10);
+ else
+ res = strcmp(s1, s2);
+
+ if (node->keydef.flags & MOD_R)
+ res = -res;
+
+ free(s1);
+ free(s2);
+ }
+ return res;
+}
+
+static int
+parse_flags(char **s, int *flags, int bflag)
+{
+ while (isalpha((int)**s))
+ switch (*((*s)++)) {
+ case 'b':
+ *flags |= bflag;
+ break;
+ case 'n':
+ *flags |= MOD_N;
+ break;
+ case 'r':
+ *flags |= MOD_R;
+ break;
+ default:
+ return -1;
+ }
+ return 0;
+}
+
+static int
+parse_keydef(struct keydef *kd, char *s, int flags)
+{
+ char *rest = s;
+
+ kd->start_column = 1;
+ kd->start_char = 1;
+ /* 0 means end of line */
+ kd->end_column = 0;
+ kd->end_char = 0;
+ kd->flags = flags;
+
+ kd->start_column = strtol(rest, &rest, 10);
+ if (kd->start_column < 1)
+ return -1;
+ if (*rest == '.')
+ kd->start_char = strtol(rest+1, &rest, 10);
+ if (kd->start_char < 1)
+ return -1;
+ if (parse_flags(&rest, &kd->flags, MOD_STARTB) < 0)
+ return -1;
+ if (*rest == ',') {
+ kd->end_column = strtol(rest+1, &rest, 10);
+ if (kd->end_column && kd->end_column < kd->start_column)
+ return -1;
+ if (*rest == '.') {
+ kd->end_char = strtol(rest+1, &rest, 10);
+ if (kd->end_char < 1)
+ return -1;
+ }
+ if (parse_flags(&rest, &kd->flags, MOD_ENDB) < 0)
+ return -1;
+ }
+ if (*rest != '\0')
+ return -1;
+ return 0;
+}
+
+static char *
+skipblank(char *s)
+{
+ while(*s && isblank(*s))
+ s++;
+ return s;
+}
+
+static char *
+nextcol(char *s)
+{
+ if (!fieldsep) {
+ s = skipblank(s);
+ while(*s && !isblank(*s))
+ s++;
+ } else {
+ if (!strchr(s, *fieldsep))
+ s = strchr(s, '\0');
+ else
+ s = strchr(s, *fieldsep) + 1;
+ }
+ return s;
+}
+
+static char *
+columns(char *line, const struct keydef *kd)
+{
+ char *start, *end;
+ char *res;
+ int i;
+
+ for (i = 1, start = line; i < kd->start_column; i++)
+ start = nextcol(start);
+ if (kd->flags & MOD_STARTB)
+ start = skipblank(start);
+ start += MIN(kd->start_char, nextcol(start) - start) - 1;
+
+ if (kd->end_column) {
+ for (i = 1, end = line; i < kd->end_column; i++)
+ end = nextcol(end);
+ if (kd->flags & MOD_ENDB)
+ end = skipblank(end);
+ if (kd->end_char)
+ end += MIN(kd->end_char, nextcol(end) - end);
+ else
+ end = nextcol(end);
+ } else {
+ if (!(end = strchr(line, '\n')))
+ end = strchr(line, '\0');
+ }
+
+ if (!(res = strndup(start, end - start)))
+ enprintf(2, "strndup:");
+ return res;
+}
diff --git a/sbase/split.1 b/sbase/split.1
@@ -0,0 +1,58 @@
+.TH SPLIT 1 sbase\-VERSION
+.SH NAME
+split \- split up a file
+.SH SYNOPSIS
+.B split
+.RB [ \-d ]
+.RB [ \-a
+.IR len ]
+.RB [ \-b
+.RI [ bytes [k|m|g]]]
+.RB [ \-l
+.RI [ lines ]]
+.RI [ input
+.RI [ prefix ]]
+
+.SH DESCRIPTION
+.B split
+Reads a file, splitting it into smaller files, every
+.IR bytes
+bytes
+or
+.IR lines
+lines. If
+.B split
+runs out of filenames before all the data can be written, it stops at the
+last valid filename, leaving all the written data on the disk.
+
+The
+.IR b
+and
+.IR l
+flags are mutually exclusive. Only the last one specified will be obeyed.
+
+.SH OPTIONS
+.TP
+.B \-d
+Use decimal suffixes rather than alphabetical.
+
+.TP
+.B \-a "len"
+Set the suffix length to
+.IR len
+characters long.
+
+.TP
+.B \-b [bytes[k|m|g]]
+Start a new file every
+.IR bytes
+bytes. The units k, m, and g are case insensitive, and powers of 2, not 10.
+
+.TP
+.B \-l [lines]
+Start a new file every
+.IR lines
+lines.
+
+.SH SEE ALSO
+.IR cat (1)
diff --git a/sbase/split.c b/sbase/split.c
@@ -0,0 +1,141 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+static int itostr(char *, int, int);
+static FILE *nextfile(FILE *, char *, int, int);
+
+static void
+usage(void)
+{
+ eprintf("usage: split [-d] [-a len] [-b [bytes[k|m|g]]] [-l [lines]] [input [prefix]]\n");
+}
+
+static int base = 26, start = 'a';
+
+int
+main(int argc, char *argv[])
+{
+ int plen, slen = 2;
+ int ch;
+ char name[NAME_MAX+1];
+ char *prefix = "x";
+ char *file = NULL;
+ char *tmp, *end;
+ uint64_t size = 1000, scale = 1, n;
+ int always = 0;
+ FILE *in = stdin, *out = NULL;
+
+ ARGBEGIN {
+ case 'b':
+ always = 1;
+ tmp = ARGF();
+ if (!tmp)
+ break;
+
+ size = strtoull(tmp, &end, 10);
+ if (!*end)
+ break;
+ switch (toupper((int)*end)) {
+ case 'K':
+ scale = 1024;
+ break;
+ case 'M':
+ scale = 1024L * 1024L;
+ break;
+ case 'G':
+ scale = 1024L * 1024L * 1024L;
+ break;
+ default:
+ usage();
+ }
+ if (size > (UINT64_MAX/scale))
+ eprintf("'%s': out of range\n", tmp);
+ size *= scale;
+ break;
+ case 'l':
+ always = 0;
+ tmp = ARGF();
+ if (tmp)
+ size = estrtol(tmp, 10);
+ break;
+ case 'a':
+ slen = estrtol(EARGF(usage()), 10);
+ break;
+ case 'd':
+ base = 10;
+ start = '0';
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (*argv)
+ file = *argv++;
+ if (*argv)
+ prefix = *argv++;
+ if (*argv)
+ usage();
+
+ plen = strlen(prefix);
+ if (plen+slen > NAME_MAX)
+ eprintf("names cannot exceed %d bytes\n", NAME_MAX);
+ strlcpy(name, prefix, sizeof(name));
+
+ if (file && strcmp(file, "-") != 0) {
+ in = fopen(file, "r");
+ if (!in)
+ eprintf("'%s':", file);
+ }
+
+Nextfile:
+ while ((out = nextfile(out, name, plen, slen))) {
+ n = 0;
+ while ((ch = getc(in)) != EOF) {
+ putc(ch, out);
+ n += (always || ch == '\n');
+ if (n >= size)
+ goto Nextfile;
+ }
+ fclose(out);
+ break;
+ }
+ return 0;
+}
+
+int
+itostr(char *str, int x, int n)
+{
+ str[n] = '\0';
+ while (n-- > 0) {
+ str[n] = start + (x % base);
+ x /= base;
+ }
+ if (x)
+ return -1;
+ return 0;
+}
+
+FILE *
+nextfile(FILE *f, char *buf, int plen, int slen)
+{
+ static int n = 0;
+ int s;
+
+ if (f)
+ fclose(f);
+ s = itostr(buf+plen, n++, slen);
+ if (s < 0)
+ return NULL;
+
+ f = fopen(buf, "w");
+ if (!f)
+ eprintf("'%s':", buf);
+ return f;
+}
diff --git a/sbase/sponge.1 b/sbase/sponge.1
@@ -0,0 +1,15 @@
+.TH SPONGE 1 sbase\-VERSION
+.SH NAME
+sponge \- soak up standard input and write to a file
+.SH SYNOPSIS
+.B sponge
+.IR file
+.SH DESCRIPTION
+.B sponge
+reads stdin completely, then writes the saved contents to
+.IR file .
+This makes it possible to easily create pipes which read from and write to
+the same file.
+
+If the given file is a symbolic link, it writes to the links's destination
+instead.
diff --git a/sbase/sponge.c b/sbase/sponge.c
@@ -0,0 +1,41 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "text.h"
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: sponge file\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp, *tmpfp;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc != 1)
+ usage();
+
+ if (!(tmpfp = tmpfile()))
+ eprintf("tmpfile:");
+
+ concat(stdin, "<stdin>", tmpfp, "<tmpfile>");
+ rewind(tmpfp);
+
+ if (!(fp = fopen(argv[0], "w")))
+ eprintf("fopen %s:", argv[0]);
+ concat(tmpfp, "<tmpfile>", fp, argv[0]);
+
+ fclose(fp);
+ fclose(tmpfp);
+
+ return 0;
+}
diff --git a/sbase/strings.1 b/sbase/strings.1
@@ -0,0 +1,17 @@
+.Dd November 23, 2014
+.Dt STRINGS 1 sbase\-VERSION
+.Os
+.Sh NAME
+.Nm strings
+.Nd print the strings of pritable characters in files
+.Sh SYNOPSIS
+.Nm strings
+.Op Ar file ...
+.Sh DESCRIPTION
+.Nm
+prints the printable character sequences that are at least 6 characters
+long. If no files are given then it uses stdin.
+.Sh STANDARDS
+.Nm
+mirrors the semantics of Plan9
+.Xr strings 1 .
diff --git a/sbase/strings.c b/sbase/strings.c
@@ -0,0 +1,62 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "util.h"
+
+static void dostrings(FILE *fp, const char *fname);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [file ...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+ int ret = 0;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0) {
+ dostrings(stdin, "<stdin>");
+ } else {
+ for (; argc > 0; argc--, argv++) {
+ if (!(fp = fopen(argv[0], "r"))) {
+ weprintf("fopen %s:", argv[0]);
+ ret = 1;
+ continue;
+ }
+ dostrings(fp, argv[0]);
+ fclose(fp);
+ }
+ }
+ return ret;
+}
+
+static void
+dostrings(FILE *fp, const char *fname)
+{
+ unsigned char buf[BUFSIZ];
+ int c, i = 0;
+ off_t offset = 0;
+
+ do {
+ offset++;
+ if (isprint(c = getc(fp)))
+ buf[i++] = c;
+ if ((!isprint(c) && i >= 6) || i == sizeof(buf) - 1) {
+ buf[i] = '\0';
+ printf("%8ld: %s\n", (long)offset - i - 1, buf);
+ i = 0;
+ }
+ } while (c != EOF);
+ if (ferror(fp))
+ eprintf("%s: read error:", fname);
+}
diff --git a/sbase/sync.1 b/sbase/sync.1
@@ -0,0 +1,14 @@
+.TH SYNC 1 sbase\-VERSION
+.SH NAME
+sync \- flush disk cache
+.SH SYNOPSIS
+.B sync
+.SH DESCRIPTION
+.B sync
+invokes
+.IR sync(2)
+to flush all unwritten changes to the disk. This is
+usually done before shutting down, rebooting or halting.
+
+.SH SEE ALSO
+.IR sync (2) fsync (2)
diff --git a/sbase/sync.c b/sbase/sync.c
@@ -0,0 +1,22 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: sync\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ if (argc != 1)
+ usage();
+ sync();
+
+ return 0;
+}
diff --git a/sbase/tail.1 b/sbase/tail.1
@@ -0,0 +1,20 @@
+.TH TAIL 1 sbase\-VERSION
+.SH NAME
+tail \- output last part of a file
+.SH SYNOPSIS
+.B tail
+.RB [ \-n
+.IR lines ]
+.RI [ file ]
+.SH DESCRIPTION
+.B tail
+writes the last 10 lines of the file to stdout. If no file is given, tail reads
+from stdin.
+.SH OPTIONS
+.TP
+.BI \-n " lines"
+outputs the given number of lines. If
+.I lines
+begins with '+' it is used as an offset from the beginning of the file.
+.SH SEE ALSO
+.IR head (1)
diff --git a/sbase/tail.c b/sbase/tail.c
@@ -0,0 +1,101 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "text.h"
+#include "util.h"
+
+static void dropinit(FILE *, const char *, long);
+static void taketail(FILE *, const char *, long);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-n lines] [file]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ long n = 10;
+ FILE *fp;
+ void (*tail)(FILE *, const char *, long) = taketail;
+ char *lines;
+ int ret = 0;
+ int newline, many;
+
+ ARGBEGIN {
+ case 'n':
+ lines = EARGF(usage());
+ n = abs(estrtol(lines, 0));
+ if (lines[0] == '+')
+ tail = dropinit;
+ break;
+ ARGNUM:
+ n = ARGNUMF(0);
+ break;
+ default:
+ usage();
+ } ARGEND;
+ if (argc == 0) {
+ tail(stdin, "<stdin>", n);
+ } else {
+ many = argc > 1;
+ for (newline = 0; argc > 0; argc--, argv++) {
+ if (!(fp = fopen(argv[0], "r"))) {
+ weprintf("fopen %s:", argv[0]);
+ ret = 1;
+ continue;
+ }
+ if (many)
+ printf("%s==> %s <==\n",
+ newline ? "\n" : "", argv[0]);
+ newline = 1;
+ tail(fp, argv[0], n);
+ fclose(fp);
+ }
+ }
+ return ret;
+}
+
+static void
+dropinit(FILE *fp, const char *str, long n)
+{
+ char *buf = NULL;
+ size_t size = 0;
+ ssize_t len;
+ unsigned long i = 0;
+
+ while (i < n && ((len = getline(&buf, &size, fp)) != -1))
+ if (len && buf[len - 1] == '\n')
+ i++;
+ free(buf);
+ concat(fp, str, stdout, "<stdout>");
+}
+
+static void
+taketail(FILE *fp, const char *str, long n)
+{
+ char **ring = NULL;
+ long i, j;
+ size_t *size = NULL;
+
+ ring = ecalloc(n, sizeof *ring);
+ size = ecalloc(n, sizeof *size);
+
+ for (i = j = 0; getline(&ring[i], &size[i], fp) != -1; i = j = (i + 1) % n)
+ ;
+ if (ferror(fp))
+ eprintf("%s: read error:", str);
+
+ do {
+ if (ring[j]) {
+ fputs(ring[j], stdout);
+ free(ring[j]);
+ }
+ } while ((j = (j+1)%n) != i);
+ free(ring);
+ free(size);
+}
diff --git a/sbase/tar.1 b/sbase/tar.1
@@ -0,0 +1,62 @@
+.TH TAR 1 sbase\-VERSION
+.SH NAME
+tar \- create, list or extract a tape archive
+.SH SYNOPSIS
+.B tar
+.RB [ \-f
+.IR tarfile]
+.RB [ \-C
+.IR dir ]
+.RB [ - ] x | t
+
+.B tar
+.RB [ \-f
+.IR tarfile]
+.RB [ \-C
+.IR dir ]
+.RB [ - ] c
+.I dir
+
+.B tar
+.RB [ \-C
+.IR dir ]
+.B cf
+.I tarfile
+.I dir
+
+.B tar
+.RB [ \-C
+.IR dir ]
+.B x|tf
+.I tarfile
+
+.SH DESCRIPTION
+.B tar
+is the standard file archiver. Generally the archives
+created with it are further compressed.
+.SH OPTIONS
+.TP
+.B x
+extract tarball from stdin
+.TP
+.B t
+list all files in tarball from stdin
+.TP
+.BI c\ path
+creates tarball from
+.I path
+and prints it to stdout
+.TP
+.BI f\ tarfile
+Make
+.I tarfile
+be the archive, rather than stdin or stdout.
+.TP
+.BI C\ dir
+Change dierctory to
+.I dir
+before beginning.
+.SH SEE ALSO
+.IR ar (1)
+.IR gzip (1)
+.IR bzip2 (1)
diff --git a/sbase/tar.c b/sbase/tar.c
@@ -0,0 +1,332 @@
+/* See LICENSE file for copyright and license details. */
+#include <grp.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "util.h"
+
+typedef struct Header Header;
+struct Header {
+ char name[100];
+ char mode[8];
+ char uid[8];
+ char gid[8];
+ char size[12];
+ char mtime[12];
+ char chksum[8];
+ char type;
+ char link[100];
+ char magic[6];
+ char version[2];
+ char uname[32];
+ char gname[32];
+ char major[8];
+ char minor[8];
+ char prefix[155];
+};
+
+enum {
+ Blksiz = 512
+};
+
+enum Type {
+ REG = '0', AREG = '\0', HARDLINK = '1', SYMLINK = '2', CHARDEV = '3',
+ BLOCKDEV = '4', DIRECTORY = '5', FIFO = '6'
+};
+
+static void putoctal(char *, unsigned, int);
+static int archive(const char *);
+static int unarchive(char *, int, char[Blksiz]);
+static int print(char *, int , char[Blksiz]);
+static void c(const char *);
+static void xt(int (*)(char*, int, char[Blksiz]));
+
+static FILE *tarfile;
+static ino_t tarinode;
+static dev_t tardev;
+
+static int mflag = 0;
+
+static void
+usage(void)
+{
+ eprintf("usage: tar [-f tarfile] [-C dir] [-]x[m]|t\n"
+ " tar [-f tarfile] [-C dir] [-]c dir\n"
+ " tar [-C dir] cf tarfile dir\n"
+ " tar [-C dir] x[m]|tf tarfile\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct stat st;
+ char *file = NULL, *dir = ".", *ap;
+ char mode = '\0';
+
+ ARGBEGIN {
+ case 'x':
+ case 'c':
+ case 't':
+ if (mode)
+ usage();
+ mode = ARGC();
+ break;
+ case 'C':
+ dir = EARGF(usage());
+ break;
+ case 'f':
+ file = EARGF(usage());
+ break;
+ case 'm':
+ mflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (!mode) {
+ if (argc < 1)
+ usage();
+
+ for (ap = argv[0]; *ap; ap++) {
+ switch (*ap) {
+ case 'x':
+ case 'c':
+ case 't':
+ if (mode)
+ usage();
+ mode = *ap;
+ break;
+ case 'f':
+ if (argc < 2)
+ usage();
+ argc--, argv++;
+ file = argv[0];
+ break;
+ case 'C':
+ if (argc < 2)
+ usage();
+ argc--, argv++;
+ dir = argv[0];
+ break;
+ case 'm':
+ mflag = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc--, argv++;
+ }
+
+ if (!mode || argc != (mode == 'c'))
+ usage();
+
+ if (file) {
+ tarfile = fopen(file, (mode == 'c') ? "wb" : "rb");
+ if (!tarfile)
+ eprintf("tar: open '%s':", file);
+ if (lstat(file, &st) < 0)
+ eprintf("tar: stat '%s':", file);
+ tarinode = st.st_ino;
+ tardev = st.st_dev;
+ } else {
+ tarfile = (mode == 'c') ? stdout : stdin;
+ }
+
+ chdir(dir);
+
+ if (mode == 'c') {
+ c(argv[0]);
+ } else {
+ xt((mode == 'x') ? unarchive : print);
+ }
+
+ return 0;
+}
+
+static void
+putoctal(char *dst, unsigned num, int n)
+{
+ snprintf(dst, n, "%.*o", n-1, num);
+}
+
+static int
+archive(const char* path)
+{
+ unsigned char b[Blksiz];
+ unsigned chksum;
+ int l, x;
+ Header *h = (void*)b;
+ FILE *f = NULL;
+ struct stat st;
+ struct passwd *pw;
+ struct group *gr;
+ mode_t mode;
+
+ lstat(path, &st);
+ if (st.st_ino == tarinode && st.st_dev == tardev) {
+ fprintf(stderr, "ignoring '%s'\n", path);
+ return 0;
+ }
+ pw = getpwuid(st.st_uid);
+ gr = getgrgid(st.st_gid);
+
+ memset(b, 0, sizeof b);
+ snprintf(h->name, sizeof h->name, "%s", path);
+ putoctal(h->mode, (unsigned)st.st_mode&0777, sizeof h->mode);
+ putoctal(h->uid, (unsigned)st.st_uid, sizeof h->uid);
+ putoctal(h->gid, (unsigned)st.st_gid, sizeof h->gid);
+ putoctal(h->size, 0, sizeof h->size);
+ putoctal(h->mtime, (unsigned)st.st_mtime, sizeof h->mtime);
+ memcpy(h->magic, "ustar", sizeof h->magic);
+ memcpy(h->version, "00", sizeof h->version);
+ snprintf(h->uname, sizeof h->uname, "%s", pw ? pw->pw_name : "");
+ snprintf(h->gname, sizeof h->gname, "%s", gr ? gr->gr_name : "");
+
+ mode = st.st_mode;
+ if (S_ISREG(mode)) {
+ h->type = REG;
+ putoctal(h->size, (unsigned)st.st_size, sizeof h->size);
+ f = fopen(path, "r");
+ } else if (S_ISDIR(mode)) {
+ h->type = DIRECTORY;
+ } else if (S_ISLNK(mode)) {
+ h->type = SYMLINK;
+ readlink(path, h->link, (sizeof h->link)-1);
+ } else if (S_ISCHR(mode) || S_ISBLK(mode)) {
+ h->type = S_ISCHR(mode) ? CHARDEV : BLOCKDEV;
+#if defined(major) && defined(minor)
+ putoctal(h->major, (unsigned)major(st.st_dev), sizeof h->major);
+ putoctal(h->minor, (unsigned)minor(st.st_dev), sizeof h->minor);
+#else
+ return 0;
+#endif
+ } else if (S_ISFIFO(mode)) {
+ h->type = FIFO;
+ }
+
+ memset(h->chksum, ' ', sizeof h->chksum);
+ for (x = 0, chksum = 0; x < sizeof *h; x++)
+ chksum += b[x];
+ putoctal(h->chksum, chksum, sizeof h->chksum);
+
+ fwrite(b, Blksiz, 1, tarfile);
+ if (!f)
+ return 0;
+ while ((l = fread(b, 1, Blksiz, f)) > 0) {
+ if (l < Blksiz)
+ memset(b+l, 0, Blksiz-l);
+ fwrite(b, Blksiz, 1, tarfile);
+ }
+ fclose(f);
+ return 0;
+}
+
+static int
+unarchive(char *fname, int l, char b[Blksiz])
+{
+ char lname[101];
+ FILE *f = NULL;
+ unsigned long mode, major, minor, type, mtime;
+ struct timeval times[2];
+ Header *h = (void*)b;
+
+ if (!mflag)
+ mtime = strtoul(h->mtime, 0, 8);
+ unlink(fname);
+ switch (h->type) {
+ case REG:
+ case AREG:
+ mode = strtoul(h->mode, 0, 8);
+ if (!(f = fopen(fname, "w")) || chmod(fname, mode))
+ perror(fname);
+ break;
+ case HARDLINK:
+ case SYMLINK:
+ snprintf(lname, sizeof lname, "%s", h->link);
+ if (!((h->type == HARDLINK) ? link : symlink)(lname, fname))
+ perror(fname);
+ break;
+ case DIRECTORY:
+ mode = strtoul(h->mode, 0, 8);
+ if (mkdir(fname, (mode_t)mode))
+ perror(fname);
+ break;
+ case CHARDEV:
+ case BLOCKDEV:
+#ifdef makedev
+ mode = strtoul(h->mode, 0, 8);
+ major = strtoul(h->major, 0, 8);
+ minor = strtoul(h->mode, 0, 8);
+ type = (h->type == CHARDEV) ? S_IFCHR : S_IFBLK;
+ if (mknod(fname, type | mode, makedev(major, minor)))
+ perror(fname);
+#endif
+ break;
+ case FIFO:
+ mode = strtoul(h->mode, 0, 8);
+ if (mknod(fname, S_IFIFO | mode, 0))
+ perror(fname);
+ break;
+ default:
+ fprintf(stderr, "usupported tarfiletype %c\n", h->type);
+ }
+ if (getuid() == 0 && chown(fname, strtoul(h->uid, 0, 8),
+ strtoul(h->gid, 0, 8)))
+ perror(fname);
+
+ for (; l > 0; l -= Blksiz) {
+ fread(b, Blksiz, 1, tarfile);
+ if (f)
+ fwrite(b, MIN(l, 512), 1, f);
+ }
+ if (f)
+ fclose(f);
+
+ if (!mflag) {
+ times[0].tv_sec = times[1].tv_sec = mtime;
+ times[0].tv_usec = times[1].tv_usec = 0;
+ if (utimes(fname, times))
+ perror(fname);
+ }
+ return 0;
+}
+
+static int
+print(char * fname, int l, char b[Blksiz])
+{
+ puts(fname);
+ for (; l > 0; l -= Blksiz)
+ fread(b, Blksiz, 1, tarfile);
+ return 0;
+}
+
+static void
+c(const char * path)
+{
+ archive(path);
+ recurse(path, c);
+}
+
+static void
+xt(int (*fn)(char*, int, char[Blksiz]))
+{
+ char b[Blksiz], fname[257], *s;
+ Header *h = (void*)b;
+
+ while (fread(b, Blksiz, 1, tarfile) && h->name[0] != '\0') {
+ s = fname;
+ if (h->prefix[0] != '\0')
+ s += sprintf(s, "%.*s/", (int)sizeof h->prefix, h->prefix);
+ sprintf(s, "%.*s", (int)sizeof h->name, h->name);
+ fn(fname, strtol(h->size, 0, 8), b);
+ }
+}
diff --git a/sbase/tee.1 b/sbase/tee.1
@@ -0,0 +1,14 @@
+.TH TEE 1 sbase\-VERSION
+.SH NAME
+tee \- duplicate stdin
+.SH SYNOPSIS
+.B tee
+.RB [ \-a ]
+.RI [ file ...]
+.SH DESCRIPTION
+.B tee
+writes from stdin to stdout, making copies in each file.
+.SH OPTIONS
+.TP
+.B \-a
+append to each file rather than overwriting.
diff --git a/sbase/tee.c b/sbase/tee.c
@@ -0,0 +1,49 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-a] [file...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int aflag = 0;
+ char buf[BUFSIZ];
+ int i, nfps;
+ size_t n;
+ FILE **fps = NULL;
+
+ ARGBEGIN {
+ case 'a':
+ aflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ nfps = argc + 1;
+ fps = ecalloc(nfps, sizeof *fps);
+
+ for (i = 0; argc > 0; argc--, argv++, i++)
+ if (!(fps[i] = fopen(*argv, aflag ? "a" : "w")))
+ eprintf("fopen %s:", *argv);
+ fps[i] = stdout;
+
+ while ((n = fread(buf, 1, sizeof buf, stdin)) > 0) {
+ for (i = 0; i < nfps; i++) {
+ if (fwrite(buf, 1, n, fps[i]) != n)
+ eprintf("%s: write error:", buf);
+ }
+ }
+ if (ferror(stdin))
+ eprintf("<stdin>: read error:");
+
+ return 0;
+}
diff --git a/sbase/test.1 b/sbase/test.1
@@ -0,0 +1,96 @@
+.TH TEST 1 sbase\-VERSION
+.SH NAME
+test \- check file types and compare values
+.SH SYNOPSIS
+.B test
+.RB EXPRESSION
+.SH DESCRIPTION
+.B Exit with the status determined by EXPRESSION.
+.SH OPTIONS
+.TP
+.B ! EXPRESSION
+invert EXPORESSION
+.TP
+.B \-b FILE
+FILE exists and is block special
+.TP
+.B \-c FILE
+FILE exists and is character special
+.TP
+.B \-d FILE
+FILE exists and is a directory
+.TP
+.B \-e FILE
+FILE exists
+.TP
+.B \-f FILE
+ILE exists and is a regular file
+.TP
+.B \-g FILE
+FILE exists and is set-group-ID
+.TP
+.B \-k FILE
+FILE exists and its sticky bit is set
+.TP
+.B \-h FILE
+FILE exists and is a symbolic link (same as -L)
+.TP
+.B \-L FILE
+FILE exists and is a symbolic link (same as -h)
+.TP
+.B \-n STRING
+the length of STRING is nonzero
+.TP
+.B \-p FILE
+FILE exists and is a named pipe
+.TP
+.B \-r FILE
+FILE exists and read permission is granted
+.TP
+.B \-S FILE
+FILE exists and is a socket
+.TP
+.B \-s FILE
+FILE exists and has a size greater than zero
+.TP
+.B \-t FD
+file descriptor FD is opened on a terminal
+.TP
+.B \-u FILE
+exists and its set-user-ID bit is set
+.TP
+.B \-w FILE
+FILE exists and write permission is granted
+.TP
+.B \-x FILE
+FILE exists and execute (or search) permission is granted
+.TP
+.B \-z STRING
+the length of STRING is zero
+.TP
+.B s1 = s2
+True if the strings s1 and s2 are identical
+.TP
+.B s1 != s2
+True if the strings s1 and s2 are not identical
+.TP
+.B s1
+True if s1 is not the null string
+.TP
+.B n1 -eq n2
+True if the integers n1 and n2 are equal
+.TP
+.B n1 -ne n2
+True if the integers n1 and n2 are not equal
+.TP
+.B n1 -gt n2
+True if the integer n1 is greater than the integer n2
+.TP
+.B n1 -ge n2
+True if the integer n1 is great than or equal to the integer n2
+.TP
+.B n1 -lt n2
+True if the integer n1 is less than the integer n2
+.TP
+.B n1 -le n2
+True if the integer n1 is less than or equal to the integer n2
+\ No newline at end of file
diff --git a/sbase/test.c b/sbase/test.c
@@ -0,0 +1,169 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+stoi(char *s, int *a)
+{
+ char *p;
+ errno = 0;
+ *a = strtol(s, &p, 0);
+ if (errno || !*s || *p)
+ enprintf(2, "bad integer %s\n", s);
+}
+
+static int unary_b(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISBLK (buf.st_mode); }
+static int unary_c(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISCHR (buf.st_mode); }
+static int unary_d(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISDIR (buf.st_mode); }
+static int unary_f(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISREG (buf.st_mode); }
+static int unary_g(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISGID & buf.st_mode ; }
+static int unary_h(char *s) { struct stat buf; if (lstat(s, &buf)) return 0; return S_ISLNK (buf.st_mode); }
+static int unary_p(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISFIFO (buf.st_mode); }
+static int unary_S(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISSOCK (buf.st_mode); }
+static int unary_s(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return buf.st_size ; }
+static int unary_u(char *s) { struct stat buf; if ( stat(s, &buf)) return 0; return S_ISUID & buf.st_mode ; }
+
+static int unary_n(char *s) { return strlen(s); }
+static int unary_z(char *s) { return !strlen(s); }
+
+static int unary_e(char *s) { return access(s, F_OK); }
+static int unary_r(char *s) { return access(s, R_OK); }
+static int unary_w(char *s) { return access(s, W_OK); }
+static int unary_x(char *s) { return access(s, X_OK); }
+
+static int unary_t(char *s) { int fd; stoi(s, &fd); return isatty(fd); }
+
+static int binary_se(char *s1, char *s2) { return strcmp(s1, s2) == 0; }
+static int binary_sn(char *s1, char *s2) { return strcmp(s1, s2) != 0; }
+
+static int binary_eq(char *s1, char *s2) { int a, b; stoi(s1, &a); stoi(s2, &b); return a == b; }
+static int binary_ne(char *s1, char *s2) { int a, b; stoi(s1, &a); stoi(s2, &b); return a != b; }
+static int binary_gt(char *s1, char *s2) { int a, b; stoi(s1, &a); stoi(s2, &b); return a > b; }
+static int binary_ge(char *s1, char *s2) { int a, b; stoi(s1, &a); stoi(s2, &b); return a >= b; }
+static int binary_lt(char *s1, char *s2) { int a, b; stoi(s1, &a); stoi(s2, &b); return a < b; }
+static int binary_le(char *s1, char *s2) { int a, b; stoi(s1, &a); stoi(s2, &b); return a <= b; }
+
+typedef struct {
+ char *name;
+ int (*func)();
+} Test;
+
+static Test unary[] = {
+ { "-b", unary_b },
+ { "-c", unary_c },
+ { "-d", unary_d },
+ { "-e", unary_e },
+ { "-f", unary_f },
+ { "-g", unary_g },
+ { "-h", unary_h },
+ { "-L", unary_h },
+ { "-n", unary_n },
+ { "-p", unary_p },
+ { "-r", unary_r },
+ { "-S", unary_S },
+ { "-s", unary_s },
+ { "-t", unary_t },
+ { "-u", unary_u },
+ { "-w", unary_w },
+ { "-x", unary_x },
+ { "-z", unary_z },
+
+ { NULL, NULL },
+};
+
+static Test binary[] = {
+ { "=" , binary_se },
+ { "!=" , binary_sn },
+ { "-eq", binary_eq },
+ { "-ne", binary_ne },
+ { "-gt", binary_gt },
+ { "-ge", binary_ge },
+ { "-lt", binary_lt },
+ { "-le", binary_le },
+
+ { NULL, NULL },
+};
+
+static Test *
+find_test(Test *tests, char *name)
+{
+ Test *t;
+
+ for (t = tests; t->name; ++t)
+ if (strcmp(t->name, name) == 0)
+ return t;
+ return NULL;
+}
+
+static int
+noarg(char **argv)
+{
+ return 0;
+}
+
+static int
+onearg(char **argv)
+{
+ return strlen(argv[0]);
+}
+
+static int
+twoarg(char **argv)
+{
+ Test *t = find_test(unary, *argv);
+
+ if (strcmp(argv[0], "!") == 0)
+ return !onearg(argv + 1);
+
+ if (t)
+ return t->func(argv[1]);
+
+ return enprintf(2, "bad unary test %s\n", argv[0]), 0;
+}
+
+static int
+threearg(char **argv)
+{
+ Test *t = find_test(binary, argv[1]);
+
+ if (t)
+ return t->func(argv[0], argv[2]);
+
+ if (strcmp(argv[0], "!") == 0)
+ return !twoarg(argv + 1);
+
+ return enprintf(2, "bad binary test %s\n", argv[1]), 0;
+}
+
+static int
+fourarg(char **argv)
+{
+ if (strcmp(argv[0], "!") == 0)
+ return !threearg(argv + 1);
+
+ return enprintf(2, "too many arguments\n"), 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ int (*narg[])(char**) = { noarg, onearg, twoarg, threearg, fourarg };
+ int len = strlen(argv[0]);
+
+ if (len && argv[0][len - 1] == '[')
+ if (strcmp(argv[--argc], "]") != 0)
+ enprintf(2, "no matching ]\n");
+
+ --argc; ++argv;
+
+ if (argc > 4)
+ enprintf(2, "too many arguments\n");
+
+ return !narg[argc](argv);
+}
diff --git a/sbase/text.h b/sbase/text.h
@@ -0,0 +1,11 @@
+/* See LICENSE file for copyright and license details. */
+
+struct linebuf {
+ char **lines;
+ long nlines;
+ long capacity;
+};
+#define EMPTY_LINEBUF {NULL, 0, 0,}
+void getlines(FILE *, struct linebuf *);
+
+void concat(FILE *, const char *, FILE *, const char *);
diff --git a/sbase/touch.1 b/sbase/touch.1
@@ -0,0 +1,32 @@
+.Dd January 20, 2014
+.Dt TOUCH 1 sbase\-VERSION
+.Sh NAME
+.Nm touch
+.Nd set file timestamps
+.Sh SYNOPSIS
+.Nm touch
+.Op Fl acm
+.Op Fl t Ar stamp
+.Ar file ...
+.Sh DESCRIPTION
+.Nm
+sets the access and modification times of files to the current time of day. If the file
+doesn't exist, it is created with the default permissions.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl a
+Set the access time of the file.
+.It Fl c
+Do not create the file it it does not exist. The exit
+status is not affected.
+.It Fl m
+Change the modification time of the file.
+.It Fl t Ar stamp
+Set the timestamp to be used with
+.Op Fl am .
+The format of the timestamp is simply the number of seconds
+since Jan 1, 1970. This specification of time does not conform
+to POSIX.
+.El
+.Sh SEE ALSO
+.Xr date 1
diff --git a/sbase/touch.c b/sbase/touch.c
@@ -0,0 +1,80 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <utime.h>
+
+#include "util.h"
+
+static int aflag;
+static int cflag;
+static int mflag;
+static time_t t;
+
+static void
+touch(const char *file)
+{
+ int fd;
+ struct stat st;
+ struct utimbuf ut;
+ int r;
+
+ if ((r = stat(file, &st)) < 0) {
+ if (errno != ENOENT)
+ eprintf("stat %s:", file);
+ if (cflag)
+ return;
+ } else if (r == 0) {
+ ut.actime = aflag ? t : st.st_atime;
+ ut.modtime = mflag ? t : st.st_mtime;
+ if (utime(file, &ut) < 0)
+ eprintf("utime %s:", file);
+ return;
+ }
+
+ if ((fd = open(file, O_CREAT | O_EXCL, 0644)) < 0)
+ eprintf("open %s:", file);
+ close(fd);
+
+ touch(file);
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-acm] [-t stamp] file ...\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ t = time(NULL);
+
+ ARGBEGIN {
+ case 'a':
+ aflag = 1;
+ break;
+ case 'c':
+ cflag = 1;
+ break;
+ case 'm':
+ mflag = 1;
+ break;
+ case 't':
+ t = estrtol(EARGF(usage()), 0);
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ for (; argc > 0; argc--, argv++)
+ touch(argv[0]);
+
+ return 0;
+}
diff --git a/sbase/tr.1 b/sbase/tr.1
@@ -0,0 +1,76 @@
+.Dd January 16, 2015
+.Dt TR 1 sbase\-VERSION
+.Sh NAME
+.Nm tr
+.Nd translate characters
+.Sh SYNOPSIS
+.Nm tr
+.Op Fl c | Fl C
+.Op Fl sd
+.Ar set1 set2
+.Sh DESCRIPTION
+.Nm
+matches characters from stdin and performs translations to stdout.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl c | Fl C
+Match to
+.Ar set1
+complement.
+.It Fl d
+Delete characters matching
+.Ar set1 .
+.It Fl s
+Squeeze repeated characters matching
+.Ar set1
+or
+.Ar set2
+if -d is set.
+.El
+.Sh SET
+.Bl -tag -width Ds
+.It Literal 'c'
+.It Escape sequence '\ec'
+\e\e, \ea, \eb, \ef, \en, \er, \et, \ev
+.It Range 'c-d'
+.It Repeat '[c*n]'
+Only in
+.Ar set2 .
+If n = 0 or left out, set n to length of
+.Ar set1 .
+.It Character class '[:class:]'
+See
+.Xr wctype 3 .
+.El
+.Sh TRANSLATION
+If no options are specified,
+.Nm
+translates from
+.Ar set1
+to
+.Ar set2
+by index or character class.
+.Pp
+If
+.Ar set2
+is shorter than
+.Ar set1 ,
+overflowing characters translate to the last character in
+.Ar set2 .
+.Sh EXIT STATUS
+.Bl -tag -width Ds
+.It 0
+Input processed successfully.
+.It 1
+An error occurred.
+.El
+.Sh SEE ALSO
+.Xr sed 1 ,
+.Xr awk 1 ,
+.Xr utf8 7
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+except from octal sequences and equivalence classes.
diff --git a/sbase/tr.c b/sbase/tr.c
@@ -0,0 +1,289 @@
+/* See LICENSE file for copyright and license details. */
+#include <wctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utf.h"
+#include "util.h"
+
+static int cflag = 0;
+static int dflag = 0;
+static int sflag = 0;
+
+struct range {
+ Rune start;
+ Rune end;
+ size_t quant;
+};
+
+static struct {
+ char *name;
+ int (*check)(wint_t);
+} classes[] = {
+ { "alnum", iswalnum },
+ { "alpha", iswalpha },
+ { "blank", iswblank },
+ { "cntrl", iswcntrl },
+ { "digit", iswdigit },
+ { "graph", iswgraph },
+ { "lower", iswlower },
+ { "print", iswprint },
+ { "punct", iswpunct },
+ { "space", iswspace },
+ { "upper", iswupper },
+ { "xdigit", iswxdigit },
+};
+
+static struct range *set1 = NULL;
+static size_t set1ranges = 0;
+static int (*set1check)(wint_t) = NULL;
+static struct range *set2 = NULL;
+static size_t set2ranges = 0;
+static int (*set2check)(wint_t) = NULL;
+
+
+static size_t
+rangelen(struct range r)
+{
+ return (r.end - r.start + 1) * r.quant;
+}
+
+static size_t
+setlen(struct range *set, size_t setranges)
+{
+ size_t len = 0, i;
+
+ for (i = 0; i < setranges; i++)
+ len += rangelen(set[i]);
+
+ return len;
+}
+
+static int
+rstrmatch(Rune *r, char *s, size_t n)
+{
+ size_t i;
+
+ for (i = 0; i < n; i++)
+ if (r[i] != s[i])
+ return 0;
+ return 1;
+}
+
+static size_t
+resolveescapes(Rune *r, size_t len)
+{
+ size_t i, off, m;
+
+ for (i = 0; i < len - 1; i++) {
+ if (r[i] != '\\')
+ continue;
+ off = 0;
+
+ switch (r[i + 1]) {
+ case '\\': r[i] = '\\'; off++; break;
+ case 'a': r[i] = '\a'; off++; break;
+ case 'b': r[i] = '\b'; off++; break;
+ case 'f': r[i] = '\f'; off++; break;
+ case 'n': r[i] = '\n'; off++; break;
+ case 'r': r[i] = '\r'; off++; break;
+ case 't': r[i] = '\t'; off++; break;
+ case 'v': r[i] = '\v'; off++; break;
+ default: continue;
+ }
+
+ for (m = i + 1; m <= len - off; m++)
+ r[m] = r[m + off];
+ len -= off;
+ }
+
+ return len;
+}
+
+static size_t
+makeset(char *str, struct range **set, int (**check)(wint_t))
+{
+ Rune *rstr;
+ size_t len, i, j, m, n;
+ size_t q, setranges = 0;
+ int factor, base;
+
+ /* rstr defines at most len ranges */
+ len = chartorunearr(str, &rstr);
+ len = resolveescapes(rstr, len);
+ *set = emalloc(len * sizeof(**set));
+
+ for (i = 0; i < len; i++) {
+ if (rstr[i] == '[') {
+ j = i;
+nextbrack:
+ if (j == len)
+ goto literal;
+ for (m = j; m < len; m++)
+ if (rstr[m] == ']') {
+ j = m;
+ break;
+ }
+ if (j == i)
+ goto literal;
+
+ /* CLASSES [=EQUIV=] (skip) */
+ if (j - i > 3 && rstr[i + 1] == '=' && rstr[m - 1] == '=') {
+ i = j;
+ continue;
+ }
+
+ /* CLASSES [:CLASS:] */
+ if (j - i > 3 && rstr[i + 1] == ':' && rstr[m - 1] == ':') {
+ for (n = 0; n < LEN(classes); n++) {
+ if (rstrmatch(rstr + i + 2, classes[n].name, j - i - 3)) {
+ *check = classes[n].check;
+ return 0;
+ }
+ }
+ eprintf("Invalid character class.\n");
+ }
+
+ /* REPEAT [_*n] (only allowed in set2) */
+ if (j - i > 2 && rstr[i + 2] == '*' && set1ranges > 0) {
+ /* check if right side of '*' is a number */
+ q = 0;
+ factor = 1;
+ base = (rstr[i + 3] == '0') ? 8 : 10;
+ for (n = j - 1; n > i + 2; n--) {
+ if (rstr[n] < '0' && rstr[n] > '9') {
+ n = 0;
+ break;
+ }
+ q += (rstr[n] - '0') * factor;
+ factor *= base;
+ }
+
+ if (n == 0) {
+ j = m + 1;
+ goto nextbrack;
+ }
+ (*set)[setranges].start = rstr[i + 1];
+ (*set)[setranges].end = rstr[i + 1];
+ (*set)[setranges].quant = q ? q : setlen(set1, set1ranges);
+ setranges++;
+ i = j;
+ continue;
+ }
+
+ j = m + 1;
+ goto nextbrack;
+ }
+literal:
+ /* RANGES [_-__-_], _-__-_ */
+ /* LITERALS _______ */
+ (*set)[setranges].start = rstr[i];
+
+ if (i < len - 2 && rstr[i + 1] == '-' && rstr[i + 2] >= rstr[i])
+ i += 2;
+ (*set)[setranges].end = rstr[i];
+ (*set)[setranges].quant = 1;
+ setranges++;
+ }
+
+ free(rstr);
+ return setranges;
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-cCds] set1 [set2]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ Rune r = 0, lastrune = 0;
+ size_t off1, off2, i, m;
+
+ ARGBEGIN {
+ case 'c':
+ case 'C':
+ cflag = 1;
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1 || argc > 2 || (argc == 1 && dflag == sflag))
+ usage();
+ set1ranges = makeset(argv[0], &set1, &set1check);
+ if (argc == 2)
+ set2ranges = makeset(argv[1], &set2, &set2check);
+ if (dflag == sflag && !set2ranges && !set2check)
+ eprintf("set2 must be non-empty.\n");
+ if (set2check && set2check != iswlower && set2check != iswupper)
+ eprintf("set2 can only be the 'lower' or 'upper' class.\n");
+read:
+ if (!readrune("<stdin>", stdin, &r))
+ return 0;
+ off1 = off2 = 0;
+ for (i = 0; i < set1ranges; i++) {
+ if (set1[i].start <= r && r <= set1[i].end) {
+ if (dflag && !cflag)
+ goto read;
+ if (sflag) {
+ if (r == lastrune)
+ goto read;
+ else
+ goto write;
+ }
+ for (m = 0; m < i; m++)
+ off1 += rangelen(set1[m]);
+ off1 += r - set1[m].start;
+ if (off1 > setlen(set2, set2ranges) - 1) {
+ r = set2[set2ranges - 1].end;
+ goto write;
+ }
+ for (m = 0; m < set2ranges; m++) {
+ if (off2 + rangelen(set2[m]) > off1) {
+ m++;
+ break;
+ }
+ off2 += rangelen(set2[m]);
+ }
+ m--;
+ r = set2[m].start + (off1 - off2) / set2[m].quant;
+
+ goto write;
+ }
+ }
+ if (set1check && set1check((wint_t)r)) {
+ if (dflag && !cflag)
+ goto read;
+ if (sflag) {
+ if (r == lastrune)
+ goto read;
+ else
+ goto write;
+ }
+ if (set1check == iswupper && set2check == iswlower)
+ r = towlower((wint_t)r);
+ else if (set1check == iswlower && set2check == iswupper)
+ r = towupper((wint_t)r);
+ else if (set2ranges > 0)
+ r = set2[set2ranges - 1].end;
+ else
+ eprintf("Misaligned character classes.\n");
+ }
+ if (dflag && cflag)
+ goto read;
+ if (dflag && sflag && r == lastrune)
+ goto read;
+write:
+ lastrune = r;
+ writerune("<stdout>", stdout, &r);
+ goto read;
+}
diff --git a/sbase/true.1 b/sbase/true.1
@@ -0,0 +1,16 @@
+.Dd January 16, 2015
+.Dt TRUE 1 sbase\-VERSION
+.Sh NAME
+.Nm true
+.Nd return success
+.Sh SYNOPSIS
+.Nm true
+.Sh DESCRIPTION
+.Nm
+returns a status code indicating success
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
diff --git a/sbase/true.c b/sbase/true.c
@@ -0,0 +1,6 @@
+/* See LICENSE file for copyright and license details. */
+int
+main(void)
+{
+ return 0;
+}
diff --git a/sbase/tty.1 b/sbase/tty.1
@@ -0,0 +1,13 @@
+.TH TTY 1 sbase\-VERSION
+.SH NAME
+tty \- print terminal name
+.SH SYNOPSIS
+.B tty
+.SH DESCRIPTION
+.B tty
+prints the name of the terminal open on stdin.
+.P
+The status code is 0 if stdin is a terminal, and 1 if not. If an error occurred
+the status code is 2.
+.SH SEE ALSO
+.IR ttyname (3)
diff --git a/sbase/tty.c b/sbase/tty.c
@@ -0,0 +1,27 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *tty;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ tty = ttyname(STDIN_FILENO);
+ puts(tty ? tty : "not a tty");
+ return tty ? 0 : 1;
+}
diff --git a/sbase/uname.1 b/sbase/uname.1
@@ -0,0 +1,32 @@
+.TH UNAME 1 sbase\-VERSION
+.SH NAME
+uname \- print system information
+.SH SYNOPSIS
+.B uname
+.RB [ \-amnrsv ]
+.SH DESCRIPTION
+.B uname
+prints system information. If no flags are given, uname will print only the
+name of the operating system
+.RB ( \-s ).
+.SH OPTIONS
+.TP
+.B \-a
+print all the information below.
+.TP
+.B \-m
+print the machine's architecture.
+.TP
+.B \-n
+print the system's network name.
+.TP
+.B \-r
+print the operating system's release name.
+.TP
+.B \-s
+print the name of the operating system.
+.TP
+.B \-v
+print the operating system's version name.
+.SH SEE ALSO
+.IR uname (2)
diff --git a/sbase/uname.c b/sbase/uname.c
@@ -0,0 +1,63 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-amnrsv]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int mflag = 0;
+ int nflag = 0;
+ int rflag = 0;
+ int sflag = 0;
+ int vflag = 0;
+ struct utsname u;
+
+ ARGBEGIN {
+ case 'a':
+ mflag = nflag = rflag = sflag = vflag = 1;
+ break;
+ case 'm':
+ mflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+ if (uname(&u) < 0)
+ eprintf("uname:");
+
+ if (sflag || !(nflag || rflag || vflag || mflag))
+ putword(u.sysname);
+ if (nflag)
+ putword(u.nodename);
+ if (rflag)
+ putword(u.release);
+ if (vflag)
+ putword(u.version);
+ if (mflag)
+ putword(u.machine);
+ putchar('\n');
+
+ return 0;
+}
diff --git a/sbase/unexpand.1 b/sbase/unexpand.1
@@ -0,0 +1,25 @@
+.TH EXPAND 1 sbase\-VERSION
+.SH NAME
+unexpand \- convert blanks to tabs
+.SH SYNOPSIS
+.B unexpand
+.RB [ \-a ]
+.RB [ \-t
+.IR n ]
+.RI [ file ...]
+.SH DESCRIPTION
+unexpand processes the named files or the standard input, writing the
+standard output with consecutive blanks (spaces and tabs) converted
+into tabs. Backspace characters are preserved into the output and
+decrement the column count for tab calculations.
+.SH OPTIONS
+.TP
+.BI \-a
+convert blanks to tabs everywhere, not just at the start of lines
+.TP
+.BI \-t " n"
+set tab size to
+.I n
+spaces (default: 8)
+.SH SEE ALSO
+.IR expand (1)
diff --git a/sbase/unexpand.c b/sbase/unexpand.c
@@ -0,0 +1,119 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+#include "utf.h"
+#include "util.h"
+
+static void unexpand(const char *, FILE *);
+
+static int aflag = 0;
+static int tabsize = 8;
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-a] [-t n] [file ...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+ int ret = 0;
+
+ ARGBEGIN {
+ case 't':
+ tabsize = estrtol(EARGF(usage()), 0);
+ if (tabsize <= 0)
+ eprintf("unexpand: invalid tabsize\n");
+ /* Fallthrough: -t implies -a */
+ case 'a':
+ aflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0) {
+ unexpand("<stdin>", stdin);
+ } else {
+ for (; argc > 0; argc--, argv++) {
+ if (!(fp = fopen(argv[0], "r"))) {
+ weprintf("fopen %s:", argv[0]);
+ ret = 1;
+ continue;
+ }
+ unexpand(argv[0], fp);
+ fclose(fp);
+ }
+ }
+ return ret;
+}
+
+static void
+unexpandspan(unsigned int n, unsigned int col)
+{
+ unsigned int off = (col-n) % tabsize;
+ Rune r;
+
+ if (n + off >= tabsize && n > 1)
+ n += off;
+
+ r = '\t';
+ for (; n >= tabsize; n -= tabsize)
+ writerune("<stdout>", stdout, &r);
+ r = ' ';
+ while (n--)
+ writerune("<stdout>", stdout, &r);
+}
+
+static void
+unexpand(const char *file, FILE *fp)
+{
+ unsigned int n = 0, col = 0;
+ Rune r;
+ int bol = 1;
+
+ while (1) {
+ if (!readrune(file, fp, &r))
+ break;
+
+ switch (r) {
+ case ' ':
+ if (bol || aflag)
+ n++;
+ col++;
+ break;
+ case '\t':
+ if (bol || aflag)
+ n += tabsize - col % tabsize;
+ col += tabsize - col % tabsize;
+ break;
+ case '\b':
+ if (bol || aflag)
+ unexpandspan(n, col);
+ col -= (col > 0);
+ n = 0;
+ bol = 0;
+ break;
+ case '\n':
+ if (bol || aflag)
+ unexpandspan(n, col);
+ n = col = 0;
+ bol = 1;
+ break;
+ default:
+ if (bol || aflag)
+ unexpandspan(n, col);
+ n = 0;
+ col++;
+ bol = 0;
+ }
+ if ((r != ' ' && r != '\t') || (!aflag && !bol))
+ writerune("<stdout>", stdout, &r);
+ }
+ if (n > 0 && (bol || aflag))
+ unexpandspan(n, col);
+}
diff --git a/sbase/uniq.1 b/sbase/uniq.1
@@ -0,0 +1,39 @@
+.TH UNIQ 1 sbase\-VERSION
+.SH NAME
+uniq \- multi-column
+.SH SYNOPSIS
+.B uniq
+.RB [ \-cdu ]
+.RI [ file ]
+.SH DESCRIPTION
+.B uniq
+reads file and writes one copy of a line
+from each group of consecutive duplicate lines
+to stdout.
+If no file is given, uniq reads from stdin.
+.SH OPTIONS
+.TP
+.B \-c
+prefixes each line with a count
+of its consecutive occurrences in the input.
+.TP
+.B \-d
+suppresses non-duplicate lines
+(thus 'uniq -d' prints only duplicates).
+.TP
+.B \-u
+suppresses non-unique lines
+(thus 'uniq -u' prints only uniques).
+.SH BUGS
+The original sbase implementation of
+.B uniq
+supported multiple input-file arguments,
+as e.g. cat and grep do.
+Unfortunately, POSIX uniq treats its second argument (if present)
+as an output filename and clobbers it.
+Since users and scripts which rely on uniq
+supporting multiple input-file arguments
+would be at risk of data loss
+if they ever ran into a POSIX-compatible uniq,
+support for multiple input-file arguments
+was removed from this implementation.
diff --git a/sbase/uniq.c b/sbase/uniq.c
@@ -0,0 +1,103 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "text.h"
+#include "util.h"
+
+static void uniqline(char *);
+static void uniq(FILE *, const char *);
+static void uniqfinish(void);
+
+static const char *countfmt = "";
+static int dflag = 0;
+static int uflag = 0;
+
+static char *prevline = NULL;
+static long prevlinecount = 0;
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-cdiu] [input]]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+
+ ARGBEGIN {
+ case 'i':
+ eprintf("not implemented\n");
+ case 'c':
+ countfmt = "%7ld ";
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'u':
+ uflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0) {
+ uniq(stdin, "<stdin>");
+ } else if (argc == 1) {
+ if (!(fp = fopen(argv[0], "r")))
+ eprintf("fopen %s:", argv[0]);
+ uniq(fp, argv[0]);
+ fclose(fp);
+ } else
+ usage();
+ uniqfinish();
+
+ return 0;
+}
+
+static void
+uniqline(char *l)
+{
+ int linesequel = (!l || !prevline)
+ ? l == prevline
+ : !strcmp(l, prevline);
+
+ if (linesequel) {
+ ++prevlinecount;
+ return;
+ }
+
+ if (prevline) {
+ if ((prevlinecount == 1 && !dflag) ||
+ (prevlinecount != 1 && !uflag)) {
+ printf(countfmt, prevlinecount);
+ fputs(prevline, stdout);
+ }
+ free(prevline);
+ prevline = NULL;
+ }
+
+ if (l)
+ prevline = estrdup(l);
+ prevlinecount = 1;
+}
+
+static void
+uniq(FILE *fp, const char *str)
+{
+ char *buf = NULL;
+ size_t size = 0;
+
+ while (getline(&buf, &size, fp) != -1)
+ uniqline(buf);
+}
+
+static void
+uniqfinish(void)
+{
+ uniqline(NULL);
+}
diff --git a/sbase/unlink.1 b/sbase/unlink.1
@@ -0,0 +1,16 @@
+.TH UNLINK 1 sbase\-VERSION
+.SH NAME
+unlink \- call the unlink function
+.SH SYNOPSIS
+.B unlink
+.RB file
+.SH DESCRIPTION
+.B unlink
+calls the
+.IR unlink
+function on
+.IR file.
+
+
+.SH SEE ALSO
+.IR unlink (2)
diff --git a/sbase/unlink.c b/sbase/unlink.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: unlink file\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ if (argc != 2)
+ usage();
+
+ if (unlink(argv[1]) < 0)
+ eprintf("unlink: '%s':", argv[1]);
+
+ return 0;
+}
diff --git a/sbase/utf.h b/sbase/utf.h
@@ -0,0 +1,54 @@
+/* MIT/X Consortium Copyright (c) 2012 Connor Lane Smith <cls@lubutu.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#include <stdio.h>
+
+typedef int Rune;
+
+enum {
+ UTFmax = 6, /* maximum bytes per rune */
+ Runeself = 0x80, /* rune and utf are equal (<) */
+ Runeerror = 0xFFFD, /* decoding error in utf */
+ Runemax = 0x10FFFF /* maximum rune value */
+};
+
+int runetochar(char *, const Rune *);
+int chartorune(Rune *, const char *);
+int charntorune(Rune *, const char *, size_t);
+int runelen(const Rune);
+size_t runenlen(const Rune *, size_t);
+int fullrune(const char *, size_t);
+char *utfecpy(char *, char *, const char *);
+size_t utflen(const char *);
+size_t utfnlen(const char *, size_t);
+char *utfrune(const char *, Rune);
+char *utfrrune(const char *, Rune);
+char *utfutf(const char *, const char *);
+
+int isalpharune(Rune);
+int islowerrune(Rune);
+int isspacerune(Rune);
+int istitlerune(Rune);
+int isupperrune(Rune);
+int isdigitrune(Rune);
+
+int readrune(const char *, FILE *, Rune *);
+void writerune(const char *, FILE *, Rune *);
+int chartorunearr(const char*, Rune **);
diff --git a/sbase/util.h b/sbase/util.h
@@ -0,0 +1,55 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/types.h>
+
+#include <regex.h>
+#include <stddef.h>
+
+#include "arg.h"
+#include "compat.h"
+
+#define UTF8_POINT(c) (((c) & 0xc0) != 0x80)
+
+#undef MIN
+#define MIN(x,y) ((x) < (y) ? (x) : (y))
+#undef MAX
+#define MAX(x,y) ((x) > (y) ? (x) : (y))
+
+#define LEN(x) (sizeof (x) / sizeof *(x))
+
+extern char *argv0;
+
+char *agetcwd(void);
+void apathmax(char **, long *);
+
+void *ecalloc(size_t, size_t);
+void *emalloc(size_t size);
+void *erealloc(void *, size_t);
+char *estrdup(const char *);
+
+void enprintf(int, const char *, ...);
+void eprintf(const char *, ...);
+void weprintf(const char *, ...);
+
+double estrtod(const char *);
+long estrtol(const char *, int);
+
+#undef strcasestr
+char *strcasestr(const char *, const char *);
+
+#undef strlcat
+size_t strlcat(char *, const char *, size_t);
+#undef strlcpy
+size_t strlcpy(char *, const char *, size_t);
+
+/* regex */
+int enregcomp(int, regex_t *, const char *, int);
+int eregcomp(regex_t *, const char *, int);
+
+/* misc */
+void enmasse(int, char **, int (*)(const char *, const char *));
+void fnck(const char *, const char *, int (*)(const char *, const char *));
+mode_t getumask(void);
+char *humansize(double);
+mode_t parsemode(const char *, mode_t, mode_t);
+void putword(const char *);
+void recurse(const char *, void (*)(const char *));
diff --git a/sbase/uudecode.1 b/sbase/uudecode.1
@@ -0,0 +1,19 @@
+.TH UUDECODE 1 sbase\-VERSION
+.SH NAME
+uudecode \- decode a uuencoded file
+.SH SYNOPSIS
+.B uudecode
+.RI [file]
+.SH DESCRIPTION
+.B uudecode
+reads file (or by default, the standard input) and writes a decoded
+version to the file specified in the uuencoded header. In case that
+the file already exists, it is truncated. Otherwise a new file is
+created. After the operation the permissions of the created/accessed
+are changed to reflect the mode in the header.
+.SH NOTES
+This version of uudecode does not currently support the base64
+encoding algorithm.
+For safety currently uudecode operates only on regular files and
+stdout. Trying to uudecode to a link, directory, or special file
+yields an error.
diff --git a/sbase/uudecode.c b/sbase/uudecode.c
@@ -0,0 +1,179 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "text.h"
+#include "util.h"
+
+static void uudecode(FILE *, FILE *);
+static void parseheader(FILE *, const char *, const char *, mode_t *, char **);
+static FILE *parsefile(const char *);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [file]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp = NULL, *nfp = NULL;
+ char *fname;
+ mode_t mode = 0;
+
+ ARGBEGIN {
+ case 'm':
+ eprintf("-m not implemented\n");
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc > 1)
+ usage();
+
+ if (argc == 0) {
+ parseheader(stdin, "<stdin>", "begin ", &mode, &fname);
+ if (!(nfp = parsefile(fname)))
+ eprintf("fopen %s:", fname);
+ uudecode(stdin, nfp);
+ } else {
+ if (!(fp = fopen(argv[0], "r")))
+ eprintf("fopen %s:", argv[0]);
+ parseheader(fp, argv[0], "begin ", &mode, &fname);
+ if (!(nfp = parsefile(fname)))
+ eprintf("fopen %s:", fname);
+ uudecode(fp, nfp);
+ }
+
+ if (chmod(fname, mode) < 0)
+ eprintf("chmod %s:", fname);
+ if (fp)
+ fclose(fp);
+ if (nfp)
+ fclose(nfp);
+
+ return 0;
+}
+
+static FILE *
+parsefile(const char *fname)
+{
+ struct stat st;
+ int ret;
+
+ if (strcmp(fname, "/dev/stdout") == 0)
+ return stdout;
+ ret = lstat(fname, &st);
+ /* if it is a new file, try to open it */
+ if (ret < 0 && errno == ENOENT)
+ goto tropen;
+ if (ret < 0) {
+ weprintf("lstat %s:", fname);
+ return NULL;
+ }
+ if (!S_ISREG(st.st_mode)) {
+ weprintf("for safety uudecode operates only on regular files and /dev/stdout\n");
+ return NULL;
+ }
+tropen:
+ return fopen(fname,"w");
+}
+
+static void
+parseheader(FILE *fp, const char *s, const char *header, mode_t *mode, char **fname)
+{
+ char bufs[PATH_MAX + 11]; /* len header + mode + maxname */
+ char *p, *q;
+ size_t n;
+
+ if (!fgets(bufs, sizeof(bufs), fp))
+ if (ferror(fp))
+ eprintf("%s: read error:", s);
+ if (bufs[0] == '\0' || feof(fp))
+ eprintf("empty or nil header string\n");
+ if (!(p = strchr(bufs, '\n')))
+ eprintf("header string too long or non-newline terminated file\n");
+ p = bufs;
+ if (strncmp(bufs, header, strlen(header)) != 0)
+ eprintf("malformed header prefix\n");
+ p += strlen(header);
+ if (!(q = strchr(p, ' ')))
+ eprintf("malformed mode string in header\n");
+ *q++ = '\0';
+ /* now mode should be null terminated, q points to fname */
+ *mode = parsemode(p, *mode, 0);
+ n = strlen(q);
+ while (n > 0 && (q[n - 1] == '\n' || q[n - 1] == '\r'))
+ q[--n] = '\0';
+ if (n > 0)
+ *fname = q;
+}
+
+static void
+uudecode(FILE *fp, FILE *outfp)
+{
+ char *bufb = NULL, *p;
+ size_t n = 0;
+ ssize_t len;
+ int ch, i;
+
+#define DEC(c) (((c) - ' ') & 077) /* single character decode */
+#define IS_DEC(c) ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) )
+#define OUT_OF_RANGE(c) eprintf("character %c out of range: [%d-%d]", (c), 1 + ' ', 077 + ' ' + 1)
+
+ while ((len = getline(&bufb, &n, fp)) != -1) {
+ p = bufb;
+ /* trim newlines */
+ if (len && bufb[len - 1] != '\n')
+ bufb[len - 1] = '\0';
+ else
+ eprintf("no newline found, aborting\n");
+ /* check for last line */
+ if ((i = DEC(*p)) <= 0)
+ break;
+ for (++p; i > 0; p += 4, i -= 3) {
+ if (i >= 3) {
+ if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) &&
+ IS_DEC(*(p + 2)) && IS_DEC(*(p + 3))))
+ OUT_OF_RANGE(*p);
+
+ ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
+ putc(ch, outfp);
+ ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
+ putc(ch, outfp);
+ ch = DEC(p[2]) << 6 | DEC(p[3]);
+ putc(ch, outfp);
+ } else {
+ if (i >= 1) {
+ if (!(IS_DEC(*p) && IS_DEC(*(p + 1))))
+ OUT_OF_RANGE(*p);
+
+ ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
+ putc(ch, outfp);
+ }
+ if (i >= 2) {
+ if (!(IS_DEC(*(p + 1)) &&
+ IS_DEC(*(p + 2))))
+ OUT_OF_RANGE(*p);
+
+ ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
+ putc(ch, outfp);
+ }
+ }
+ }
+ if (ferror(fp))
+ eprintf("read error:");
+ }
+ /* check for end or fail */
+ len = getline(&bufb, &n, fp);
+ if (len < 3 || strncmp(bufb, "end", 3) != 0 || bufb[3] != '\n')
+ eprintf("invalid uudecode footer \"end\" not found\n");
+ free(bufb);
+}
diff --git a/sbase/uuencode.1 b/sbase/uuencode.1
@@ -0,0 +1,16 @@
+.TH UUENCODE 1 sbase\-VERSION
+.SH NAME
+uuencode \- encode a binary file
+.SH SYNOPSIS
+.B uuencode
+.RI [file]
+.RB name
+.SH DESCRIPTION
+.B uuencode
+reads file (or by default, the standard input) and writes an encoded
+version to the standard output. The encoding uses only printing ASCII
+characters and includes the mode of the file and the operand name
+for use by uudecode.
+.SH NOTES
+This version of uuencode does not currently support the base64
+encoding algorithm.
diff --git a/sbase/uuencode.c b/sbase/uuencode.c
@@ -0,0 +1,78 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void uuencode(FILE *, const char *, const char *);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [file] name\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+
+ ARGBEGIN {
+ case 'm':
+ eprintf("-m not implemented\n");
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0 || argc > 2)
+ usage();
+
+ if (argc == 1) {
+ uuencode(stdin, argv[0], "<stdin>");
+ } else {
+ if (!(fp = fopen(argv[0], "r")))
+ eprintf("fopen %s:", argv[0]);
+ uuencode(fp, argv[1], argv[0]);
+ fclose(fp);
+ }
+ return 0;
+}
+
+static void
+uuencode(FILE *fp, const char *name, const char *s)
+{
+ struct stat st;
+ unsigned char buf[45], *p;
+ ssize_t n;
+ int ch;
+
+ if (fstat(fileno(fp), &st) < 0)
+ eprintf("fstat %s:", s);
+ printf("begin %o %s\n", st.st_mode & 0777, name);
+ while ((n = fread(buf, 1, sizeof(buf), fp))) {
+ ch = ' ' + (n & 0x3f);
+ putchar(ch == ' ' ? '`' : ch);
+ for (p = buf; n > 0; n -= 3, p += 3) {
+ if (n < 3) {
+ p[2] = '\0';
+ if (n < 2)
+ p[1] = '\0';
+ }
+ ch = ' ' + ((p[0] >> 2) & 0x3f);
+ putchar(ch == ' ' ? '`' : ch);
+ ch = ' ' + (((p[0] << 4) | ((p[1] >> 4) & 0xf)) & 0x3f);
+ putchar(ch == ' ' ? '`' : ch);
+ ch = ' ' + (((p[1] << 2) | ((p[2] >> 6) & 0x3)) & 0x3f);
+ putchar(ch == ' ' ? '`' : ch);
+ ch = ' ' + (p[2] & 0x3f);
+ putchar(ch == ' ' ? '`' : ch);
+ }
+ putchar('\n');
+ }
+ if (ferror(fp))
+ eprintf("'%s' read error:", s);
+ printf("%c\nend\n", '`');
+}
diff --git a/sbase/wc.1 b/sbase/wc.1
@@ -0,0 +1,25 @@
+.TH WC 1 sbase\-VERSION
+.SH NAME
+wc \- word count
+.SH SYNOPSIS
+.B wc
+.RB [ \-clmw ]
+.RI [ file ...]
+.SH DESCRIPTION
+.B wc
+prints the number of lines, words, and bytes in each file. If any flags are
+given, wc will print only the requested information. If no files are given, wc
+reads stdin.
+.SH OPTIONS
+.TP
+.B \-c
+print the number of bytes.
+.TP
+.B \-l
+print the number of lines.
+.TP
+.B \-m
+print the number of characters, not bytes.
+.TP
+.B \-w
+print the number of words.
diff --git a/sbase/wc.c b/sbase/wc.c
@@ -0,0 +1,104 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void output(const char *, long, long, long);
+static void wc(FILE *, const char *);
+
+static int lflag = 0;
+static int wflag = 0;
+static char cmode = 0;
+static long tc = 0, tl = 0, tw = 0;
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-clmw] [files...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+ int i;
+
+ ARGBEGIN {
+ case 'c':
+ cmode = 'c';
+ break;
+ case 'm':
+ cmode = 'm';
+ break;
+ case 'l':
+ lflag = 1;
+ break;
+ case 'w':
+ wflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0) {
+ wc(stdin, NULL);
+ } else {
+ for (i = 0; i < argc; i++) {
+ if (!(fp = fopen(argv[i], "r"))) {
+ weprintf("fopen %s:", argv[i]);
+ continue;
+ }
+ wc(fp, argv[i]);
+ fclose(fp);
+ }
+ if (argc > 1)
+ output("total", tc, tl, tw);
+ }
+ return 0;
+}
+
+void
+output(const char *str, long nc, long nl, long nw)
+{
+ int noflags = !cmode && !lflag && !wflag;
+
+ if (lflag || noflags)
+ printf(" %5ld", nl);
+ if (wflag || noflags)
+ printf(" %5ld", nw);
+ if (cmode || noflags)
+ printf(" %5ld", nc);
+ if (str)
+ printf(" %s", str);
+ putchar('\n');
+}
+
+void
+wc(FILE *fp, const char *str)
+{
+ int word = 0;
+ int c;
+ long nc = 0, nl = 0, nw = 0;
+
+ while ((c = getc(fp)) != EOF) {
+ if (cmode != 'm' || UTF8_POINT(c))
+ nc++;
+ if (c == '\n')
+ nl++;
+ if (!isspace(c))
+ word = 1;
+ else if (word) {
+ word = 0;
+ nw++;
+ }
+ }
+ if (word)
+ nw++;
+ tc += nc;
+ tl += nl;
+ tw += nw;
+ output(str, nc, nl, nw);
+}
diff --git a/sbase/xargs.1 b/sbase/xargs.1
@@ -0,0 +1,50 @@
+.TH XARGS 1 sbase\-VERSION
+.SH NAME
+xargs \- constuct argument list(s) and execute command
+.SH SYNOPSIS
+.B xargs
+.RB [\-n
+.IR maxargs ]
+.RB [ \-r ]
+.RB [ \-E
+.IR eofstr ]
+.RI [ cmd
+.IR [arg... ] ]
+.SH DESCRIPTION
+xargs reads space, tab, newline and EOF delimited strings from stdin
+and executes the specified cmd with the strings as arguments.
+
+Any arguments specified on the command line are given to the command upon
+each invocation, followed by some number of the arguments read from
+stdin. The command is repeatedly executed one or more times until stdin
+is exhausted.
+
+Spaces, tabs and newlines may be embedded in arguments using single (`'')
+or double (`"') quotes or backslashes ('\\'). Single quotes escape all
+non-single quote characters, excluding newlines, up to the matching single
+quote. Double quotes escape all non-double quote characters, excluding
+newlines, up to the matching double quote. Any single character, including
+newlines, may be escaped by a backslash.
+.SH OPTIONS
+.TP
+.B \-n maxargs
+Use at most maxargs arguments per command line.
+.TP
+.BI \-r
+Do not run the command if there are no arguments. Normally the command is
+executed at least once even if there are no arguments.
+.TP
+.B \-E eofstr
+Use eofstr as a logical EOF marker.
+.SH EXIT STATUS
+xargs exits with one of the following values:
+
+ 0 All invocations of command returned a zero exit
+ status.
+ 123 One or more invocations of command returned a
+ nonzero exit status.
+ 124 The command exited with a 255 exit status.
+ 125 The command was killed or stopped by a signal.
+ 126 The command was found but could not be executed.
+ 127 The command could not be found.
+ 1 Some other error occurred.
diff --git a/sbase/xargs.c b/sbase/xargs.c
@@ -0,0 +1,260 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "text.h"
+#include "util.h"
+
+enum {
+ NARGS = 10000
+};
+
+static int inputc(void);
+static void deinputc(int);
+static void fillargbuf(int);
+static int eatspace(void);
+static int parsequote(int);
+static int parseescape(void);
+static char *poparg(void);
+static void waitchld(void);
+static void spawn(void);
+
+static char *cmd[NARGS];
+static char *argb;
+static size_t argbsz;
+static size_t argbpos;
+static long maxargs = 0;
+static int nerrors = 0;
+static char *eofstr;
+static int rflag = 0, nflag = 0;
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-n maxargs] [-r] [-E eofstr] [cmd [arg...]]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int leftover = 0;
+ long argsz, argmaxsz;
+ char *arg = "";
+ int i, a;
+
+ ARGBEGIN {
+ case 'n':
+ nflag = 1;
+ if ((maxargs = strtol(EARGF(usage()), NULL, 10)) <= 0)
+ eprintf("%s: value for -n option should be >= 1\n", argv0);
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 'E':
+ eofstr = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ argmaxsz = sysconf(_SC_ARG_MAX);
+ if (argmaxsz < 0)
+ eprintf("sysconf:");
+ /* Leave some room for environment variables */
+ argmaxsz -= 4 * 1024;
+
+ do {
+ argsz = 0; i = 0; a = 0;
+ if (argc > 0) {
+ for (; i < argc; i++) {
+ cmd[i] = estrdup(argv[i]);
+ argsz += strlen(cmd[i]) + 1;
+ }
+ } else {
+ cmd[i] = estrdup("/bin/echo");
+ argsz += strlen(cmd[i]) + 1;
+ i++;
+ }
+ while (leftover == 1 || (arg = poparg())) {
+ if (argsz + strlen(arg) + 1 > argmaxsz ||
+ i >= NARGS - 1) {
+ if (strlen(arg) + 1 > argmaxsz)
+ eprintf("insufficient argument space\n");
+ leftover = 1;
+ break;
+ }
+ cmd[i] = estrdup(arg);
+ argsz += strlen(cmd[i]) + 1;
+ i++;
+ a++;
+ leftover = 0;
+ if (nflag == 1 && a >= maxargs)
+ break;
+ }
+ cmd[i] = NULL;
+ if (a >= maxargs && nflag == 1)
+ spawn();
+ else if (!a || (i == 1 && rflag == 1))
+ ;
+ else
+ spawn();
+ for (; i >= 0; i--)
+ free(cmd[i]);
+ } while (arg);
+
+ free(argb);
+
+ return nerrors > 0 ? 123 : 0;
+}
+
+static int
+inputc(void)
+{
+ int ch;
+
+ ch = getc(stdin);
+ if (ch == EOF && ferror(stdin))
+ eprintf("stdin: read error:");
+ return ch;
+}
+
+static void
+deinputc(int ch)
+{
+ ungetc(ch, stdin);
+}
+
+static void
+fillargbuf(int ch)
+{
+ if (argbpos >= argbsz) {
+ argbsz = argbpos == 0 ? 1 : argbsz * 2;
+ argb = erealloc(argb, argbsz);
+ }
+ argb[argbpos] = ch;
+}
+
+static int
+eatspace(void)
+{
+ int ch;
+
+ while ((ch = inputc()) != EOF) {
+ switch (ch) {
+ case ' ': case '\t': case '\n':
+ break;
+ default:
+ deinputc(ch);
+ return ch;
+ }
+ }
+ return -1;
+}
+
+static int
+parsequote(int q)
+{
+ int ch;
+
+ while ((ch = inputc()) != EOF) {
+ if (ch == q)
+ return 0;
+ if (ch != '\n') {
+ fillargbuf(ch);
+ argbpos++;
+ }
+ }
+ return -1;
+}
+
+static int
+parseescape(void)
+{
+ int ch;
+
+ if ((ch = inputc()) != EOF) {
+ fillargbuf(ch);
+ argbpos++;
+ return ch;
+ }
+ return -1;
+}
+
+static char *
+poparg(void)
+{
+ int ch;
+
+ argbpos = 0;
+ if (eatspace() < 0)
+ return NULL;
+ while ((ch = inputc()) != EOF) {
+ switch (ch) {
+ case ' ': case '\t': case '\n':
+ goto out;
+ case '\'':
+ if (parsequote('\'') < 0)
+ eprintf("unterminated single quote\n");
+ break;
+ case '\"':
+ if (parsequote('\"') < 0)
+ eprintf("unterminated double quote\n");
+ break;
+ case '\\':
+ if (parseescape() < 0)
+ eprintf("backslash at EOF\n");
+ break;
+ default:
+ fillargbuf(ch);
+ argbpos++;
+ break;
+ }
+ }
+out:
+ fillargbuf('\0');
+ if (eofstr && strcmp(argb, eofstr) == 0)
+ return NULL;
+ return argb;
+}
+
+static void
+waitchld(void)
+{
+ int status;
+
+ wait(&status);
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) == 255)
+ exit(124);
+ if (WEXITSTATUS(status) == 127 ||
+ WEXITSTATUS(status) == 126)
+ exit(WEXITSTATUS(status));
+ if (status != 0)
+ nerrors++;
+ }
+ if (WIFSIGNALED(status))
+ exit(125);
+}
+
+static void
+spawn(void)
+{
+ pid_t pid;
+ int savederrno;
+
+ pid = fork();
+ if (pid < 0)
+ eprintf("fork:");
+ if (pid == 0) {
+ execvp(*cmd, cmd);
+ savederrno = errno;
+ weprintf("execvp %s:", *cmd);
+ _exit(savederrno == ENOENT ? 127 : 126);
+ }
+ waitchld();
+}
diff --git a/sbase/yes.1 b/sbase/yes.1
@@ -0,0 +1,10 @@
+.TH YES 1 sbase\-VERSION
+.SH NAME
+yes \- output a string repeatedly
+.SH SYNOPSIS
+.B yes
+.RB [ string ... ]
+.SH DESCRIPTION
+.B yes
+will repeatedly output 'y' or the strings specified.
+
diff --git a/sbase/yes.c b/sbase/yes.c
@@ -0,0 +1,24 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [string]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ for (;;)
+ puts(argc >= 1 ? argv[0] : "y");
+ return 1; /* should not reach */
+}
diff --git a/sdhcp/LICENSE b/sdhcp/LICENSE
@@ -0,0 +1,22 @@
+MIT/X Consortium License
+
+© 2012 David Galos (galosd83 (at) students.rowan.edu)
+© 2014 Hiltjo Posthuma <hiltjo at codemadness dot org>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/sdhcp/Makefile b/sdhcp/Makefile
@@ -0,0 +1,65 @@
+include config.mk
+
+.POSIX:
+.SUFFIXES: .c .o
+
+HDR = util.h arg.h
+LIB = \
+ util/strlcpy.o \
+ util/eprintf.o
+
+SRC = sdhcp.c
+
+OBJ = $(SRC:.c=.o) $(LIB)
+BIN = $(SRC:.c=)
+MAN = $(SRC:.c=.1)
+
+all: options binlib
+
+options:
+ @echo sdhcp build options:
+ @echo "CFLAGS = ${CFLAGS}"
+ @echo "LDFLAGS = ${LDFLAGS}"
+ @echo "CC = ${CC}"
+
+binlib: util.a
+ $(MAKE) bin
+
+bin: $(BIN)
+
+$(OBJ): $(HDR) config.mk
+
+.o:
+ @echo LD $@
+ @$(LD) -o $@ $< util.a $(LDFLAGS)
+
+.c.o:
+ @echo CC $<
+ @$(CC) -c -o $@ $< $(CFLAGS)
+
+util.a: $(LIB)
+ @echo AR $@
+ @$(AR) -r -c $@ $(LIB)
+ @ranlib $@
+
+install: all
+ @echo installing executables to $(DESTDIR)$(PREFIX)/sbin
+ @mkdir -p $(DESTDIR)$(PREFIX)/sbin
+ @cp -f $(BIN) $(DESTDIR)$(PREFIX)/sbin
+ @cd $(DESTDIR)$(PREFIX)/sbin && chmod 755 $(BIN)
+ @echo installing manual pages to $(DESTDIR)$(MANPREFIX)/man1
+ @mkdir -p $(DESTDIR)$(MANPREFIX)/man1
+ @for m in $(MAN); do sed "s/VERSION/$(VERSION)/g" < "$$m" > $(DESTDIR)$(MANPREFIX)/man1/"$$m"; done
+ @cd $(DESTDIR)$(MANPREFIX)/man1 && chmod 644 $(MAN)
+
+uninstall:
+ @echo removing executables from $(DESTDIR)$(PREFIX)/sbin
+ @cd $(DESTDIR)$(PREFIX)/sbin && rm -f $(BIN)
+ @echo removing manual pages from $(DESTDIR)$(MANPREFIX)/man1
+ @cd $(DESTDIR)$(MANPREFIX)/man1 && rm -f $(MAN)
+
+clean:
+ @echo cleaning
+ @rm -f $(BIN) $(OBJ) util.a
+
+.PHONY: all options clean install uninstall
diff --git a/sdhcp/TODO b/sdhcp/TODO
@@ -0,0 +1,41 @@
+TODO:
+ [ ] manual check memcpy bounds.
+ [ ] add flag (-s?) to probe a specific DHCP server, not broadcast?
+ probably skip in run() Init: etc stages.
+ [ ] sane default value for client-id and test it.
+ [ ] add new options to man page (-d, -i).
+ [ ] update LICENSE.
+ [ ] replace unsigned char ip[4] and so on from function declarations.
+ [?] ipv6 support ?
+ [ ] allow sdhcp to run in the foreground (-f?)
+
+Changed (for now):
+ - cleanup
+ - code style.
+ - trailing whitespace and use tabs.
+ - remove debug (dbgprintf()) code in sdhcp.c.
+ - code compiles more cleanly (ansi and c99),
+ -D_BSD_SOURCE added and explicitly added missing headers (time.h and unistd.h).
+ - moved man page from sdhcp.8 to sdhcp.1
+ - typos:
+ - sdhcp.c: interface typo.
+ - sdhcp.1: shdcp typo.
+ - make exit([01]), EXIT_SUCCESS or EXIT_FAILURE.
+ - replace write() for stdout messages with fprintf()
+ - replace die() with eprintf().
+ - makefile:
+ - man page install should respect $DESTDIR.
+ - make sure on install /sbin and mandir exists.
+ - add config.mk, and follow suckless Makefile style.
+ - add arg.h
+ - first parameter remains interface.
+ - second parameter is optional client-id, used to be hardcoded to
+ "vaio".
+ - add -d flag, don't update /etc/resolv.conf.
+ - add -i flag, don't set ip.
+ - add -e flag, run program, this has the following variables set:
+ $SERVER, DHCP ip.
+ $DNS, DNS ip.
+ $ROUTER, router ip.
+ $MASK, network mask.
+ $CLIENT, client ip.
diff --git a/sdhcp/arg.h b/sdhcp/arg.h
@@ -0,0 +1,63 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef ARG_H__
+#define ARG_H__
+
+extern char *argv0;
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
+ argv[0] && argv[0][1]\
+ && argv[0][0] == '-';\
+ argc--, argv++) {\
+ char argc_;\
+ char **argv_;\
+ int brk_;\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ for (brk_ = 0, argv[0]++, argv_ = argv;\
+ argv[0][0] && !brk_;\
+ argv[0]++) {\
+ if (argv_ != argv)\
+ break;\
+ argc_ = argv[0][0];\
+ switch (argc_)
+
+/* Handles obsolete -NUM syntax */
+#define ARGNUM case '0':\
+ case '1':\
+ case '2':\
+ case '3':\
+ case '4':\
+ case '5':\
+ case '6':\
+ case '7':\
+ case '8':\
+ case '9'
+
+#define ARGEND }\
+ }
+
+#define ARGC() argc_
+
+#define ARGNUMF(base) (brk_ = 1, estrtol(argv[0], (base)))
+
+#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ ((x), abort(), (char *)0) :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ (char *)0 :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#endif
diff --git a/sdhcp/config.mk b/sdhcp/config.mk
@@ -0,0 +1,13 @@
+# sdhcp version
+VERSION = 0.1
+
+PREFIX = /usr/local
+DESTDIR =
+MANPREFIX = $(PREFIX)/share/man
+
+#CC = gcc
+#CC = musl-gcc
+LD = $(CC)
+CPPFLAGS = -D_BSD_SOURCE
+CFLAGS = -g -Wall -Wextra -O0 -ansi $(CPPFLAGS)
+LDFLAGS = -g
diff --git a/sdhcp/debug.c b/sdhcp/debug.c
@@ -0,0 +1,148 @@
+#include <stdarg.h>
+
+void bpdump(unsigned char *p, int n);
+
+unsigned short
+nhgets(unsigned char c[2])
+{
+ return ((c[0] << 8) + c[1]) & 0xffff;
+}
+
+unsigned long
+nhgetl(unsigned char c[4])
+{
+ return (nhgets(c) << 16) + nhgets(c + 2);
+}
+
+char *
+ipstr(unsigned char *ip)
+{
+ char * ch = malloc(3 * 4 + 3 + 10);
+ sprintf(ch, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
+ return ch;
+}
+
+#if 0
+void
+dbgprintf(char *str, ...)
+{
+ va_list ap;
+ va_start(ap, str);
+ vfprintf(stderr, str, ap);
+ va_end(ap);
+}
+#endif
+
+void
+bpdump(unsigned char *p, int n)
+{
+ int len, i, code;
+ Bootp *bp;
+ unsigned char type;
+ char *types[] = {
+ "discover", "offer", "request",
+ "decline", "ack", "nak", "release", "inform"
+ };
+ /* Udphdr *up; */
+
+ bp = (Bootp*)p;
+ /* up = (Udphdr*)bp->udphdr; */
+
+ if(n < bp->magic - p) {
+ fprintf(stderr, "dhcpclient: short bootp packet");
+ return;
+ }
+
+ optget(bp, &type, ODtype, sizeof type);
+ fprintf(stderr, "DHCP%s\n", types[type - 1]);
+ /* fprintf(stderr, "laddr=%I lport=%d raddr=%I rport=%d\n", up->laddr,
+ nhgets(up->lport), up->raddr, nhgets(up->rport)); */
+ fprintf(stderr, "op = %d htype = %d hlen = %d hops = %d\n", *bp->op, *bp->htype,
+ *bp->hlen, *bp->hops);
+ fprintf(stderr, "xid = %x secs = %d flags = %x\n", nhgetl(bp->xid),
+ nhgets(bp->secs), nhgets(bp->flags));
+ fprintf(stderr, "ciaddr = %s, yiaddr = %s, siaddr = %s, giaddr = %s\n",
+ ipstr(bp->ciaddr), ipstr(bp->yiaddr), ipstr(bp->siaddr), ipstr(bp->giaddr));
+ fprintf(stderr, "chaddr =");
+ for(i=0; i<15; i++)
+ fprintf(stderr, "%.2x:", bp->chaddr[i]);
+ fprintf(stderr, "%.2x\n", bp->chaddr[15]);
+ fprintf(stderr, "sname = %s\n", bp->sname);
+ fprintf(stderr, "file = %s\n", bp->file);
+
+ n -= bp->magic - p;
+ p = bp->magic;
+
+ if(n < 4)
+ return;
+ if(memcmp(magic, p, 4) != 0)
+ fprintf(stderr, "dhcpclient: bad opt magic %#x %#x %#x %#x\n",
+ p[0], p[1], p[2], p[3]);
+ p += 4;
+ n -= 4;
+
+ while(n > 0) {
+ code = *p++;
+ n--;
+ if(code == OBpad)
+ continue;
+ if(code == OBend)
+ break;
+ if(n == 0) {
+ fprintf(stderr, " bad option: %d", code);
+ return;
+ }
+ len = *p++;
+ n--;
+ if(len > n) {
+ fprintf(stderr, " bad option: %d", code);
+ return;
+ }
+ switch(code) {
+ case ODtype:
+ fprintf(stderr, "DHCP type %d\n", p[0]);
+ break;
+ case ODclientid:
+ fprintf(stderr, "client id=");
+ for(i = 0; i<len; i++)
+ fprintf(stderr, "%x ", p[i]);
+ fprintf(stderr, "\n");
+ break;
+ case ODlease:
+ fprintf(stderr, "lease=%d sec\n", nhgetl(p));
+ break;
+ case ODserverid:
+ fprintf(stderr, "server id=%s\n", ipstr(p));
+ break;
+ case OBmask:
+ fprintf(stderr, "mask=%s\n", ipstr(p));
+ break;
+ case OBrouter:
+ fprintf(stderr, "router=%s\n", ipstr(p));
+ break;
+ case ODipaddr:
+ fprintf(stderr, "ip addr=%s\n", ipstr(p));
+ break;
+ case OBdnsserver:
+ fprintf(stderr, "dns=%s\n", ipstr(p));
+ break;
+ case OBbaddr:
+ fprintf(stderr, "broadcast=%s\n", ipstr(p));
+ break;
+ case ODrenewaltime:
+ fprintf(stderr, "renew time=%d sec\n", nhgetl(p));
+ break;
+ case ODrebindingtime:
+ fprintf(stderr, "rebind time=%d sec\n", nhgetl(p));
+ break;
+ default:
+ fprintf(stderr, "unknown option %d\n", code);
+ for(i = 0; i<len; i++)
+ fprintf(stderr, "%x ", p[i]);
+ fprintf(stderr, "\n");
+ break;
+ }
+ p += len;
+ n -= len;
+ }
+}
diff --git a/sdhcp/sdhcp.1 b/sdhcp/sdhcp.1
@@ -0,0 +1,49 @@
+.TH SDHCP-VERSION 1
+.SH NAME
+sdhcp \- a simple dhcp client
+.SH SYNOPSIS
+.B sdhcp
+.RB [ \-d ]
+.RB [ \-i ]
+.RB "[ \-e"
+.IR "program"
+.RB "]"
+.RB "[ "
+.IR interface
+.RB "]"
+.RB "["
+.IR "client\-id"
+.RB "]"
+.SH DESCRIPTION
+sdhcp is a simple, tiny dhcp client. It runs until it enters the "Bound"
+state, then forks to the background and runs as a daemon to keep
+the lease alive.
+.SH OPTIONS
+.TP
+.B \-d
+don't change DNS in /etc/resolv.conf.
+.TP
+.B \-i
+don't change interface information such as an IP address.
+.TP
+.B "\-e program"
+run program. Variables will be set, see VARIABLES.
+.SH VARIABLES
+.LP
+The following variables are set:
+.LP
+$SERVER DHCP IP.
+.LP
+$DNS DNS IP.
+.LP
+$ROUTER router IP.
+.LP
+$MASK network mask.
+.LP
+$CLIENT your client IP.
+.SH BUGS
+I'm sure there's plenty. It only currently supports a small subset of
+DHCP options, and has been untested on larger networks. It ignores most of
+the DHCP options it understands. Send bug reports to me!
+.SH AUTHOR
+see LICENSE file
diff --git a/sdhcp/sdhcp.c b/sdhcp/sdhcp.c
@@ -0,0 +1,495 @@
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <net/route.h>
+#include <signal.h>
+#include <poll.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "util.h"
+#include "arg.h"
+
+typedef struct bootp {
+ unsigned char op [1];
+ unsigned char htype [1];
+ unsigned char hlen [1];
+ unsigned char hops [1];
+ unsigned char xid [4];
+ unsigned char secs [2];
+ unsigned char flags [2];
+ unsigned char ciaddr [4];
+ unsigned char yiaddr [4];
+ unsigned char siaddr [4];
+ unsigned char giaddr [4];
+ unsigned char chaddr [16];
+ unsigned char sname [64];
+ unsigned char file [128];
+ unsigned char magic [4];
+ unsigned char optdata [312-4];
+} Bootp;
+
+enum {
+ DHCPdiscover = 1,
+ DHCPoffer,
+ DHCPrequest,
+ DHCPdecline,
+ DHCPack,
+ DHCPnak,
+ DHCPrelease,
+ DHCPinform,
+ Timeout = 200,
+
+ Bootrequest = 1,
+ Bootreply = 2,
+ /* bootp flags */
+ Fbroadcast = 1 << 15,
+
+ OBpad = 0,
+ OBmask = 1,
+ OBrouter = 3,
+ OBnameserver = 5,
+ OBdnsserver = 6,
+ OBbaddr = 28,
+ ODipaddr = 50, /* 0x32 */
+ ODlease = 51,
+ ODoverload = 52,
+ ODtype = 53, /* 0x35 */
+ ODserverid = 54, /* 0x36 */
+ ODparams = 55, /* 0x37 */
+ ODmessage = 56,
+ ODmaxmsg = 57,
+ ODrenewaltime = 58,
+ ODrebindingtime = 59,
+ ODvendorclass = 60,
+ ODclientid = 61, /* 0x3d */
+ ODtftpserver = 66,
+ ODbootfile = 67,
+ OBend = 255,
+};
+
+enum { Broadcast, Unicast};
+
+Bootp bp;
+unsigned char magic[] = {99, 130, 83, 99};
+
+/* conf */
+static unsigned char xid[sizeof bp.xid];
+static unsigned char hwaddr[16];
+static time_t starttime;
+static char *ifname = "eth0";
+static char *cid = "";
+static char *program = "";
+static int sock;
+/* sav */
+static unsigned char server[4];
+static unsigned char client[4];
+static unsigned char mask[4];
+static unsigned char router[4];
+static unsigned char dns[4];
+static unsigned long t1;
+
+static int dflag = 1; /* change DNS in /etc/resolv.conf ? */
+static int iflag = 1; /* set IP ? */
+
+#define IP(a,b,c,d) (unsigned char[4]){a,b,c,d}
+
+static void
+hnput(unsigned char *dst, unsigned long long src, size_t n)
+{
+ unsigned int i;
+
+ for(i = 0; n--; i++)
+ dst[i] = (src >> (n * 8)) & 0xff;
+}
+
+static struct sockaddr *
+iptoaddr(struct sockaddr *ifaddr, unsigned char ip[4], int port)
+{
+ struct sockaddr_in *in = (struct sockaddr_in *)ifaddr;
+
+ in->sin_family = AF_INET;
+ in->sin_port = htons(port);
+ memcpy(&(in->sin_addr), ip, sizeof in->sin_addr);
+ return ifaddr;
+}
+
+/* sendto UDP wrapper */
+static ssize_t
+udpsend(unsigned char ip[4], int fd, void *data, size_t n)
+{
+ struct sockaddr addr;
+ socklen_t addrlen = sizeof addr;
+ ssize_t sent;
+
+ iptoaddr(&addr, ip, 67); /* bootp server */
+ if((sent = sendto(fd, data, n, 0, &addr, addrlen)) == -1)
+ eprintf("sendto:");
+ return sent;
+}
+
+/* recvfrom UDP wrapper */
+static ssize_t
+udprecv(unsigned char ip[4], int fd, void *data, size_t n)
+{
+ struct sockaddr addr;
+ socklen_t addrlen = sizeof addr;
+ ssize_t r;
+
+ iptoaddr(&addr, ip, 68); /* bootp client */
+ if((r = recvfrom(fd, data, n, 0, &addr, &addrlen)) == -1)
+ eprintf("recvfrom:");
+ return r;
+}
+
+static void
+setip(unsigned char ip[4], unsigned char mask[4], unsigned char gateway[4])
+{
+ struct ifreq ifreq;
+ struct rtentry rtreq;
+ int fd;
+
+ memset(&ifreq, 0, sizeof(ifreq));
+ memset(&rtreq, 0, sizeof(rtreq));
+
+ strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE);
+ iptoaddr(&(ifreq.ifr_addr), ip, 0);
+ if((fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)) == -1)
+ eprintf("can't set ip, socket:");
+ ioctl(fd, SIOCSIFADDR, &ifreq);
+ iptoaddr(&(ifreq.ifr_netmask), mask, 0);
+ ioctl(fd, SIOCSIFNETMASK, &ifreq);
+ ifreq.ifr_flags = IFF_UP | IFF_RUNNING | IFF_BROADCAST | IFF_MULTICAST;
+ ioctl(fd, SIOCSIFFLAGS, &ifreq);
+ /* gw */
+ rtreq.rt_flags = (RTF_UP | RTF_GATEWAY);
+ iptoaddr(&(rtreq.rt_gateway), gateway, 0);
+ iptoaddr(&(rtreq.rt_genmask), IP(0, 0, 0, 0), 0);
+ iptoaddr(&(rtreq.rt_dst), IP(0, 0, 0, 0), 0);
+ ioctl(fd, SIOCADDRT, &rtreq);
+
+ close(fd);
+}
+
+static void
+cat(int dfd, char *src)
+{
+ char buf[BUFSIZ];
+ int n, fd;
+
+ if((fd = open(src, O_RDONLY)) == -1)
+ return; /* can't read, but don't error out */
+ while((n = read(fd, buf, sizeof buf)) > 0)
+ write(dfd, buf, n);
+ close(fd);
+}
+
+static void
+setdns(unsigned char dns[4])
+{
+ char buf[128];
+ int fd;
+
+ if((fd = creat("/etc/resolv.conf", 0644)) == -1) {
+ weprintf("can't change /etc/resolv.conf:");
+ return;
+ }
+ cat(fd, "/etc/resolv.conf.head");
+ if(snprintf(buf, sizeof(buf) - 1, "\nnameserver %d.%d.%d.%d\n",
+ dns[0], dns[1], dns[2], dns[3]) > 0)
+ write(fd, buf, strlen(buf));
+ cat(fd, "/etc/resolv.conf.tail");
+ close(fd);
+}
+
+static void
+optget(Bootp *bp, void *data, int opt, int n)
+{
+ unsigned char *p = bp->optdata;
+ unsigned char *top = ((unsigned char *)bp) + sizeof *bp;
+ int code, len;
+
+ while(p < top) {
+ code = *p++;
+ if(code == OBpad)
+ continue;
+ if(code == OBend || p == top)
+ break;
+ len = *p++;
+ if(len > top - p)
+ break;
+ if(code == opt) {
+ memcpy(data, p, MIN(len, n));
+ return;
+ }
+ p += len;
+ }
+}
+
+static unsigned char *
+optput(unsigned char *p, int opt, unsigned char *data, size_t len)
+{
+ *p++ = opt;
+ *p++ = (unsigned char)len;
+ memcpy(p, data, len);
+ return p + len;
+}
+
+static unsigned char *
+hnoptput(unsigned char *p, int opt, long long data, size_t len)
+{
+ *p++ = opt;
+ *p++ = (unsigned char)len;
+ hnput(p, data, len);
+ return p + len;
+}
+
+static void
+dhcpsend(int type, int how)
+{
+ unsigned char *ip, *p;
+
+ memset(&bp, 0, sizeof bp);
+ hnput(bp.op, Bootrequest, 1);
+ hnput(bp.htype, 1, 1);
+ hnput(bp.hlen, 6, 1);
+ memcpy(bp.xid, xid, sizeof xid);
+ hnput(bp.flags, Fbroadcast, sizeof bp.flags);
+ hnput(bp.secs, time(NULL) - starttime, sizeof bp.secs);
+ memcpy(bp.magic, magic, sizeof bp.magic);
+ memcpy(bp.chaddr, hwaddr, sizeof bp.chaddr);
+ p = bp.optdata;
+ p = hnoptput(p, ODtype, type, 1);
+ p = optput(p, ODclientid, (unsigned char*)cid, strlen(cid));
+
+ switch(type) {
+ case DHCPdiscover:
+ break;
+ case DHCPrequest:
+ /* memcpy(bp.ciaddr, client, sizeof bp.ciaddr); */
+ p = hnoptput(p, ODlease, t1, sizeof t1);
+ p = optput(p, ODipaddr, client, sizeof client);
+ p = optput(p, ODserverid, server, sizeof server);
+ break;
+ case DHCPrelease:
+ memcpy(bp.ciaddr, client, sizeof client);
+ p = optput(p, ODipaddr, client, sizeof client);
+ p = optput(p, ODserverid, server, sizeof server);
+ break;
+ }
+ *p++ = OBend;
+
+ ip = (how == Broadcast) ? IP(255, 255, 255, 255) : server;
+ udpsend(ip, sock, &bp, p - (unsigned char *)&bp);
+}
+
+static int
+dhcprecv(void)
+{
+ unsigned char type;
+ struct pollfd pfd;
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = sock;
+ pfd.events = POLLIN;
+
+ memset(&bp, 0, sizeof bp);
+ if(poll(&pfd, 1, -1) == -1) {
+ if(errno != EINTR)
+ eprintf("poll:");
+ else
+ return Timeout;
+ }
+ udprecv(IP(255, 255, 255, 255), sock, &bp, sizeof bp);
+ optget(&bp, &type, ODtype, sizeof type);
+ return type;
+}
+
+static void
+acceptlease(void)
+{
+ char buf[128];
+
+ if(iflag == 1)
+ setip(client, mask, router);
+ if(dflag == 1)
+ setdns(dns);
+ if(*program) {
+ snprintf(buf, sizeof(buf), "%d.%d.%d.%d", server[0], server[1], server[2], server[3]);
+ setenv("SERVER", buf, 1);
+ snprintf(buf, sizeof(buf), "%d.%d.%d.%d", client[0], client[1], client[2], client[3]);
+ setenv("CLIENT", buf, 1);
+ snprintf(buf, sizeof(buf), "%d.%d.%d.%d", mask[0], mask[1], mask[2], mask[3]);
+ setenv("MASK", buf, 1);
+ snprintf(buf, sizeof(buf), "%d.%d.%d.%d", router[0], router[1], router[2], router[3]);
+ setenv("ROUTER", buf, 1);
+ snprintf(buf, sizeof(buf), "%d.%d.%d.%d", dns[0], dns[1], dns[2], dns[3]);
+ setenv("DNS", buf, 1);
+ system(program);
+ }
+ alarm(t1);
+}
+
+static void
+run(void)
+{
+#if 0
+InitReboot:
+ /* send DHCPrequest to old server */
+ dhcpsend(DHCPrequest, Broadcast);
+ goto Rebooting;
+Rebooting:
+ switch (dhcprecv()) {
+ case DHCPnak:
+ goto Init;
+ case DHCPack:
+ acceptoffer();
+ goto Bound;
+ }
+#endif
+Init:
+ dhcpsend(DHCPdiscover, Broadcast);
+ alarm(1);
+ goto Selecting;
+Selecting:
+ switch(dhcprecv()) {
+ case DHCPoffer:
+ alarm(0);
+ memcpy(client, bp.yiaddr, sizeof client);
+ optget(&bp, server, ODserverid, sizeof server);
+ optget(&bp, mask, OBmask, sizeof mask);
+ optget(&bp, router, OBrouter, sizeof router);
+ optget(&bp, dns, OBdnsserver, sizeof dns);
+ optget(&bp, &t1, ODlease, sizeof t1);
+ t1 = ntohl(t1);
+ dhcpsend(DHCPrequest, Broadcast);
+ goto Requesting;
+ case Timeout:
+ goto Init;
+ default:
+ goto Selecting;
+ }
+Requesting:
+ switch(dhcprecv()) {
+ case DHCPoffer:
+ goto Requesting; /* ignore other offers. */
+#if 0
+ case DHCPack: /* (and you don't want it) ? */
+ dhcpsend(DHCPdecline, Unicast);
+ goto Init;
+#endif
+ case DHCPack:
+ acceptlease();
+ goto Bound;
+ }
+Bound:
+ fputs("Congrats! You should be on the 'net.\n", stdout);
+ if(fork())
+ exit(EXIT_SUCCESS);
+ switch (dhcprecv()) {
+ case DHCPoffer:
+ case DHCPack:
+ case DHCPnak:
+ goto Bound; /* discard offer, ACK or NAK */
+ case Timeout:
+ dhcpsend(DHCPrequest, Unicast);
+ goto Renewing;
+ }
+Renewing:
+ switch(dhcprecv()) {
+ case DHCPack:
+ acceptlease();
+ goto Bound;
+ case DHCPnak:
+ goto Init;
+ case Timeout:
+ dhcpsend(DHCPrequest, Broadcast);
+ goto Rebinding;
+ }
+Rebinding:
+ switch(dhcprecv()) {
+ case DHCPnak: /* lease expired */
+ goto Init;
+ case DHCPack:
+ acceptlease();
+ goto Bound;
+ }
+}
+
+static void nop(int unused) {
+ (void) unused;
+}
+
+static void cleanexit(int unused) {
+ (void) unused;
+ dhcpsend(DHCPrelease, Unicast);
+ exit(EXIT_SUCCESS);
+}
+
+static void
+usage(void) {
+ eprintf("usage: sdhcp [-i] [-d] [-e program] [ifname] [clientid]\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ int bcast = 1;
+ struct ifreq ifreq;
+ struct sockaddr addr;
+ int rnd;
+
+ ARGBEGIN {
+ case 'e': /* run program */
+ program = EARGF(usage());
+ break;
+ case 'i': /* don't set ip */
+ iflag = 0;
+ break;
+ case 'd': /* don't update DNS in/etc/resolv.conf */
+ dflag = 0;
+ break;
+ default:
+ usage();
+ break;
+ } ARGEND;
+
+ if(argc >= 1)
+ ifname = argv[0]; /* interface name */
+ if(argc >= 2)
+ cid = argv[1]; /* client-id */
+
+ memset(&ifreq, 0, sizeof(ifreq));
+ signal(SIGALRM, nop);
+ signal(SIGTERM, cleanexit);
+
+ if((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
+ eprintf("socket:");
+ if(setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof bcast) == -1)
+ eprintf("setsockopt:");
+
+ strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE);
+ ioctl(sock, SIOCGIFINDEX, &ifreq);
+ if(setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof ifreq) == -1)
+ eprintf("setsockopt:");
+ iptoaddr(&addr, IP(255, 255, 255, 255), 68);
+ if(bind(sock, (void*)&addr, sizeof addr) != 0)
+ eprintf("bind:");
+ ioctl(sock, SIOCGIFHWADDR, &ifreq);
+ memcpy(hwaddr, ifreq.ifr_hwaddr.sa_data, sizeof ifreq.ifr_hwaddr.sa_data);
+
+ if((rnd = open("/dev/urandom", O_RDONLY)) == -1)
+ eprintf("can't open /dev/urandom to generate unique transaction identifier:");
+ read(rnd, xid, sizeof xid);
+ close(rnd);
+
+ starttime = time(NULL);
+ run();
+ return EXIT_SUCCESS;
+}
diff --git a/sdhcp/util.h b/sdhcp/util.h
@@ -0,0 +1,9 @@
+#define MIN(a,b) (((a)<(b))?(a):(b))
+#define bpdump(p,n) 1
+
+#undef strlcpy
+size_t strlcpy(char *, const char *, size_t);
+
+void weprintf(const char *, ...);
+void eprintf(const char *, ...);
+void enprintf(int, const char *, ...);
diff --git a/sdhcp/util/eprintf.c b/sdhcp/util/eprintf.c
@@ -0,0 +1,67 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../util.h"
+
+char *argv0;
+
+static void venprintf(int, const char *, va_list);
+
+void
+eprintf(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ venprintf(EXIT_FAILURE, fmt, ap);
+ va_end(ap);
+}
+
+void
+enprintf(int status, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ venprintf(status, fmt, ap);
+ va_end(ap);
+}
+
+void
+venprintf(int status, const char *fmt, va_list ap)
+{
+#ifdef DEBUG
+ fprintf(stderr, "%s: ", argv0);
+#endif
+
+ vfprintf(stderr, fmt, ap);
+
+ if(fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ }
+
+ exit(status);
+}
+
+void
+weprintf(const char *fmt, ...)
+{
+ va_list ap;
+
+#ifdef DEBUG
+ fprintf(stderr, "%s: ", argv0);
+#endif
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ }
+}
diff --git a/sdhcp/util/strlcpy.c b/sdhcp/util/strlcpy.c
@@ -0,0 +1,32 @@
+/* Taken from OpenBSD */
+#include <sys/types.h>
+#include <string.h>
+#include "../util.h"
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0')
+ break;
+ }
+ }
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+ return(s - src - 1); /* count does not include NUL */
+}
diff --git a/sed/Makefile b/sed/Makefile
@@ -0,0 +1,9 @@
+OBJ = compile.o fgetln.o main.o misc.o process.o reallocarray.o strlcat.o strlcpy.o
+TARG = sed
+
+all: $(TARG)
+
+include ../std.mk
+
+clean:
+ rm -f $(TARG) $(OBJ)
diff --git a/sed/compile.c b/sed/compile.c
@@ -0,0 +1,861 @@
+/* $OpenBSD: compile.c,v 1.36 2014/10/08 04:19:08 deraadt Exp $ */
+
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "defs.h"
+#include "extern.h"
+#include "util.h"
+
+#define LHSZ 128
+#define LHMASK (LHSZ - 1)
+static struct labhash {
+ struct labhash *lh_next;
+ u_int lh_hash;
+ struct s_command *lh_cmd;
+ int lh_ref;
+} *labels[LHSZ];
+
+static char *compile_addr(char *, struct s_addr *);
+static char *compile_ccl(char **, char *);
+static char *compile_delimited(char *, char *, int);
+static char *compile_flags(char *, struct s_subst *);
+static char *compile_re(char *, regex_t **);
+static char *compile_subst(char *, struct s_subst *);
+static char *compile_text(void);
+static char *compile_tr(char *, char **);
+static struct s_command
+ **compile_stream(struct s_command **);
+static char *duptoeol(char *, char *, char **);
+static void enterlabel(struct s_command *);
+static struct s_command
+ *findlabel(char *);
+static void fixuplabel(struct s_command *, struct s_command *);
+static void uselabel(void);
+
+/*
+ * Command specification. This is used to drive the command parser.
+ */
+struct s_format {
+ char code; /* Command code */
+ int naddr; /* Number of address args */
+ enum e_args args; /* Argument type */
+};
+
+static struct s_format cmd_fmts[] = {
+ {'{', 2, GROUP},
+ {'}', 0, ENDGROUP},
+ {'a', 1, TEXT},
+ {'b', 2, BRANCH},
+ {'c', 2, TEXT},
+ {'d', 2, EMPTY},
+ {'D', 2, EMPTY},
+ {'g', 2, EMPTY},
+ {'G', 2, EMPTY},
+ {'h', 2, EMPTY},
+ {'H', 2, EMPTY},
+ {'i', 1, TEXT},
+ {'l', 2, EMPTY},
+ {'n', 2, EMPTY},
+ {'N', 2, EMPTY},
+ {'p', 2, EMPTY},
+ {'P', 2, EMPTY},
+ {'q', 1, EMPTY},
+ {'r', 1, RFILE},
+ {'s', 2, SUBST},
+ {'t', 2, BRANCH},
+ {'w', 2, WFILE},
+ {'x', 2, EMPTY},
+ {'y', 2, TR},
+ {'!', 2, NONSEL},
+ {':', 0, LABEL},
+ {'#', 0, COMMENT},
+ {'=', 1, EMPTY},
+ {'\0', 0, COMMENT},
+};
+
+/* The compiled program. */
+struct s_command *prog;
+
+/*
+ * Compile the program into prog.
+ * Initialise appends.
+ */
+void
+compile(void)
+{
+ *compile_stream(&prog) = NULL;
+ fixuplabel(prog, NULL);
+ uselabel();
+ appends = xreallocarray(NULL, appendnum, sizeof(struct s_appends));
+ match = xreallocarray(NULL, maxnsub + 1, sizeof(regmatch_t));
+}
+
+#define EATSPACE() do { \
+ if (p) \
+ while (isascii((unsigned char)*p) && \
+ isspace((unsigned char)*p)) \
+ p++; \
+ } while (0)
+
+static struct s_command **
+compile_stream(struct s_command **link)
+{
+ char *p;
+ static char *lbuf; /* To avoid excessive malloc calls */
+ static size_t bufsize;
+ struct s_command *cmd, *cmd2, *stack;
+ struct s_format *fp;
+ int naddr; /* Number of addresses */
+
+ stack = 0;
+ for (;;) {
+ if ((p = cu_fgets(&lbuf, &bufsize)) == NULL) {
+ if (stack != 0)
+ err(COMPILE, "unexpected EOF (pending }'s)");
+ return (link);
+ }
+
+semicolon: EATSPACE();
+ if (*p == '#' || *p == '\0')
+ continue;
+ if (*p == ';') {
+ p++;
+ goto semicolon;
+ }
+ *link = cmd = xmalloc(sizeof(struct s_command));
+ link = &cmd->next;
+ cmd->nonsel = cmd->inrange = 0;
+ /* First parse the addresses */
+ naddr = 0;
+
+/* Valid characters to start an address */
+#define addrchar(c) (strchr("0123456789/\\$", (c)))
+ if (addrchar(*p)) {
+ naddr++;
+ cmd->a1 = xmalloc(sizeof(struct s_addr));
+ p = compile_addr(p, cmd->a1);
+ EATSPACE(); /* EXTENSION */
+ if (*p == ',') {
+ p++;
+ EATSPACE(); /* EXTENSION */
+ naddr++;
+ cmd->a2 = xmalloc(sizeof(struct s_addr));
+ p = compile_addr(p, cmd->a2);
+ EATSPACE();
+ } else {
+ cmd->a2 = 0;
+ }
+ } else {
+ cmd->a1 = cmd->a2 = 0;
+ }
+
+nonsel: /* Now parse the command */
+ if (!*p)
+ err(COMPILE, "command expected");
+ cmd->code = *p;
+ for (fp = cmd_fmts; fp->code; fp++)
+ if (fp->code == *p)
+ break;
+ if (!fp->code)
+ err(COMPILE, "invalid command code %c", *p);
+ if (naddr > fp->naddr)
+ err(COMPILE,
+ "command %c expects up to %d address(es), found %d",
+ *p, fp->naddr, naddr);
+ switch (fp->args) {
+ case NONSEL: /* ! */
+ p++;
+ EATSPACE();
+ cmd->nonsel = ! cmd->nonsel;
+ goto nonsel;
+ case GROUP: /* { */
+ p++;
+ EATSPACE();
+ cmd->next = stack;
+ stack = cmd;
+ link = &cmd->u.c;
+ if (*p)
+ goto semicolon;
+ break;
+ case ENDGROUP:
+ /*
+ * Short-circuit command processing, since end of
+ * group is really just a noop.
+ */
+ cmd->nonsel = 1;
+ if (stack == 0)
+ err(COMPILE, "unexpected }");
+ cmd2 = stack;
+ stack = cmd2->next;
+ cmd2->next = cmd;
+ /*FALLTHROUGH*/
+ case EMPTY: /* d D g G h H l n N p P q x = \0 */
+ p++;
+ EATSPACE();
+ if (*p == ';') {
+ p++;
+ link = &cmd->next;
+ goto semicolon;
+ }
+ if (*p)
+ err(COMPILE,
+"extra characters at the end of %c command", cmd->code);
+ break;
+ case TEXT: /* a c i */
+ p++;
+ EATSPACE();
+ if (*p != '\\')
+ err(COMPILE, "command %c expects \\ followed by"
+ " text", cmd->code);
+ p++;
+ EATSPACE();
+ if (*p)
+ err(COMPILE, "extra characters after \\ at the"
+ " end of %c command", cmd->code);
+ cmd->t = compile_text();
+ break;
+ case COMMENT: /* \0 # */
+ break;
+ case WFILE: /* w */
+ p++;
+ EATSPACE();
+ if (*p == '\0')
+ err(COMPILE, "filename expected");
+ cmd->t = duptoeol(p, "w command", NULL);
+ if (aflag)
+ cmd->u.fd = -1;
+ else if ((cmd->u.fd = open(p,
+ O_WRONLY|O_APPEND|O_CREAT|O_TRUNC,
+ DEFFILEMODE)) == -1)
+ err(FATAL, "%s: %s", p, strerror(errno));
+ break;
+ case RFILE: /* r */
+ p++;
+ EATSPACE();
+ cmd->t = duptoeol(p, "read command", NULL);
+ break;
+ case BRANCH: /* b t */
+ p++;
+ EATSPACE();
+ if (*p == '\0')
+ cmd->t = NULL;
+ else
+ cmd->t = duptoeol(p, "branch", &p);
+ if (*p == ';') {
+ p++;
+ goto semicolon;
+ }
+ break;
+ case LABEL: /* : */
+ p++;
+ EATSPACE();
+ cmd->t = duptoeol(p, "label", &p);
+ if (strlen(cmd->t) == 0)
+ err(COMPILE, "empty label");
+ enterlabel(cmd);
+ if (*p == ';') {
+ p++;
+ goto semicolon;
+ }
+ break;
+ case SUBST: /* s */
+ p++;
+ if (*p == '\0' || *p == '\\')
+ err(COMPILE, "substitute pattern can not be"
+ " delimited by newline or backslash");
+ cmd->u.s = xmalloc(sizeof(struct s_subst));
+ p = compile_re(p, &cmd->u.s->re);
+ if (p == NULL)
+ err(COMPILE, "unterminated substitute pattern");
+ --p;
+ p = compile_subst(p, cmd->u.s);
+ p = compile_flags(p, cmd->u.s);
+ EATSPACE();
+ if (*p == ';') {
+ p++;
+ link = &cmd->next;
+ goto semicolon;
+ }
+ break;
+ case TR: /* y */
+ p++;
+ p = compile_tr(p, (char **)&cmd->u.y);
+ EATSPACE();
+ if (*p == ';') {
+ p++;
+ link = &cmd->next;
+ goto semicolon;
+ }
+ if (*p)
+ err(COMPILE, "extra text at the end of a"
+ " transform command");
+ break;
+ }
+ }
+}
+
+/*
+ * Get a delimited string. P points to the delimeter of the string; d points
+ * to a buffer area. Newline and delimiter escapes are processed; other
+ * escapes are ignored.
+ *
+ * Returns a pointer to the first character after the final delimiter or NULL
+ * in the case of a non-terminated string. The character array d is filled
+ * with the processed string.
+ */
+static char *
+compile_delimited(char *p, char *d, int is_tr)
+{
+ char c;
+
+ c = *p++;
+ if (c == '\0')
+ return (NULL);
+ else if (c == '\\')
+ err(COMPILE, "\\ can not be used as a string delimiter");
+ else if (c == '\n')
+ err(COMPILE, "newline can not be used as a string delimiter");
+ while (*p) {
+ if (*p == '[' && *p != c) {
+ if ((d = compile_ccl(&p, d)) == NULL)
+ err(COMPILE, "unbalanced brackets ([])");
+ continue;
+ } else if (*p == '\\' && p[1] == '[') {
+ *d++ = *p++;
+ } else if (*p == '\\' && p[1] == c) {
+ p++;
+ } else if (*p == '\\' && p[1] == 'n') {
+ *d++ = '\n';
+ p += 2;
+ continue;
+ } else if (*p == '\\' && p[1] == '\\') {
+ if (is_tr)
+ p++;
+ else
+ *d++ = *p++;
+ } else if (*p == c) {
+ *d = '\0';
+ return (p + 1);
+ }
+ *d++ = *p++;
+ }
+ return (NULL);
+}
+
+
+/* compile_ccl: expand a POSIX character class */
+static char *
+compile_ccl(char **sp, char *t)
+{
+ int c, d;
+ char *s = *sp;
+
+ *t++ = *s++;
+ if (*s == '^')
+ *t++ = *s++;
+ if (*s == ']')
+ *t++ = *s++;
+ for (; *s && (*t = *s) != ']'; s++, t++)
+ if (*s == '[' && ((d = *(s+1)) == '.' || d == ':' || d == '=')) {
+ *++t = *++s, t++, s++;
+ for (c = *s; (*t = *s) != ']' || c != d; s++, t++)
+ if ((c = *s) == '\0')
+ return NULL;
+ } else if (*s == '\\' && s[1] == 'n') {
+ *t = '\n';
+ s++;
+ }
+ if (*s == ']') {
+ *sp = ++s;
+ return (++t);
+ } else {
+ return (NULL);
+ }
+}
+
+/*
+ * Get a regular expression. P points to the delimiter of the regular
+ * expression; repp points to the address of a regexp pointer. Newline
+ * and delimiter escapes are processed; other escapes are ignored.
+ * Returns a pointer to the first character after the final delimiter
+ * or NULL in the case of a non terminated regular expression. The regexp
+ * pointer is set to the compiled regular expression.
+ * Cflags are passed to regcomp.
+ */
+static char *
+compile_re(char *p, regex_t **repp)
+{
+ int eval;
+ char *re;
+
+ re = xmalloc(strlen(p) + 1); /* strlen(re) <= strlen(p) */
+ p = compile_delimited(p, re, 0);
+ if (p && strlen(re) == 0) {
+ *repp = NULL;
+ free(re);
+ return (p);
+ }
+ *repp = xmalloc(sizeof(regex_t));
+ if (p && (eval = regcomp(*repp, re, Eflag ? REG_EXTENDED : 0)) != 0)
+ err(COMPILE, "RE error: %s", strregerror(eval, *repp));
+ if (maxnsub < (*repp)->re_nsub)
+ maxnsub = (*repp)->re_nsub;
+ free(re);
+ return (p);
+}
+
+/*
+ * Compile the substitution string of a regular expression and set res to
+ * point to a saved copy of it. Nsub is the number of parenthesized regular
+ * expressions.
+ */
+static char *
+compile_subst(char *p, struct s_subst *s)
+{
+ static char *lbuf;
+ static size_t bufsize;
+ int asize, ref, size;
+ char c, *text, *op, *sp;
+ int sawesc = 0;
+
+ c = *p++; /* Terminator character */
+ if (c == '\0')
+ return (NULL);
+
+ s->maxbref = 0;
+ s->linenum = linenum;
+ text = NULL;
+ asize = size = 0;
+ do {
+ size_t len = ROUNDLEN(strlen(p) + 1);
+ if (asize - size < len) {
+ do {
+ asize += len;
+ } while (asize - size < len);
+ text = xrealloc(text, asize);
+ }
+ op = sp = text + size;
+ for (; *p; p++) {
+ if (*p == '\\' || sawesc) {
+ /*
+ * If this is a continuation from the last
+ * buffer, we won't have a character to
+ * skip over.
+ */
+ if (sawesc)
+ sawesc = 0;
+ else
+ p++;
+
+ if (*p == '\0') {
+ /*
+ * This escaped character is continued
+ * in the next part of the line. Note
+ * this fact, then cause the loop to
+ * exit w/ normal EOL case and reenter
+ * above with the new buffer.
+ */
+ sawesc = 1;
+ p--;
+ continue;
+ } else if (strchr("123456789", *p) != NULL) {
+ *sp++ = '\\';
+ ref = *p - '0';
+ if (s->re != NULL &&
+ ref > s->re->re_nsub)
+ err(COMPILE,
+"\\%c not defined in the RE", *p);
+ if (s->maxbref < ref)
+ s->maxbref = ref;
+ } else if (*p == '&' || *p == '\\')
+ *sp++ = '\\';
+ } else if (*p == c) {
+ p++;
+ *sp++ = '\0';
+ size += sp - op;
+ s->new = xrealloc(text, size);
+ return (p);
+ } else if (*p == '\n') {
+ err(COMPILE,
+"unescaped newline inside substitute pattern");
+ /* NOTREACHED */
+ }
+ *sp++ = *p;
+ }
+ size += sp - op;
+ } while ((p = cu_fgets(&lbuf, &bufsize)));
+ err(COMPILE, "unterminated substitute in regular expression");
+ /* NOTREACHED */
+ return NULL;
+}
+
+/*
+ * Compile the flags of the s command
+ */
+static char *
+compile_flags(char *p, struct s_subst *s)
+{
+ int gn; /* True if we have seen g or n */
+ long l;
+ char wfile[PATH_MAX], *q;
+
+ s->n = 1; /* Default */
+ s->p = 0;
+ s->wfile = NULL;
+ s->wfd = -1;
+ for (gn = 0;;) {
+ EATSPACE(); /* EXTENSION */
+ switch (*p) {
+ case 'g':
+ if (gn)
+ err(COMPILE, "more than one number or 'g' in"
+ " substitute flags");
+ gn = 1;
+ s->n = 0;
+ break;
+ case '\0':
+ case '\n':
+ case ';':
+ return (p);
+ case 'p':
+ s->p = 1;
+ break;
+ case '1': case '2': case '3':
+ case '4': case '5': case '6':
+ case '7': case '8': case '9':
+ if (gn)
+ err(COMPILE, "more than one number or 'g' in"
+ " substitute flags");
+ gn = 1;
+ l = strtol(p, &p, 10);
+ if (l <= 0 || l >= INT_MAX)
+ err(COMPILE,
+ "number in substitute flags out of range");
+ s->n = (int)l;
+ continue;
+ case 'w':
+ p++;
+#ifdef HISTORIC_PRACTICE
+ if (*p != ' ') {
+ err(WARNING, "space missing before w wfile");
+ return (p);
+ }
+#endif
+ EATSPACE();
+ q = wfile;
+ while (*p) {
+ if (*p == '\n')
+ break;
+ *q++ = *p++;
+ }
+ *q = '\0';
+ if (q == wfile)
+ err(COMPILE, "no wfile specified");
+ s->wfile = strdup(wfile);
+ if (!aflag && (s->wfd = open(wfile,
+ O_WRONLY|O_APPEND|O_CREAT|O_TRUNC,
+ DEFFILEMODE)) == -1)
+ err(FATAL, "%s: %s", wfile, strerror(errno));
+ return (p);
+ default:
+ err(COMPILE,
+ "bad flag in substitute command: '%c'", *p);
+ break;
+ }
+ p++;
+ }
+}
+
+/*
+ * Compile a translation set of strings into a lookup table.
+ */
+static char *
+compile_tr(char *p, char **transtab)
+{
+ int i;
+ char *lt, *op, *np;
+ char *old = NULL, *new = NULL;
+
+ if (*p == '\0' || *p == '\\')
+ err(COMPILE,
+"transform pattern can not be delimited by newline or backslash");
+ old = xmalloc(strlen(p) + 1);
+ p = compile_delimited(p, old, 1);
+ if (p == NULL) {
+ err(COMPILE, "unterminated transform source string");
+ goto bad;
+ }
+ new = xmalloc(strlen(p) + 1);
+ p = compile_delimited(--p, new, 1);
+ if (p == NULL) {
+ err(COMPILE, "unterminated transform target string");
+ goto bad;
+ }
+ EATSPACE();
+ if (strlen(new) != strlen(old)) {
+ err(COMPILE, "transform strings are not the same length");
+ goto bad;
+ }
+ /* We assume characters are 8 bits */
+ lt = xmalloc(UCHAR_MAX + 1);
+ for (i = 0; i <= UCHAR_MAX; i++)
+ lt[i] = (char)i;
+ for (op = old, np = new; *op; op++, np++)
+ lt[(u_char)*op] = *np;
+ *transtab = lt;
+ free(old);
+ free(new);
+ return (p);
+bad:
+ free(old);
+ free(new);
+ return (NULL);
+}
+
+/*
+ * Compile the text following an a, c, or i command.
+ */
+static char *
+compile_text(void)
+{
+ int asize, esc_nl, size;
+ char *lbuf, *text, *p, *op, *s;
+ size_t bufsize;
+
+ lbuf = text = NULL;
+ asize = size = 0;
+ while ((p = cu_fgets(&lbuf, &bufsize))) {
+ size_t len = ROUNDLEN(strlen(p) + 1);
+ if (asize - size < len) {
+ do {
+ asize += len;
+ } while (asize - size < len);
+ text = xrealloc(text, asize);
+ }
+ op = s = text + size;
+ for (esc_nl = 0; *p != '\0'; p++) {
+ if (*p == '\\' && p[1] != '\0' && *++p == '\n')
+ esc_nl = 1;
+ *s++ = *p;
+ }
+ size += s - op;
+ if (!esc_nl) {
+ *s = '\0';
+ break;
+ }
+ }
+ free(lbuf);
+ text = xrealloc(text, size + 1);
+ text[size] = '\0';
+ return (text);
+}
+
+/*
+ * Get an address and return a pointer to the first character after
+ * it. Fill the structure pointed to according to the address.
+ */
+static char *
+compile_addr(char *p, struct s_addr *a)
+{
+ char *end;
+
+ switch (*p) {
+ case '\\': /* Context address */
+ ++p;
+ /* FALLTHROUGH */
+ case '/': /* Context address */
+ p = compile_re(p, &a->u.r);
+ if (p == NULL)
+ err(COMPILE, "unterminated regular expression");
+ a->type = AT_RE;
+ return (p);
+
+ case '$': /* Last line */
+ a->type = AT_LAST;
+ return (p + 1);
+ /* Line number */
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ a->type = AT_LINE;
+ a->u.l = strtoul(p, &end, 10);
+ return (end);
+ default:
+ err(COMPILE, "expected context address");
+ return (NULL);
+ }
+}
+
+/*
+ * duptoeol --
+ * Return a copy of all the characters up to \n or \0.
+ */
+static char *
+duptoeol(char *s, char *ctype, char **semi)
+{
+ size_t len;
+ int ws;
+ char *start;
+
+ ws = 0;
+ if (semi) {
+ for (start = s; *s != '\0' && *s != '\n' && *s != ';'; ++s)
+ ws = isspace((unsigned char)*s);
+ } else {
+ for (start = s; *s != '\0' && *s != '\n'; ++s)
+ ws = isspace((unsigned char)*s);
+ *s = '\0';
+ }
+ if (ws)
+ err(WARNING, "whitespace after %s", ctype);
+ len = s - start + 1;
+ if (semi)
+ *semi = s;
+ s = xmalloc(len);
+ strlcpy(s, start, len);
+ return (s);
+}
+
+/*
+ * Convert goto label names to addresses, and count a and r commands, in
+ * the given subset of the script. Free the memory used by labels in b
+ * and t commands (but not by :).
+ *
+ * TODO: Remove } nodes
+ */
+static void
+fixuplabel(struct s_command *cp, struct s_command *end)
+{
+
+ for (; cp != end; cp = cp->next)
+ switch (cp->code) {
+ case 'a':
+ case 'r':
+ appendnum++;
+ break;
+ case 'b':
+ case 't':
+ /* Resolve branch target. */
+ if (cp->t == NULL) {
+ cp->u.c = NULL;
+ break;
+ }
+ if ((cp->u.c = findlabel(cp->t)) == NULL)
+ err(COMPILE2, "undefined label '%s'", cp->t);
+ free(cp->t);
+ break;
+ case '{':
+ /* Do interior commands. */
+ fixuplabel(cp->u.c, cp->next);
+ break;
+ }
+}
+
+/*
+ * Associate the given command label for later lookup.
+ */
+static void
+enterlabel(struct s_command *cp)
+{
+ struct labhash **lhp, *lh;
+ u_char *p;
+ u_int h, c;
+
+ for (h = 0, p = (u_char *)cp->t; (c = *p) != 0; p++)
+ h = (h << 5) + h + c;
+ lhp = &labels[h & LHMASK];
+ for (lh = *lhp; lh != NULL; lh = lh->lh_next)
+ if (lh->lh_hash == h && strcmp(cp->t, lh->lh_cmd->t) == 0)
+ err(COMPILE2, "duplicate label '%s'", cp->t);
+ lh = xmalloc(sizeof *lh);
+ lh->lh_next = *lhp;
+ lh->lh_hash = h;
+ lh->lh_cmd = cp;
+ lh->lh_ref = 0;
+ *lhp = lh;
+}
+
+/*
+ * Find the label contained in the command l in the command linked
+ * list cp. L is excluded from the search. Return NULL if not found.
+ */
+static struct s_command *
+findlabel(char *name)
+{
+ struct labhash *lh;
+ u_char *p;
+ u_int h, c;
+
+ for (h = 0, p = (u_char *)name; (c = *p) != 0; p++)
+ h = (h << 5) + h + c;
+ for (lh = labels[h & LHMASK]; lh != NULL; lh = lh->lh_next) {
+ if (lh->lh_hash == h && strcmp(name, lh->lh_cmd->t) == 0) {
+ lh->lh_ref = 1;
+ return (lh->lh_cmd);
+ }
+ }
+ return (NULL);
+}
+
+/*
+ * Warn about any unused labels. As a side effect, release the label hash
+ * table space.
+ */
+static void
+uselabel(void)
+{
+ struct labhash *lh, *next;
+ int i;
+
+ for (i = 0; i < LHSZ; i++) {
+ for (lh = labels[i]; lh != NULL; lh = next) {
+ next = lh->lh_next;
+ if (!lh->lh_ref)
+ err(WARNING, "unused label '%s'",
+ lh->lh_cmd->t);
+ free(lh);
+ }
+ }
+}
diff --git a/sed/defs.h b/sed/defs.h
@@ -0,0 +1,148 @@
+/* * $OpenBSD: defs.h,v 1.4 2008/10/16 16:34:32 millert Exp $*/
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)defs.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * Types of address specifications
+ */
+enum e_atype {
+ AT_RE, /* Line that match RE */
+ AT_LINE, /* Specific line */
+ AT_LAST, /* Last line */
+};
+
+/*
+ * Format of an address
+ */
+struct s_addr {
+ enum e_atype type; /* Address type */
+ union {
+ u_long l; /* Line number */
+ regex_t *r; /* Regular expression */
+ } u;
+};
+
+/*
+ * Substitution command
+ */
+struct s_subst {
+ int n; /* Occurrence to subst. */
+ int p; /* True if p flag */
+ char *wfile; /* NULL if no wfile */
+ int wfd; /* Cached file descriptor */
+ regex_t *re; /* Regular expression */
+ int maxbref; /* Largest backreference. */
+ u_long linenum; /* Line number. */
+ char *new; /* Replacement text */
+};
+
+
+/*
+ * An internally compiled command.
+ * Initialy, label references are stored in t, on a second pass they
+ * are updated to pointers.
+ */
+struct s_command {
+ struct s_command *next; /* Pointer to next command */
+ struct s_addr *a1, *a2; /* Start and end address */
+ char *t; /* Text for : a c i r w */
+ union {
+ struct s_command *c; /* Command(s) for b t { */
+ struct s_subst *s; /* Substitute command */
+ u_char *y; /* Replace command array */
+ int fd; /* File descriptor for w */
+ } u;
+ char code; /* Command code */
+ u_int nonsel:1; /* True if ! */
+ u_int inrange:1; /* True if in range */
+};
+
+/*
+ * Types of command arguments recognised by the parser
+ */
+enum e_args {
+ EMPTY, /* d D g G h H l n N p P q x = \0 */
+ TEXT, /* a c i */
+ NONSEL, /* ! */
+ GROUP, /* { */
+ ENDGROUP, /* } */
+ COMMENT, /* # */
+ BRANCH, /* b t */
+ LABEL, /* : */
+ RFILE, /* r */
+ WFILE, /* w */
+ SUBST, /* s */
+ TR /* y */
+};
+
+/*
+ * Structure containing things to append before a line is read
+ */
+struct s_appends {
+ enum {AP_STRING, AP_FILE} type;
+ char *s;
+ size_t len;
+};
+
+enum e_spflag {
+ APPEND, /* Append to the contents. */
+ REPLACE, /* Replace the contents. */
+};
+
+/*
+ * Structure for a space (process, hold, otherwise).
+ */
+typedef struct {
+ char *space; /* Current space pointer. */
+ size_t len; /* Current length. */
+ int deleted; /* If deleted. */
+ char *back; /* Backing memory. */
+ size_t blen; /* Backing memory length. */
+} SPACE;
+
+/*
+ * Error severity codes:
+ */
+#define FATAL 0 /* Exit immediately with 1 */
+#define ERROR 1 /* Continue, but change exit value */
+#define WARNING 2 /* Just print the warning */
+#define COMPILE 3 /* Print error, count and finish script */
+#define COMPILE2 3 /* Print error, count and finish script */
+
+/*
+ * Round up to the nearest multiple of _POSIX2_LINE_MAX
+ */
+#define ROUNDLEN(x) \
+ (((x) + _POSIX2_LINE_MAX - 1) & ~(_POSIX2_LINE_MAX - 1))
diff --git a/sed/extern.h b/sed/extern.h
@@ -0,0 +1,57 @@
+/* * $OpenBSD: extern.h,v 1.7 2014/10/08 04:19:08 deraadt Exp $*/
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)extern.h 8.1 (Berkeley) 6/6/93
+ */
+
+extern struct s_command *prog;
+extern struct s_appends *appends;
+extern regmatch_t *match;
+extern size_t maxnsub;
+extern u_long linenum;
+extern int appendnum;
+extern int lastline;
+extern int Eflag, aflag, eflag, nflag;
+extern char *fname;
+
+void cfclose(struct s_command *, struct s_command *);
+void compile(void);
+void cspace(SPACE *, char *, size_t, enum e_spflag);
+char *cu_fgets(char **, size_t *);
+void err(int, const char *, ...);
+int mf_fgets(SPACE *, enum e_spflag);
+void process(void);
+char *strregerror(int, regex_t *);
+void *xmalloc(size_t);
+void *xreallocarray(void *, size_t, size_t);
+void *xrealloc(void *, size_t);
diff --git a/sed/fgetln.c b/sed/fgetln.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright © 2005 Hector Garcia Alvarez
+ * Copyright © 2005, 2008-2012 Guillem Jover <guillem@hadrons.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <string.h>
+
+struct filebuf {
+ FILE *fp;
+ char *buf;
+ size_t len;
+};
+
+#define FILEBUF_POOL_ITEMS 32
+
+static struct filebuf fb_pool[FILEBUF_POOL_ITEMS];
+static int fb_pool_cur;
+
+char *
+fgetln(FILE *stream, size_t *len)
+{
+ struct filebuf *fb;
+ ssize_t nread;
+
+ /* Try to diminish the possibility of several fgetln() calls being
+ * used on different streams, by using a pool of buffers per file. */
+ fb = &fb_pool[fb_pool_cur];
+ if (fb->fp != stream && fb->fp != NULL) {
+ fb_pool_cur++;
+ fb_pool_cur %= FILEBUF_POOL_ITEMS;
+ fb = &fb_pool[fb_pool_cur];
+ }
+ fb->fp = stream;
+
+ nread = getline(&fb->buf, &fb->len, stream);
+ /* Note: the getdelim/getline API ensures nread != 0. */
+ if (nread == -1) {
+ *len = 0;
+ return NULL;
+ } else {
+ *len = (size_t)nread;
+ return fb->buf;
+ }
+}
diff --git a/sed/main.c b/sed/main.c
@@ -0,0 +1,358 @@
+/* $OpenBSD: main.c,v 1.17 2009/10/27 23:59:43 deraadt Exp $ */
+
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <regex.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "defs.h"
+#include "extern.h"
+#include "util.h"
+
+/*
+ * Linked list of units (strings and files) to be compiled
+ */
+struct s_compunit {
+ struct s_compunit *next;
+ enum e_cut {CU_FILE, CU_STRING} type;
+ char *s; /* Pointer to string or fname */
+};
+
+/*
+ * Linked list pointer to compilation units and pointer to current
+ * next pointer.
+ */
+static struct s_compunit *script, **cu_nextp = &script;
+
+/*
+ * Linked list of files to be processed
+ */
+struct s_flist {
+ char *fname;
+ struct s_flist *next;
+};
+
+/*
+ * Linked list pointer to files and pointer to current
+ * next pointer.
+ */
+static struct s_flist *files, **fl_nextp = &files;
+
+int Eflag, aflag, eflag, nflag;
+
+/*
+ * Current file and line number; line numbers restart across compilation
+ * units, but span across input files.
+ */
+char *fname; /* File name. */
+u_long linenum;
+int lastline; /* TRUE on the last line of the last file */
+
+static void add_compunit(enum e_cut, char *);
+static void add_file(char *);
+
+int
+main(int argc, char *argv[])
+{
+ int c, fflag;
+
+ fflag = 0;
+ while ((c = getopt(argc, argv, "Eae:f:nru")) != -1)
+ switch (c) {
+ case 'E':
+ case 'r':
+ Eflag = 1;
+ break;
+ case 'a':
+ aflag = 1;
+ break;
+ case 'e':
+ eflag = 1;
+ add_compunit(CU_STRING, optarg);
+ break;
+ case 'f':
+ fflag = 1;
+ add_compunit(CU_FILE, optarg);
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'u':
+ setlinebuf(stdout);
+ break;
+ default:
+ case '?':
+ (void)fprintf(stderr,
+ "usage: sed [-aEnru] command [file ...]\n"
+ " sed [-aEnru] [-e command] [-f command_file] [file ...]\n");
+ exit(1);
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* First usage case; script is the first arg */
+ if (!eflag && !fflag && *argv) {
+ add_compunit(CU_STRING, *argv);
+ argv++;
+ }
+
+ compile();
+
+ /* Continue with first and start second usage */
+ if (*argv)
+ for (; *argv; argv++)
+ add_file(*argv);
+ else
+ add_file(NULL);
+ process();
+ cfclose(prog, NULL);
+ if (fclose(stdout))
+ err(FATAL, "stdout: %s", strerror(errno));
+ exit (0);
+}
+
+/*
+ * Like fgets, but go through the chain of compilation units chaining them
+ * together. Empty strings and files are ignored.
+ */
+char *
+cu_fgets(char **outbuf, size_t *outsize)
+{
+ static enum {ST_EOF, ST_FILE, ST_STRING} state = ST_EOF;
+ static FILE *f; /* Current open file */
+ static char *s; /* Current pointer inside string */
+ static char string_ident[30];
+ size_t len;
+ char *p;
+
+ if (*outbuf == NULL)
+ *outsize = 0;
+
+again:
+ switch (state) {
+ case ST_EOF:
+ if (script == NULL)
+ return (NULL);
+ linenum = 0;
+ switch (script->type) {
+ case CU_FILE:
+ if ((f = fopen(script->s, "r")) == NULL)
+ err(FATAL,
+ "%s: %s", script->s, strerror(errno));
+ fname = script->s;
+ state = ST_FILE;
+ goto again;
+ case CU_STRING:
+ if ((snprintf(string_ident,
+ sizeof(string_ident), "\"%s\"", script->s)) >=
+ sizeof(string_ident))
+ strlcpy(string_ident +
+ sizeof(string_ident) - 6, " ...\"", 5);
+ fname = string_ident;
+ s = script->s;
+ state = ST_STRING;
+ goto again;
+ }
+ case ST_FILE:
+ if ((p = fgetln(f, &len)) != NULL) {
+ linenum++;
+ if (len >= *outsize) {
+ free(*outbuf);
+ *outsize = ROUNDLEN(len + 1);
+ *outbuf = xmalloc(*outsize);
+ }
+ memcpy(*outbuf, p, len);
+ (*outbuf)[len] = '\0';
+ if (linenum == 1 && p[0] == '#' && p[1] == 'n')
+ nflag = 1;
+ return (*outbuf);
+ }
+ script = script->next;
+ (void)fclose(f);
+ state = ST_EOF;
+ goto again;
+ case ST_STRING:
+ if (linenum == 0 && s[0] == '#' && s[1] == 'n')
+ nflag = 1;
+ p = *outbuf;
+ len = *outsize;
+ for (;;) {
+ if (len <= 1) {
+ *outbuf = xrealloc(*outbuf,
+ *outsize + _POSIX2_LINE_MAX);
+ p = *outbuf + *outsize - len;
+ len += _POSIX2_LINE_MAX;
+ *outsize += _POSIX2_LINE_MAX;
+ }
+ switch (*s) {
+ case '\0':
+ state = ST_EOF;
+ if (s == script->s) {
+ script = script->next;
+ goto again;
+ } else {
+ script = script->next;
+ *p = '\0';
+ linenum++;
+ return (*outbuf);
+ }
+ case '\n':
+ *p++ = '\n';
+ *p = '\0';
+ s++;
+ linenum++;
+ return (*outbuf);
+ default:
+ *p++ = *s++;
+ len--;
+ }
+ }
+ }
+ /* NOTREACHED */
+ return NULL;
+}
+
+/*
+ * Like fgets, but go through the list of files chaining them together.
+ * Set len to the length of the line.
+ */
+int
+mf_fgets(SPACE *sp, enum e_spflag spflag)
+{
+ static FILE *f; /* Current open file */
+ size_t len;
+ char *p;
+ int c;
+
+ if (f == NULL)
+ /* Advance to first non-empty file */
+ for (;;) {
+ if (files == NULL) {
+ lastline = 1;
+ return (0);
+ }
+ if (files->fname == NULL) {
+ f = stdin;
+ fname = "stdin";
+ } else {
+ fname = files->fname;
+ if ((f = fopen(fname, "r")) == NULL)
+ err(FATAL, "%s: %s",
+ fname, strerror(errno));
+ }
+ if ((c = getc(f)) != EOF) {
+ (void)ungetc(c, f);
+ break;
+ }
+ (void)fclose(f);
+ files = files->next;
+ }
+
+ if (lastline) {
+ sp->len = 0;
+ return (0);
+ }
+
+ /*
+ * Use fgetln so that we can handle essentially infinite input data.
+ * Can't use the pointer into the stdio buffer as the process space
+ * because the ungetc() can cause it to move.
+ */
+ p = fgetln(f, &len);
+ if (ferror(f))
+ err(FATAL, "%s: %s", fname, strerror(errno ? errno : EIO));
+ cspace(sp, p, len, spflag);
+
+ linenum++;
+ /* Advance to next non-empty file */
+ while ((c = getc(f)) == EOF) {
+ (void)fclose(f);
+ files = files->next;
+ if (files == NULL) {
+ lastline = 1;
+ return (1);
+ }
+ if (files->fname == NULL) {
+ f = stdin;
+ fname = "stdin";
+ } else {
+ fname = files->fname;
+ if ((f = fopen(fname, "r")) == NULL)
+ err(FATAL, "%s: %s", fname, strerror(errno));
+ }
+ }
+ (void)ungetc(c, f);
+ return (1);
+}
+
+/*
+ * Add a compilation unit to the linked list
+ */
+static void
+add_compunit(enum e_cut type, char *s)
+{
+ struct s_compunit *cu;
+
+ cu = xmalloc(sizeof(struct s_compunit));
+ cu->type = type;
+ cu->s = s;
+ cu->next = NULL;
+ *cu_nextp = cu;
+ cu_nextp = &cu->next;
+}
+
+/*
+ * Add a file to the linked list
+ */
+static void
+add_file(char *s)
+{
+ struct s_flist *fp;
+
+ fp = xmalloc(sizeof(struct s_flist));
+ fp->next = NULL;
+ *fl_nextp = fp;
+ fp->fname = s;
+ fl_nextp = &fp->next;
+}
diff --git a/sed/misc.c b/sed/misc.c
@@ -0,0 +1,124 @@
+/* $OpenBSD: misc.c,v 1.10 2014/10/08 04:19:08 deraadt Exp $ */
+
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "defs.h"
+#include "extern.h"
+#include "util.h"
+
+/*
+ * malloc with result test
+ */
+void *
+xmalloc(size_t size)
+{
+ void *p;
+
+ if ((p = malloc(size)) == NULL)
+ err(FATAL, "%s", strerror(errno));
+ return (p);
+}
+
+void *
+xreallocarray(void *o, size_t nmemb, size_t size)
+{
+ void *p;
+
+ if ((p = reallocarray(o, nmemb, size)) == NULL)
+ err(FATAL, "%s", strerror(errno));
+ return (p);
+}
+
+/*
+ * realloc with result test
+ */
+void *
+xrealloc(void *p, size_t size)
+{
+
+ if ((p = realloc(p, size)) == NULL)
+ err(FATAL, "%s", strerror(errno));
+ return (p);
+}
+
+/*
+ * Return a string for a regular expression error passed. This is a overkill,
+ * because of the silly semantics of regerror (we can never know the size of
+ * the buffer).
+ */
+char *
+strregerror(int errcode, regex_t *preg)
+{
+ static char *oe;
+ size_t s;
+
+ free(oe);
+ s = regerror(errcode, preg, "", 0);
+ oe = xmalloc(s);
+ (void)regerror(errcode, preg, oe, s);
+ return (oe);
+}
+
+/*
+ * Error reporting function
+ */
+void
+err(int severity, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)fprintf(stderr, "sed: ");
+ switch (severity) {
+ case WARNING:
+ case COMPILE:
+ (void)fprintf(stderr, "%lu: %s: ", linenum, fname);
+ }
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ (void)fprintf(stderr, "\n");
+ if (severity == WARNING)
+ return;
+ exit(1);
+ /* NOTREACHED */
+}
diff --git a/sed/process.c b/sed/process.c
@@ -0,0 +1,625 @@
+/* $OpenBSD: process.c,v 1.19 2013/11/28 18:24:55 deraadt Exp $ */
+
+/*-
+ * Copyright (c) 1992 Diomidis Spinellis.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Diomidis Spinellis of Imperial College, University of London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "defs.h"
+#include "extern.h"
+#include "util.h"
+
+static SPACE HS, PS, SS;
+#define pd PS.deleted
+#define ps PS.space
+#define psl PS.len
+#define hs HS.space
+#define hsl HS.len
+
+static inline int applies(struct s_command *);
+static void flush_appends(void);
+static void lputs(char *);
+static inline int regexec_e(regex_t *, char *, int, int, size_t);
+static void regsub(SPACE *, char *, char *);
+static int substitute(struct s_command *);
+
+struct s_appends *appends; /* Array of pointers to strings to append. */
+static int appendx; /* Index into appends array. */
+int appendnum; /* Size of appends array. */
+
+static int lastaddr; /* Set by applies if last address of a range. */
+static int sdone; /* If any substitutes since last line input. */
+ /* Iov structure for 'w' commands. */
+static regex_t *defpreg;
+size_t maxnsub;
+regmatch_t *match;
+
+#define OUT(s) do { fwrite(s, sizeof(u_char), psl, stdout); } while (0)
+
+void
+process(void)
+{
+ struct s_command *cp;
+ SPACE tspace;
+ size_t len, oldpsl;
+ char *p;
+
+ for (linenum = 0; mf_fgets(&PS, REPLACE);) {
+ pd = 0;
+top:
+ cp = prog;
+redirect:
+ while (cp != NULL) {
+ if (!applies(cp)) {
+ cp = cp->next;
+ continue;
+ }
+ switch (cp->code) {
+ case '{':
+ cp = cp->u.c;
+ goto redirect;
+ case 'a':
+ if (appendx >= appendnum) {
+ appends = xrealloc(appends,
+ sizeof(struct s_appends) *
+ (appendnum * 2));
+ appendnum *= 2;
+ }
+ appends[appendx].type = AP_STRING;
+ appends[appendx].s = cp->t;
+ appends[appendx].len = strlen(cp->t);
+ appendx++;
+ break;
+ case 'b':
+ cp = cp->u.c;
+ goto redirect;
+ case 'c':
+ pd = 1;
+ psl = 0;
+ if (cp->a2 == NULL || lastaddr)
+ (void)printf("%s", cp->t);
+ break;
+ case 'd':
+ pd = 1;
+ goto new;
+ case 'D':
+ if (pd)
+ goto new;
+ if (psl == 0 ||
+ (p = memchr(ps, '\n', psl - 1)) == NULL) {
+ pd = 1;
+ goto new;
+ } else {
+ psl -= (p + 1) - ps;
+ memmove(ps, p + 1, psl);
+ goto top;
+ }
+ case 'g':
+ cspace(&PS, hs, hsl, REPLACE);
+ break;
+ case 'G':
+ if (hs == NULL)
+ cspace(&HS, "\n", 1, REPLACE);
+ cspace(&PS, hs, hsl, 0);
+ break;
+ case 'h':
+ cspace(&HS, ps, psl, REPLACE);
+ break;
+ case 'H':
+ cspace(&HS, ps, psl, 0);
+ break;
+ case 'i':
+ (void)printf("%s", cp->t);
+ break;
+ case 'l':
+ lputs(ps);
+ break;
+ case 'n':
+ if (!nflag && !pd)
+ OUT(ps);
+ flush_appends();
+ if (!mf_fgets(&PS, REPLACE))
+ exit(0);
+ pd = 0;
+ break;
+ case 'N':
+ flush_appends();
+ if (!mf_fgets(&PS, 0)) {
+ if (!nflag && !pd)
+ OUT(ps);
+ exit(0);
+ }
+ break;
+ case 'p':
+ if (pd)
+ break;
+ OUT(ps);
+ break;
+ case 'P':
+ if (pd)
+ break;
+ if (psl != 0 &&
+ (p = memchr(ps, '\n', psl - 1)) != NULL) {
+ oldpsl = psl;
+ psl = (p + 1) - ps;
+ }
+ OUT(ps);
+ if (p != NULL)
+ psl = oldpsl;
+ break;
+ case 'q':
+ if (!nflag && !pd)
+ OUT(ps);
+ flush_appends();
+ exit(0);
+ case 'r':
+ if (appendx >= appendnum)
+ appends = xrealloc(appends,
+ sizeof(struct s_appends) *
+ (appendnum *= 2));
+ appends[appendx].type = AP_FILE;
+ appends[appendx].s = cp->t;
+ appends[appendx].len = strlen(cp->t);
+ appendx++;
+ break;
+ case 's':
+ sdone |= substitute(cp);
+ break;
+ case 't':
+ if (sdone) {
+ sdone = 0;
+ cp = cp->u.c;
+ goto redirect;
+ }
+ break;
+ case 'w':
+ if (pd)
+ break;
+ if (cp->u.fd == -1 && (cp->u.fd = open(cp->t,
+ O_WRONLY|O_APPEND|O_CREAT|O_TRUNC,
+ DEFFILEMODE)) == -1)
+ err(FATAL, "%s: %s",
+ cp->t, strerror(errno));
+ if (write(cp->u.fd, ps, psl) != psl)
+ err(FATAL, "%s: %s",
+ cp->t, strerror(errno));
+ break;
+ case 'x':
+ if (hs == NULL)
+ cspace(&HS, "\n", 1, REPLACE);
+ tspace = PS;
+ PS = HS;
+ HS = tspace;
+ break;
+ case 'y':
+ if (pd || psl == 0)
+ break;
+ for (p = ps, len = psl; --len; ++p)
+ *p = cp->u.y[(unsigned char)*p];
+ break;
+ case ':':
+ case '}':
+ break;
+ case '=':
+ (void)printf("%lu\n", linenum);
+ }
+ cp = cp->next;
+ } /* for all cp */
+
+new: if (!nflag && !pd)
+ OUT(ps);
+ flush_appends();
+ } /* for all lines */
+}
+
+/*
+ * TRUE if the address passed matches the current program state
+ * (lastline, linenumber, ps).
+ */
+#define MATCH(a) \
+ (a)->type == AT_RE ? regexec_e((a)->u.r, ps, 0, 1, psl) : \
+ (a)->type == AT_LINE ? linenum == (a)->u.l : lastline
+
+/*
+ * Return TRUE if the command applies to the current line. Sets the inrange
+ * flag to process ranges. Interprets the non-select (``!'') flag.
+ */
+static inline int
+applies(struct s_command *cp)
+{
+ int r;
+
+ lastaddr = 0;
+ if (cp->a1 == NULL && cp->a2 == NULL)
+ r = 1;
+ else if (cp->a2)
+ if (cp->inrange) {
+ if (MATCH(cp->a2)) {
+ cp->inrange = 0;
+ lastaddr = 1;
+ }
+ r = 1;
+ } else if (MATCH(cp->a1)) {
+ /*
+ * If the second address is a number less than or
+ * equal to the line number first selected, only
+ * one line shall be selected.
+ * -- POSIX 1003.2
+ */
+ if (cp->a2->type == AT_LINE &&
+ linenum >= cp->a2->u.l)
+ lastaddr = 1;
+ else
+ cp->inrange = 1;
+ r = 1;
+ } else
+ r = 0;
+ else
+ r = MATCH(cp->a1);
+ return (cp->nonsel ? !r : r);
+}
+
+/*
+ * substitute --
+ * Do substitutions in the pattern space. Currently, we build a
+ * copy of the new pattern space in the substitute space structure
+ * and then swap them.
+ */
+static int
+substitute(struct s_command *cp)
+{
+ SPACE tspace;
+ regex_t *re;
+ regoff_t slen;
+ int n, lastempty;
+ char *s;
+
+ s = ps;
+ re = cp->u.s->re;
+ if (re == NULL) {
+ if (defpreg != NULL && cp->u.s->maxbref > defpreg->re_nsub) {
+ linenum = cp->u.s->linenum;
+ err(COMPILE, "\\%d not defined in the RE",
+ cp->u.s->maxbref);
+ }
+ }
+ if (!regexec_e(re, s, 0, 0, psl))
+ return (0);
+
+ SS.len = 0; /* Clean substitute space. */
+ slen = psl;
+ n = cp->u.s->n;
+ lastempty = 1;
+
+ do {
+ /* Copy the leading retained string. */
+ if (n <= 1 && match[0].rm_so)
+ cspace(&SS, s, match[0].rm_so, APPEND);
+
+ /* Skip zero-length matches right after other matches. */
+ if (lastempty || match[0].rm_so ||
+ match[0].rm_so != match[0].rm_eo) {
+ if (n <= 1) {
+ /* Want this match: append replacement. */
+ regsub(&SS, s, cp->u.s->new);
+ if (n == 1)
+ n = -1;
+ } else {
+ /* Want a later match: append original. */
+ if (match[0].rm_eo)
+ cspace(&SS, s, match[0].rm_eo, APPEND);
+ n--;
+ }
+ }
+
+ /* Move past this match. */
+ s += match[0].rm_eo;
+ slen -= match[0].rm_eo;
+
+ /*
+ * After a zero-length match, advance one byte,
+ * and at the end of the line, terminate.
+ */
+ if (match[0].rm_so == match[0].rm_eo) {
+ if (*s == '\0' || *s == '\n')
+ slen = -1;
+ else
+ slen--;
+ if (*s != '\0')
+ cspace(&SS, s++, 1, APPEND);
+ lastempty = 1;
+ } else
+ lastempty = 0;
+
+ } while (n >= 0 && slen >= 0 && regexec_e(re, s, REG_NOTBOL, 0, slen));
+
+ /* Did not find the requested number of matches. */
+ if (n > 1)
+ return (0);
+
+ /* Copy the trailing retained string. */
+ if (slen > 0)
+ cspace(&SS, s, slen, APPEND);
+
+ /*
+ * Swap the substitute space and the pattern space, and make sure
+ * that any leftover pointers into stdio memory get lost.
+ */
+ tspace = PS;
+ PS = SS;
+ SS = tspace;
+ SS.space = SS.back;
+
+ /* Handle the 'p' flag. */
+ if (cp->u.s->p)
+ OUT(ps);
+
+ /* Handle the 'w' flag. */
+ if (cp->u.s->wfile && !pd) {
+ if (cp->u.s->wfd == -1 && (cp->u.s->wfd = open(cp->u.s->wfile,
+ O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, DEFFILEMODE)) == -1)
+ err(FATAL, "%s: %s", cp->u.s->wfile, strerror(errno));
+ if (write(cp->u.s->wfd, ps, psl) != psl)
+ err(FATAL, "%s: %s", cp->u.s->wfile, strerror(errno));
+ }
+ return (1);
+}
+
+/*
+ * Flush append requests. Always called before reading a line,
+ * therefore it also resets the substitution done (sdone) flag.
+ */
+static void
+flush_appends(void)
+{
+ FILE *f;
+ int count, i;
+ char buf[8 * 1024];
+
+ for (i = 0; i < appendx; i++)
+ switch (appends[i].type) {
+ case AP_STRING:
+ fwrite(appends[i].s, sizeof(char), appends[i].len,
+ stdout);
+ break;
+ case AP_FILE:
+ /*
+ * Read files probably shouldn't be cached. Since
+ * it's not an error to read a non-existent file,
+ * it's possible that another program is interacting
+ * with the sed script through the file system. It
+ * would be truly bizarre, but possible. It's probably
+ * not that big a performance win, anyhow.
+ */
+ if ((f = fopen(appends[i].s, "r")) == NULL)
+ break;
+ while ((count = fread(buf, sizeof(char), sizeof(buf), f)))
+ (void)fwrite(buf, sizeof(char), count, stdout);
+ (void)fclose(f);
+ break;
+ }
+ if (ferror(stdout))
+ err(FATAL, "stdout: %s", strerror(errno ? errno : EIO));
+ appendx = sdone = 0;
+}
+
+static void
+lputs(char *s)
+{
+ int count;
+ char *escapes, *p;
+ struct winsize win;
+ static int termwidth = -1;
+
+ if (termwidth == -1) {
+ if ((p = getenv("COLUMNS")))
+ termwidth = atoi(p);
+ else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 &&
+ win.ws_col > 0)
+ termwidth = win.ws_col;
+ else
+ termwidth = 60;
+ }
+
+ for (count = 0; *s; ++s) {
+ if (count >= termwidth) {
+ (void)printf("\\\n");
+ count = 0;
+ }
+ if (isascii((unsigned char)*s) && isprint((unsigned char)*s)
+ && *s != '\\') {
+ (void)putchar(*s);
+ count++;
+ } else if (*s != '\n') {
+ escapes = "\\\a\b\f\r\t\v";
+ (void)putchar('\\');
+ if ((p = strchr(escapes, *s))) {
+ (void)putchar("\\abfrtv"[p - escapes]);
+ count += 2;
+ } else {
+ (void)printf("%03o", *(u_char *)s);
+ count += 4;
+ }
+ }
+ }
+ (void)putchar('$');
+ (void)putchar('\n');
+ if (ferror(stdout))
+ err(FATAL, "stdout: %s", strerror(errno ? errno : EIO));
+}
+
+static inline int
+regexec_e(regex_t *preg, char *string, int eflags,
+ int nomatch, size_t slen)
+{
+ int eval;
+ int tmp;
+
+ if (preg == NULL) {
+ if (defpreg == NULL)
+ err(FATAL, "first RE may not be empty");
+ } else
+ defpreg = preg;
+
+ /* Set anchors, discounting trailing newline (if any). */
+ if (slen > 0 && string[slen - 1] == '\n')
+ slen--;
+
+ /* workaround for systems that don't support REG_STARTEND */
+ tmp = string[slen];
+ string[slen] = '\0';
+
+ eval = regexec(defpreg, string,
+ nomatch ? 0 : maxnsub + 1, match, eflags);
+
+ string[slen] = tmp; /* restore byte */
+
+ switch (eval) {
+ case 0:
+ return (1);
+ case REG_NOMATCH:
+ return (0);
+ }
+ err(FATAL, "RE error: %s", strregerror(eval, defpreg));
+ /* NOTREACHED */
+ return 0;
+}
+
+/*
+ * regsub - perform substitutions after a regexp match
+ * Based on a routine by Henry Spencer
+ */
+static void
+regsub(SPACE *sp, char *string, char *src)
+{
+ int len, no;
+ char c, *dst;
+
+#define NEEDSP(reqlen) \
+ if (sp->len + (reqlen) + 1 >= sp->blen) { \
+ size_t newlen = sp->blen + (reqlen) + 1024; \
+ sp->space = sp->back = xrealloc(sp->back, newlen); \
+ sp->blen = newlen; \
+ dst = sp->space + sp->len; \
+ }
+
+ dst = sp->space + sp->len;
+ while ((c = *src++) != '\0') {
+ if (c == '&')
+ no = 0;
+ else if (c == '\\' && isdigit((unsigned char)*src))
+ no = *src++ - '0';
+ else
+ no = -1;
+ if (no < 0) { /* Ordinary character. */
+ if (c == '\\' && (*src == '\\' || *src == '&'))
+ c = *src++;
+ NEEDSP(1);
+ *dst++ = c;
+ ++sp->len;
+ } else if (match[no].rm_so != -1 && match[no].rm_eo != -1) {
+ len = match[no].rm_eo - match[no].rm_so;
+ NEEDSP(len);
+ memmove(dst, string + match[no].rm_so, len);
+ dst += len;
+ sp->len += len;
+ }
+ }
+ NEEDSP(1);
+ *dst = '\0';
+}
+
+/*
+ * aspace --
+ * Append the source space to the destination space, allocating new
+ * space as necessary.
+ */
+void
+cspace(SPACE *sp, char *p, size_t len, enum e_spflag spflag)
+{
+ size_t tlen;
+
+ /* Make sure SPACE has enough memory and ramp up quickly. */
+ tlen = sp->len + len + 1;
+ if (tlen > sp->blen) {
+ size_t newlen = tlen + 1024;
+ sp->space = sp->back = xrealloc(sp->back, newlen);
+ sp->blen = newlen;
+ }
+
+ if (spflag == REPLACE)
+ sp->len = 0;
+
+ memmove(sp->space + sp->len, p, len);
+
+ sp->space[sp->len += len] = '\0';
+}
+
+/*
+ * Close all cached opened files and report any errors
+ */
+void
+cfclose(struct s_command *cp, struct s_command *end)
+{
+
+ for (; cp != end; cp = cp->next)
+ switch (cp->code) {
+ case 's':
+ if (cp->u.s->wfd != -1 && close(cp->u.s->wfd))
+ err(FATAL,
+ "%s: %s", cp->u.s->wfile, strerror(errno));
+ cp->u.s->wfd = -1;
+ break;
+ case 'w':
+ if (cp->u.fd != -1 && close(cp->u.fd))
+ err(FATAL, "%s: %s", cp->t, strerror(errno));
+ cp->u.fd = -1;
+ break;
+ case '{':
+ cfclose(cp->u.c, cp->next);
+ break;
+ }
+}
diff --git a/sed/reallocarray.c b/sed/reallocarray.c
@@ -0,0 +1,38 @@
+/* $OpenBSD: reallocarray.c,v 1.1 2014/05/08 21:43:49 deraadt Exp $ */
+/*
+ * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW (1UL << (sizeof(size_t) * 4))
+
+void *
+reallocarray(void *optr, size_t nmemb, size_t size)
+{
+ if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ nmemb > 0 && SIZE_MAX / nmemb < size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ return realloc(optr, size * nmemb);
+}
diff --git a/sed/sed.1 b/sed/sed.1
@@ -0,0 +1,566 @@
+.\" $OpenBSD: sed.1,v 1.44 2014/10/22 23:23:22 schwarze Exp $
+.\"
+.\" Copyright (c) 1992, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" from: @(#)sed.1 8.2 (Berkeley) 12/30/93
+.\"
+.Dd $Mdocdate: October 22 2014 $
+.Dt SED 1
+.Os
+.Sh NAME
+.Nm sed
+.Nd stream editor
+.Sh SYNOPSIS
+.Nm sed
+.Op Fl aEnru
+.Ar command
+.Op Ar
+.Nm sed
+.Op Fl aEnru
+.Op Fl e Ar command
+.Op Fl f Ar command_file
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility reads the specified files, or the standard input if no files
+are specified, modifying the input as specified by a list of commands.
+The input is then written to the standard output.
+.Pp
+A single command may be specified as the first argument to
+.Nm sed .
+Multiple commands may be specified
+separated by newlines or semicolons,
+or by using the
+.Fl e
+or
+.Fl f
+options.
+All commands are applied to the input in the order they are specified
+regardless of their origin.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl a
+The files listed as parameters for the
+.Ic w
+function or flag are created (or truncated) before any processing begins,
+by default.
+The
+.Fl a
+option causes
+.Nm
+to delay opening each file until a command containing the related
+.Ic w
+function or flag is applied to a line of input.
+.It Fl E
+Interpret regular expressions using POSIX extended regular expression syntax.
+The default behaviour is to use POSIX basic regular expression syntax.
+.It Fl e Ar command
+Append the editing commands specified by the
+.Ar command
+argument
+to the list of commands.
+.It Fl f Ar command_file
+Append the editing commands found in the file
+.Ar command_file
+to the list of commands.
+The editing commands should each be listed on a separate line.
+.It Fl r
+An alias for
+.Fl E ,
+for compatibility with GNU sed.
+.It Fl n
+By default, each line of input is echoed to the standard output after
+all of the commands have been applied to it.
+The
+.Fl n
+option suppresses this behavior.
+.It Fl u
+Force output to be line buffered,
+printing each line as it becomes available.
+By default, output is line buffered when standard output is a terminal
+and block buffered otherwise.
+See
+.Xr setbuf 3
+for a more detailed explanation.
+.El
+.Pp
+The form of a
+.Nm
+command is as follows:
+.Pp
+.Dl [address[,address]]function[arguments]
+.Pp
+Whitespace may be inserted before the first address and the function
+portions of the command.
+.Pp
+Normally,
+.Nm
+cyclically copies a line of input, not including its terminating newline
+character, into a
+.Em pattern space ,
+(unless there is something left after a
+.Ic D
+function),
+applies all of the commands with addresses that select that pattern space,
+copies the pattern space to the standard output, appending a newline, and
+deletes the pattern space.
+.Pp
+Some of the functions use a
+.Em hold space
+to save all or part of the pattern space for subsequent retrieval.
+.Sh SED ADDRESSES
+An address is not required, but if specified must be a number (that counts
+input lines
+cumulatively across input files), a dollar character
+.Pq Ql $
+that addresses the last line of input, or a context address
+(which consists of a regular expression preceded and followed by a
+delimiter).
+.Pp
+A command line with no addresses selects every pattern space.
+.Pp
+A command line with one address selects all of the pattern spaces
+that match the address.
+.Pp
+A command line with two addresses selects the inclusive range from
+the first pattern space that matches the first address through the next
+pattern space that matches the second.
+(If the second address is a number less than or equal to the line number
+first selected, only that line is selected.)
+Starting at the first line following the selected range,
+.Nm
+starts looking again for the first address.
+.Pp
+Editing commands can be applied to non-selected pattern spaces by use
+of the exclamation character
+.Pq Ql \&!
+function.
+.Sh SED REGULAR EXPRESSIONS
+By default,
+.Nm
+regular expressions are basic regular expressions
+.Pq BREs .
+Extended regular expressions are supported using the
+.Fl E
+and
+.Fl r
+options.
+See
+.Xr re_format 7
+for more information on regular expressions.
+In addition,
+.Nm
+has the following two additions to BREs:
+.Pp
+.Bl -enum -compact
+.It
+In a context address, any character other than a backslash
+.Pq Ql \e
+or newline character may be used to delimit the regular expression.
+The opening delimiter should be preceded by a backslash
+unless it is a slash.
+Putting a backslash character before the delimiting character
+causes the character to be treated literally.
+For example, in the context address \exabc\exdefx, the RE delimiter
+is an
+.Sq x
+and the second
+.Sq x
+stands for itself, so that the regular expression is
+.Dq abcxdef .
+.Pp
+.It
+The escape sequence \en matches a newline character embedded in the
+pattern space.
+You can't, however, use a literal newline character in an address or
+in the substitute command.
+.El
+.Pp
+One special feature of
+.Nm
+regular expressions is that they can default to the last regular
+expression used.
+If a regular expression is empty, i.e., just the delimiter characters
+are specified, the last regular expression encountered is used instead.
+The last regular expression is defined as the last regular expression
+used as part of an address or substitute command, and at run-time, not
+compile-time.
+For example, the command
+.Dq /abc/s//XXX/
+will substitute
+.Dq XXX
+for the pattern
+.Dq abc .
+.Sh SED FUNCTIONS
+In the following list of commands, the maximum number of permissible
+addresses for each command is indicated by [0addr], [1addr], or [2addr],
+representing zero, one, or two addresses.
+.Pp
+The argument
+.Ar text
+consists of one or more lines.
+To embed a newline in the text, precede it with a backslash.
+Other backslashes in text are deleted and the following character
+taken literally.
+.Pp
+The
+.Ic r
+and
+.Ic w
+functions,
+as well as the
+.Cm w
+flag to the
+.Ic s
+function,
+take an optional
+.Ar file
+parameter,
+which should be separated from the function or flag by whitespace.
+Files are created
+(or their contents truncated)
+before any input processing begins.
+.Pp
+The
+.Ic b ,
+.Ic r ,
+.Ic s ,
+.Ic t ,
+.Ic w ,
+.Ic y ,
+and
+.Ic \&:
+functions all accept additional arguments.
+The synopses below indicate which arguments have to be separated from
+the function letters by whitespace characters.
+.Pp
+Functions can be combined to form a
+.Em function list ,
+a list of
+.Nm
+functions each followed by a newline, as follows:
+.Bd -literal -offset indent
+{ function
+ function
+ ...
+ function
+}
+.Ed
+.Pp
+The braces can be preceded and followed by whitespace.
+The functions can be preceded by whitespace as well.
+.Pp
+Functions and function lists may be preceded by an exclamation mark,
+in which case they are applied only to lines that are
+.Em not
+selected by the addresses.
+.Bl -tag -width Ds
+.It [2addr] Ar function-list
+Execute
+.Ar function-list
+only when the pattern space is selected.
+.It Xo [1 addr] Ic a Ns \e
+.br
+.Ar text
+.Xc
+.Pp
+Write
+.Ar text
+to standard output immediately before each attempt to read a line of input,
+whether by executing the
+.Ic N
+function or by beginning a new cycle.
+.It [2addr] Ns Ic b Bq Ar label
+Branch to the
+.Ic \&:
+function with the specified
+.Ar label .
+If the label is not specified, branch to the end of the script.
+.It Xo [2addr] Ic c Ns \e
+.br
+.Ar text
+.Xc
+.Pp
+Delete the pattern space.
+With 0 or 1 address or at the end of a 2-address range,
+.Ar text
+is written to the standard output.
+.It [2addr] Ns Ic d
+Delete the pattern space and start the next cycle.
+.It [2addr] Ns Ic D
+Delete the initial segment of the pattern space through the first
+newline character and start the next cycle.
+.It [2addr] Ns Ic g
+Replace the contents of the pattern space with the contents of the
+hold space.
+.It [2addr] Ns Ic G
+Append a newline character followed by the contents of the hold space
+to the pattern space.
+.It [2addr] Ns Ic h
+Replace the contents of the hold space with the contents of the
+pattern space.
+.It [2addr] Ns Ic H
+Append a newline character followed by the contents of the pattern space
+to the hold space.
+.It Xo [1addr] Ic i Ns \e
+.br
+.Ar text
+.Xc
+.Pp
+Write
+.Ar text
+to the standard output.
+.It [2addr] Ns Ic l
+(The letter ell.)
+Write the pattern space to the standard output in a visually unambiguous
+form.
+This form is as follows:
+.Pp
+.Bl -tag -width "carriage-returnXX" -offset indent -compact
+.It backslash
+\e\e
+.It alert
+\ea
+.It backspace
+\eb
+.It form-feed
+\ef
+.It carriage-return
+\er
+.It tab
+\et
+.It vertical tab
+\ev
+.El
+.Pp
+Non-printable characters are written as three-digit octal numbers (with a
+preceding backslash) for each byte in the character (most significant byte
+first).
+Long lines are folded, with the point of folding indicated by displaying
+a backslash followed by a newline.
+The end of each line is marked with a
+.Ql $ .
+.It [2addr] Ns Ic n
+Write the pattern space to the standard output if the default output has
+not been suppressed, and replace the pattern space with the next line of
+input.
+.It [2addr] Ns Ic N
+Append the next line of input to the pattern space, using an embedded
+newline character to separate the appended material from the original
+contents.
+Note that the current line number changes.
+.It [2addr] Ns Ic p
+Write the pattern space to standard output.
+.It [2addr] Ns Ic P
+Write the pattern space, up to the first newline character,
+to the standard output.
+.It [1addr] Ns Ic q
+Branch to the end of the script and quit without starting a new cycle.
+.It [1addr] Ns Ic r Ar file
+Copy the contents of
+.Ar file
+to the standard output immediately before the next attempt to read a
+line of input.
+If
+.Ar file
+cannot be read for any reason, it is silently ignored and no error
+condition is set.
+.It [2addr] Ns Ic s Ns / Ns Ar RE Ns / Ns Ar replacement Ns / Ns Ar flags
+Substitute the
+.Ar replacement
+string for the first instance of the regular expression
+.Ar RE
+in the pattern space.
+Any character other than backslash or newline can be used instead of
+a slash to delimit the regular expression and the replacement.
+Within the regular expression and the replacement,
+the regular expression delimiter itself can be used as
+a literal character if it is preceded by a backslash.
+.Pp
+An ampersand
+.Pq Ql &
+appearing in the replacement is replaced by the string matching the
+regular expression.
+The special meaning of
+.Ql &
+in this context can be suppressed by preceding it by a backslash.
+The string
+.Ql \e# ,
+where
+.Ql #
+is a digit, is replaced by the text matched
+by the corresponding backreference expression (see
+.Xr re_format 7 ) .
+.Pp
+A line can be split by substituting a newline character into it.
+To specify a newline character in the replacement string, precede it with
+a backslash.
+.Pp
+The value of
+.Ar flags
+in the substitute function is zero or more of the following:
+.Bl -tag -width "XXXXXX" -offset indent
+.It Cm 0 No ... Cm 9
+Make the substitution only for the N'th occurrence of the regular
+expression in the pattern space.
+.It Cm g
+Make the substitution for all non-overlapping matches of the
+regular expression, not just the first one.
+.It Cm p
+Write the pattern space to standard output if a replacement was made.
+If the replacement string is identical to that which it replaces, it
+is still considered to have been a replacement.
+.It Cm w Ar file
+Append the pattern space to
+.Ar file
+if a replacement was made.
+If the replacement string is identical to that which it replaces, it
+is still considered to have been a replacement.
+.El
+.It [2addr] Ns Ic t Bq Ar label
+Branch to the
+.Ic \&:
+function bearing the
+.Ar label
+if any substitutions have been made since the
+most recent reading of an input line or execution of a
+.Ic t
+function.
+If no label is specified, branch to the end of the script.
+.It [2addr] Ns Ic w Ar file
+Append the pattern space to the
+.Ar file .
+.It [2addr] Ns Ic x
+Swap the contents of the pattern and hold spaces.
+.It [2addr] Ns Ic y Ns / Ns Ar string1 Ns / Ns Ar string2 Ns /
+Replace all occurrences of characters in
+.Ar string1
+in the pattern space with the corresponding characters from
+.Ar string2 .
+Any character other than a backslash or newline can be used instead of
+a slash to delimit the strings.
+Within
+.Ar string1
+and
+.Ar string2 ,
+a backslash followed by any character other than a newline is that literal
+character, and a backslash followed by an
+.Sq n
+is replaced by a newline character.
+.It [0addr] Ns Ic \&: Ns Ar label
+This function does nothing; it bears a
+.Ar label
+to which the
+.Ic b
+and
+.Ic t
+commands may branch.
+.It [1addr] Ns Ic =
+Write the line number to the standard output followed by a newline character.
+.It [0addr]
+Empty lines are ignored.
+.It [0addr] Ns Ic #
+The
+.Ql #
+and the remainder of the line are ignored (treated as a comment), with
+the single exception that if the first two characters in the file are
+.Ql #n ,
+the default output is suppressed.
+This is the same as specifying the
+.Fl n
+option on the command line.
+.El
+.Sh EXIT STATUS
+.Ex -std sed
+.Sh EXAMPLES
+The following simulates the
+.Xr cat 1
+.Fl s
+command,
+squeezing excess empty lines from standard input:
+.Bd -literal -offset indent
+$ sed -n '
+# Write non-empty lines.
+/./ {
+ p
+ d
+ }
+# Write a single empty line, then look for more empty lines.
+/^$/ p
+# Get the next line, discard the held <newline> (empty line),
+# and look for more empty lines.
+:Empty
+/^$/ {
+ N
+ s/.//
+ b Empty
+ }
+# Write the non-empty line before going back to search
+# for the first in a set of empty lines.
+ p
+\&'
+.Ed
+.Sh SEE ALSO
+.Xr awk 1 ,
+.Xr ed 1 ,
+.Xr grep 1 ,
+.Xr re_format 7
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
+.Pp
+The flags
+.Op Fl aEru
+are extensions to that specification.
+.Pp
+The use of newlines to separate multiple commands on the command line
+is non-portable;
+the use of newlines to separate multiple commands within a command file
+.Pq Fl f Ar command_file
+is portable.
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v7 .
+.Sh CAVEATS
+The use of semicolons to separate multiple commands
+is not permitted for the following commands:
+.Ic a , b , c ,
+.Ic i , r , t ,
+.Ic w , \&: ,
+and
+.Ic # .
diff --git a/sed/strlcat.c b/sed/strlcat.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+#include <sys/types.h>
+
+#include "util.h"
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+ return(dlen + (s - src)); /* count does not include NUL */
+}
diff --git a/sed/strlcpy.c b/sed/strlcpy.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+#include <sys/types.h>
+
+#include "util.h"
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0')
+ break;
+ }
+ }
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+ return(s - src - 1); /* count does not include NUL */
+}
diff --git a/sed/util.h b/sed/util.h
@@ -0,0 +1,12 @@
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stddef.h>
+
+#define DEFFILEMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
+
+char *fgetln(FILE *, size_t *);
+
+void *reallocarray(void *, size_t, size_t);
+
+size_t strlcat(char *, const char *, size_t);
+size_t strlcpy(char *, const char *, size_t);
diff --git a/sinit/LICENSE b/sinit/LICENSE
@@ -0,0 +1,21 @@
+MIT/X Consortium License
+
+© 2014 Dimitris Papastamos <sin@2f30.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/sinit/Makefile b/sinit/Makefile
@@ -0,0 +1,39 @@
+include config.mk
+
+OBJ = sinit.o
+BIN = sinit
+
+all: $(BIN)
+
+$(BIN): $(OBJ)
+ $(CC) $(LDFLAGS) -o $@ $(OBJ) $(LDLIBS)
+
+sinit.o: config.h
+
+install: all
+ mkdir -p $(DESTDIR)$(PREFIX)/bin
+ cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin
+ mkdir -p $(DESTDIR)$(MANPREFIX)/man8
+ sed "s/VERSION/$(VERSION)/g" < $(BIN).8 > $(DESTDIR)$(MANPREFIX)/man8/$(BIN).8
+
+uninstall:
+ rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN)
+ rm -f $(DESTDIR)$(MANPREFIX)/man8/$(BIN).8
+
+dist: clean
+ mkdir -p sinit-$(VERSION)
+ cp LICENSE Makefile README config.def.h config.mk sinit.8 sinit.c sinit-$(VERSION)
+ tar -cf sinit-$(VERSION).tar sinit-$(VERSION)
+ gzip sinit-$(VERSION).tar
+ rm -rf sinit-$(VERSION)
+
+clean:
+ rm -f $(BIN) $(OBJ) sinit-$(VERSION).tar.gz
+
+.SUFFIXES: .def.h
+
+.def.h.h:
+ cp $< $@
+
+.PHONY:
+ all install uninstall dist clean
diff --git a/sinit/README b/sinit/README
@@ -0,0 +1,26 @@
+sinit - suckless init
+=====================
+
+sinit is a simple init. It was initially based on
+Rich Felker's minimal init[1].
+
+Why?
+----
+
+I wanted to get rid of Busybox init on my toy distro[2].
+
+How?
+----
+
+There are 3 signals that sinit will act on.
+
+ SIGUSR1: powers off the machine.
+ SIGINT: reboots the machine (or alternatively via ctrl-alt-del).
+ SIGCHLD: reap children
+
+To see how sinit integrates with the init scripts, then have
+a look at [3].
+
+[1] https://gist.github.com/rofl0r/6168719
+[2] http://git.2f30.org/morpheus/
+[3] http://git.2f30.org/ports/tree/fs/
diff --git a/sinit/config.def.h b/sinit/config.def.h
@@ -0,0 +1,5 @@
+/* See LICENSE file for copyright and license details. */
+
+static char *const rcinitcmd[] = { "/bin/rc.init", NULL };
+static char *const rcrebootcmd[] = { "/bin/rc.shutdown", "reboot", NULL };
+static char *const rcpoweroffcmd[] = { "/bin/rc.shutdown", "poweroff", NULL };
diff --git a/sinit/config.h b/sinit/config.h
@@ -0,0 +1,5 @@
+/* See LICENSE file for copyright and license details. */
+
+static char *const rcinitcmd[] = { "/bin/rc.init", NULL };
+static char *const rcrebootcmd[] = { "/bin/rc.shutdown", "reboot", NULL };
+static char *const rcpoweroffcmd[] = { "/bin/rc.shutdown", "poweroff", NULL };
diff --git a/sinit/config.mk b/sinit/config.mk
@@ -0,0 +1,12 @@
+# sinit version
+VERSION = 0.9.2
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = $(PREFIX)/share/man
+
+CC = cc
+LD = $(CC)
+CPPFLAGS =
+CFLAGS = -Wextra -Wall -Os
+LDFLAGS = -s -static
diff --git a/sinit/sinit.8 b/sinit/sinit.8
@@ -0,0 +1,62 @@
+.Dd December 4, 2014
+.Dt SINIT 8 sinit\-VERSION
+.Os
+.Sh NAME
+.Nm sinit
+.Nd simple init
+.Sh DESCRIPTION
+.Nm
+is a simple init. It is configured by modifying
+.Dq config.h
+and recompiling the code.
+.Ss Init sequence
+By default
+.Nm
+will execute
+.Dq /bin/rc.init .
+You can chain your init scripts together at that point to allow for
+single-user and multi-user operation.
+.Ss Signal handling
+.Nm
+will respond to the following signals:
+.Bl -tag -width xxxxxxxx
+.It USR1
+Default action is to initiate the shutdown sequence by
+executing
+.Dq /bin/rc.shutdown poweroff .
+.It INT
+Default action is to initiate the reboot sequence by
+executing
+.Dq /bin/rc.shutdown reboot .
+.It CHLD
+Reap children.
+.El
+.Ss General considerations
+Running the
+.Dq rc.shutdown
+script directly is not recommended. If any
+process in your session has stale filesystem references then it is
+likely your init scripts will fail to unmount the filesystem cleanly.
+It is recommended to signal
+.Nm
+via a wrapper script.
+.Pp
+.Nm
+does not clear utmp records. The version of
+.Xr getty 8
+in ubase clears the utmp entries on the specified tty before it
+spawns the given program, usually
+.Xr login 1 .
+.Pp
+.Nm
+does not restart
+.Xr getty 8
+or interact with it in any way. You will need an external
+respawning mechanism to restart it.
+.Sh SEE ALSO
+.Xr killall5 8 ,
+.Xr getty 8
+.Sh AUTHORS
+The
+.Nm
+program was written by Dimitris Papastamos <sin@2f30.org>.
diff --git a/sinit/sinit.c b/sinit/sinit.c
@@ -0,0 +1,89 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define LEN(x) (sizeof (x) / sizeof *(x))
+
+static void sigpoweroff(void);
+static void sigreap(void);
+static void sigreboot(void);
+static void spawn(char *const []);
+
+static struct {
+ int sig;
+ void (*handler)(void);
+} sigmap[] = {
+ { SIGUSR1, sigpoweroff },
+ { SIGCHLD, sigreap },
+ { SIGINT, sigreboot },
+};
+
+#include "config.h"
+
+static sigset_t set;
+
+int
+main(void)
+{
+ int sig;
+ size_t i;
+
+ if (getpid() != 1)
+ return EXIT_FAILURE;
+ chdir("/");
+ sigfillset(&set);
+ sigprocmask(SIG_BLOCK, &set, NULL);
+ spawn(rcinitcmd);
+ while (1) {
+ sigwait(&set, &sig);
+ for (i = 0; i < LEN(sigmap); i++) {
+ if (sigmap[i].sig == sig) {
+ sigmap[i].handler();
+ break;
+ }
+ }
+ }
+ /* not reachable */
+ return EXIT_SUCCESS;
+}
+
+static void
+sigpoweroff(void)
+{
+ spawn(rcpoweroffcmd);
+}
+
+static void
+sigreap(void)
+{
+ while (waitpid(-1, NULL, WNOHANG) > 0)
+ ;
+}
+
+static void
+sigreboot(void)
+{
+ spawn(rcrebootcmd);
+}
+
+static void
+spawn(char *const argv[])
+{
+ pid_t pid;
+
+ pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ } else if (pid == 0) {
+ sigprocmask(SIG_UNBLOCK, &set, NULL);
+ setsid();
+ execvp(argv[0], argv);
+ perror("execvp");
+ _exit(EXIT_FAILURE);
+ }
+}
diff --git a/smdev/LICENSE b/smdev/LICENSE
@@ -0,0 +1,22 @@
+MIT/X Consortium License
+
+© 2013 sin <sin@2f30.org>
+© 2014 Hiltjo Posthuma <hiltjo@codemadness.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/smdev/Makefile b/smdev/Makefile
@@ -0,0 +1,66 @@
+include config.mk
+
+.POSIX:
+.SUFFIXES: .c .o
+
+LIB = \
+ util/agetcwd.o \
+ util/apathmax.o \
+ util/dev.o \
+ util/eprintf.o \
+ util/estrtol.o \
+ util/mkpath.o \
+ util/recurse.o \
+ util/strlcpy.o
+
+SRC = smdev.c
+
+OBJ = $(SRC:.c=.o) $(LIB)
+BIN = $(SRC:.c=)
+MAN = $(SRC:.c=.1)
+
+all: options binlib
+
+options:
+ @echo mdev build options:
+ @echo "CFLAGS = $(CFLAGS)"
+ @echo "LDFLAGS = $(LDFLAGS)"
+ @echo "CC = $(CC)"
+
+binlib: util.a
+ $(MAKE) bin
+
+bin: $(BIN)
+
+$(OBJ): config.h util.h config.mk
+
+config.h:
+ @echo creating $@ from config.def.h
+ @cp config.def.h $@
+
+.o:
+ @echo LD $@
+ @$(LD) -o $@ $< util.a $(LDFLAGS)
+
+.c.o:
+ @echo CC $<
+ @$(CC) -c -o $@ $< $(CFLAGS)
+
+util.a: $(LIB)
+ @echo AR $@
+ @$(AR) -r -c $@ $(LIB)
+ @ranlib $@
+
+install: all
+ @echo installing executable to $(DESTDIR)$(PREFIX)/bin
+ @mkdir -p $(DESTDIR)$(PREFIX)/bin
+ @cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin
+ @cd $(DESTDIR)$(PREFIX)/bin && chmod 755 $(BIN)
+
+uninstall:
+ @echo removing executable from $(DESTDIR)$(PREFIX)/bin
+ @cd $(DESTDIR)$(PREFIX)/bin && rm -f $(BIN)
+
+clean:
+ @echo cleaning
+ @rm -f $(BIN) $(OBJ) $(LIB) util.a
diff --git a/smdev/README b/smdev/README
@@ -0,0 +1,25 @@
+What is it?
+===========
+
+smdev is a simple program to manage device nodes. It is
+mostly compatible with mdev but doesn't have all of its features.
+
+Building
+========
+
+You need to have the kernel headers available on your
+system to build smdev.
+
+To build a statically linked smdev against musl-libc[0]
+install the relevant cross-compiler[1], then install the kernel
+headers into your cross-compiler prefix and finally run
+the following:
+
+$ make CC=x86_64-musl-linux-gcc LDFLAGS=-static
+$ x86_64-linux-musl-strip smdev
+
+On my system the above results in a 88kB statically linked
+application.
+
+[0] http://www.musl-libc.org/
+[1] http://musl.codu.org/
diff --git a/smdev/TODO b/smdev/TODO
@@ -0,0 +1,6 @@
+* remove memory allocations in the create/remove path
+* grab latest recurse.c from sbase and rework code (remove agetcwd calls)
+* use strlcpy()/strlcat() instead of snprintf() and check for path truncation
+* populatedev() makes some weird assumptions, should be reworked
+* simplify parsepath()
+* rethink mdev compatibility
diff --git a/smdev/arg.h b/smdev/arg.h
@@ -0,0 +1,55 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef __ARG_H__
+#define __ARG_H__
+
+extern char *argv0;
+
+#define USED(x) ((void)(x))
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
+ argv[0] && argv[0][1]\
+ && argv[0][0] == '-';\
+ argc--, argv++) {\
+ char _argc;\
+ char **_argv;\
+ int brk;\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ for (brk = 0, argv[0]++, _argv = argv;\
+ argv[0][0] && !brk;\
+ argv[0]++) {\
+ if (_argv != argv)\
+ break;\
+ _argc = argv[0][0];\
+ switch (_argc)
+
+#define ARGEND }\
+ USED(_argc);\
+ }\
+ USED(argv);\
+ USED(argc);
+
+#define ARGC() _argc
+
+#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ ((x), abort(), (char *)0) :\
+ (brk = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ (char *)0 :\
+ (brk = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#endif
+
diff --git a/smdev/bin/simevent b/smdev/bin/simevent
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+# Simulate add/remove events by writing directly
+# into the uevent files.
+
+if [ -z $1 ]; then
+ echo "usage: $(basename $0) add|remove" 2>&1
+ exit 1
+fi
+
+ev=$1
+for i in $(find /sys/devices -type f); do
+ f=$(basename $i)
+ if [ "$f" = "dev" ]; then
+ d=$(dirname $i)
+ echo $ev > $d/uevent
+ fi
+done
diff --git a/smdev/config.def.h b/smdev/config.def.h
@@ -0,0 +1,53 @@
+/* See LICENSE file for copyright and license details. */
+struct rule {
+ const char *devregex;
+ const char *user;
+ const char *group;
+ int mode;
+ const char *path;
+ const char *cmd;
+} rules[] = {
+ { "null", "root", "root", 0666, NULL, NULL },
+ { "zero", "root", "root", 0666, NULL, NULL },
+ { "full", "root", "root", 0666, NULL, NULL },
+ { "random", "root", "root", 0666, NULL, NULL },
+ { "urandom", "root", "root", 0444, NULL, NULL },
+ { "hwrandom", "root", "root", 0660, NULL, NULL },
+ { "mem", "root", "root", 0640, NULL, NULL },
+ { "kmem", "root", "root", 0640, NULL, NULL },
+ { "port", "root", "root", 0640, NULL, NULL },
+ { "console", "root", "tty", 0600, NULL, NULL },
+ { "ptmx", "root", "tty", 0666, NULL, NULL },
+ { "tty", "root", "tty", 0666, NULL, NULL },
+ { "tty[0-9]", "root", "root", 0600, NULL, NULL },
+ { "tty[0-9][0-9]","root", "tty", 0660, NULL, NULL },
+ { "ttyS[0-9]*", "root", "tty", 0660, NULL, NULL, },
+ { "pty.*", "root", "tty", 0660, NULL, NULL },
+ { "vcs[0-9]*", "root", "tty", 0660, NULL, NULL },
+ { "vcsa*[0-9]*", "root", "tty", 0660, NULL, NULL },
+ { "sd[a-z].*", "root", "disk", 0660, NULL, NULL },
+ { "sr[0-9]*", "root", "cdrom", 0660, NULL, "@ln -sf $DEVNAME /dev/cdrom" },
+ { "ts[0-9]+", "root", "root", 0640, "=input/", NULL },
+ { "input/.*", "root", "root", 0640, "=input/", NULL },
+ { "dri/.*", "root", "video", 0660, "=dri/", NULL },
+ { "snd/.*", "root", "audio", 0660, "=snd/", NULL },
+ { "midi.*", "root", "audio", 0660, "=snd/", NULL },
+ { "seq", "root", "audio", 0660, "=snd/", NULL },
+ { "timer", "root", "audio", 0660, "=snd/", NULL },
+ { "rtc[0-9]*", "root", "root", 0664, NULL, NULL },
+ { "vbi[0-9]", "root", "video", 0660, NULL, NULL },
+ { "video[0-9]", "root", "video", 0660, NULL, NULL },
+ { "fuse", "root", "root", 0666, NULL, NULL },
+ { ".*", "root", "root", 0660, NULL, NULL },
+};
+
+/* Fill into this table if you want to rename the network interface
+ * identified by `mac' to `name'. By default no such renaming takes
+ * place.
+ */
+struct mac2name {
+ unsigned char mac[6];
+ const char *name;
+} mac2names[] = {
+ { .mac = { 0 }, .name = NULL }
+};
diff --git a/smdev/config.h b/smdev/config.h
@@ -0,0 +1,53 @@
+/* See LICENSE file for copyright and license details. */
+struct rule {
+ const char *devregex;
+ const char *user;
+ const char *group;
+ int mode;
+ const char *path;
+ const char *cmd;
+} rules[] = {
+ { "null", "root", "root", 0666, NULL, NULL },
+ { "zero", "root", "root", 0666, NULL, NULL },
+ { "full", "root", "root", 0666, NULL, NULL },
+ { "random", "root", "root", 0666, NULL, NULL },
+ { "urandom", "root", "root", 0444, NULL, NULL },
+ { "hwrandom", "root", "root", 0660, NULL, NULL },
+ { "mem", "root", "root", 0640, NULL, NULL },
+ { "kmem", "root", "root", 0640, NULL, NULL },
+ { "port", "root", "root", 0640, NULL, NULL },
+ { "console", "root", "tty", 0600, NULL, NULL },
+ { "ptmx", "root", "tty", 0666, NULL, NULL },
+ { "tty", "root", "tty", 0666, NULL, NULL },
+ { "tty[0-9]", "root", "root", 0600, NULL, NULL },
+ { "tty[0-9][0-9]","root", "tty", 0660, NULL, NULL },
+ { "ttyS[0-9]*", "root", "tty", 0660, NULL, NULL, },
+ { "pty.*", "root", "tty", 0660, NULL, NULL },
+ { "vcs[0-9]*", "root", "tty", 0660, NULL, NULL },
+ { "vcsa*[0-9]*", "root", "tty", 0660, NULL, NULL },
+ { "sd[a-z].*", "root", "disk", 0660, NULL, NULL },
+ { "sr[0-9]*", "root", "cdrom", 0660, NULL, "@ln -sf $DEVNAME /dev/cdrom" },
+ { "ts[0-9]+", "root", "root", 0640, "=input/", NULL },
+ { "input/.*", "root", "root", 0640, "=input/", NULL },
+ { "dri/.*", "root", "video", 0660, "=dri/", NULL },
+ { "snd/.*", "root", "audio", 0660, "=snd/", NULL },
+ { "midi.*", "root", "audio", 0660, "=snd/", NULL },
+ { "seq", "root", "audio", 0660, "=snd/", NULL },
+ { "timer", "root", "audio", 0660, "=snd/", NULL },
+ { "rtc[0-9]*", "root", "root", 0664, NULL, NULL },
+ { "vbi[0-9]", "root", "video", 0660, NULL, NULL },
+ { "video[0-9]", "root", "video", 0660, NULL, NULL },
+ { "fuse", "root", "root", 0666, NULL, NULL },
+ { ".*", "root", "root", 0660, NULL, NULL },
+};
+
+/* Fill into this table if you want to rename the network interface
+ * identified by `mac' to `name'. By default no such renaming takes
+ * place.
+ */
+struct mac2name {
+ unsigned char mac[6];
+ const char *name;
+} mac2names[] = {
+ { .mac = { 0 }, .name = NULL }
+};
diff --git a/smdev/config.mk b/smdev/config.mk
@@ -0,0 +1,11 @@
+# smdev version
+VERSION = 0.2.3
+
+# paths
+PREFIX = /usr/local
+
+#CC = musl-gcc
+LD = $(CC)
+CPPFLAGS = -D_BSD_SOURCE -D_GNU_SOURCE
+CFLAGS = -std=c99 -Wall -pedantic $(CPPFLAGS)
+LDFLAGS = -s
diff --git a/smdev/mkpath.h b/smdev/mkpath.h
@@ -0,0 +1,2 @@
+/* See LICENSE file for copyright and license details. */
+int mkpath(const char *, mode_t);
diff --git a/smdev/smdev.c b/smdev/smdev.c
@@ -0,0 +1,441 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <linux/sockios.h>
+#include <linux/if_packet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <ifaddrs.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <libgen.h>
+#include <limits.h>
+#include <pwd.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "mkpath.h"
+#include "util.h"
+
+enum action {
+ ADD_ACTION,
+ REMOVE_ACTION,
+ UNKNOWN_ACTION
+};
+
+struct event {
+ int minor;
+ int major;
+ enum action action;
+ char *devpath;
+ char *devname;
+ struct rule *rule;
+};
+
+/* Simple cache for regcomp() results */
+static struct pregentry {
+ regex_t preg;
+ int cached;
+} pregcache[LEN(rules)];
+
+/* The expanded/parsed path components of a rule */
+struct rulepath {
+ char path[PATH_MAX];
+ char name[PATH_MAX];
+};
+
+static int dohotplug(void);
+static int matchrule(int, char *);
+static void runrulecmd(struct rule *);
+static void parsepath(struct rule *, struct rulepath *, const char *);
+static int removedev(struct event *);
+static int createdev(struct event *);
+static int doevent(struct event *);
+static int craftev(char *);
+static void populatedev(const char *);
+static int ifrename(void);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-s]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int sflag = 0;
+ int i;
+
+ ARGBEGIN {
+ case 's':
+ sflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ umask(0);
+ if (sflag) {
+ recurse("/sys/devices", populatedev);
+ } else {
+ if (dohotplug() < 0)
+ eprintf("Environment not set up correctly for hotplugging\n");
+ }
+
+ for (i = 0; i < LEN(pregcache); i++)
+ if (pregcache[i].cached)
+ regfree(&pregcache[i].preg);
+
+ if (ifrename() < 0)
+ return EXIT_FAILURE;
+
+ return EXIT_SUCCESS;
+}
+
+static enum action
+mapaction(const char *action)
+{
+ if (strcmp(action, "add") == 0)
+ return ADD_ACTION;
+ if (strcmp(action, "remove") == 0)
+ return REMOVE_ACTION;
+ return UNKNOWN_ACTION;
+}
+
+/* Handle hotplugging events */
+static int
+dohotplug(void)
+{
+ char *minor, *major;
+ char *action;
+ char *devpath;
+ char *devname;
+ struct event ev;
+
+ minor = getenv("MINOR");
+ major = getenv("MAJOR");
+ action = getenv("ACTION");
+ devpath = getenv("DEVPATH");
+ devname = getenv("DEVNAME");
+ if (!minor || !major || !action || !devpath || !devname)
+ return -1;
+
+ ev.minor = estrtol(minor, 10);
+ ev.major = estrtol(major, 10);
+ ev.action = mapaction(action);
+ ev.devpath = devpath;
+ ev.devname = devname;
+ return doevent(&ev);
+}
+
+/*
+ * `ruleidx' indexes into the rules[] table. We assume
+ * pregcache[] is mapped 1-1 with the rules[] table.
+ */
+static int
+matchrule(int ruleidx, char *devname)
+{
+ struct rule *rule = &rules[ruleidx];
+ regex_t *preg;
+ regmatch_t pmatch;
+ int ret;
+
+ if (!pregcache[ruleidx].cached) {
+ ret = regcomp(&pregcache[ruleidx].preg,
+ rule->devregex, REG_EXTENDED);
+ if (ret < 0)
+ eprintf("regcomp:");
+ pregcache[ruleidx].cached = 1;
+ }
+ preg = &pregcache[ruleidx].preg;
+
+ ret = regexec(preg, devname, 1, &pmatch, 0);
+ if (ret == REG_NOMATCH || pmatch.rm_so ||
+ pmatch.rm_eo != strlen(devname))
+ return -1;
+ return 0;
+}
+
+static void
+runrulecmd(struct rule *rule)
+{
+ if (rule->cmd)
+ system(&rule->cmd[1]);
+}
+
+static void
+parsepath(struct rule *rule, struct rulepath *rpath,
+ const char *devname)
+{
+ char buf[PATH_MAX], *path, *dirc;
+ const char *basedevname;
+
+ if(!(basedevname = strrchr(devname, '/')))
+ basedevname = devname;
+
+ if (!rule->path) {
+ strlcpy(rpath->name, basedevname, sizeof(rpath->name));
+ snprintf(rpath->path, sizeof(rpath->path), "/dev/%s",
+ rpath->name);
+ return;
+ }
+
+ if (rule->path[0] != '=' && rule->path[0] != '>')
+ eprintf("Invalid path '%s'\n", rule->path);
+
+ path = strdup(&rule->path[1]);
+ if (!path)
+ eprintf("strdup:");
+
+ /* No need to rename the device node */
+ if (rule->path[strlen(rule->path) - 1] == '/') {
+ snprintf(rpath->path, sizeof(rpath->path), "/dev/%s%s",
+ path, basedevname);
+ strlcpy(rpath->name, basedevname, sizeof(rpath->name));
+ free(path);
+ return;
+ }
+
+ if (strchr(path, '/')) {
+ if (!(dirc = strdup(path)))
+ eprintf("strdup:");
+ snprintf(buf, sizeof(buf), "/dev/%s", dirname(dirc));
+ strlcpy(rpath->name, basename(path), sizeof(rpath->name));
+ snprintf(rpath->path, sizeof(rpath->path), "%s/%s", buf,
+ rpath->name);
+ free(dirc);
+ } else {
+ strlcpy(rpath->name, path, sizeof(rpath->name));
+ snprintf(rpath->path, sizeof(rpath->path), "/dev/%s",
+ rpath->name);
+ }
+
+ free(path);
+}
+
+static int
+removedev(struct event *ev)
+{
+ struct rule *rule;
+ struct rulepath rpath;
+ char *ocwd;
+ char buf[PATH_MAX];
+
+ rule = ev->rule;
+ ocwd = agetcwd();
+
+ parsepath(rule, &rpath, ev->devname);
+
+ if(rule->cmd) {
+ if (chdir("/dev") < 0)
+ eprintf("chdir /dev:");
+ runrulecmd(rule);
+ }
+
+ if (chdir(ocwd) < 0)
+ eprintf("chdir %s:", ocwd);
+
+ free(ocwd);
+
+ if (rule->path && rule->path[0] == '!')
+ return 0;
+
+ /* Delete device node */
+ unlink(rpath.path);
+ /* Delete symlink */
+ if (rule->path && rule->path[0] == '>') {
+ snprintf(buf, sizeof(buf), "/dev/%s", ev->devname);
+ unlink(buf);
+ }
+ return 0;
+}
+
+static int
+createdev(struct event *ev)
+{
+ struct rule *rule;
+ struct rulepath rpath;
+ struct passwd *pw;
+ struct group *gr;
+ char *dirc, *ocwd;
+ char buf[BUFSIZ];
+ int type;
+
+ rule = ev->rule;
+ ocwd = agetcwd();
+
+ if (rule->path && rule->path[0] == '!')
+ goto runrule;
+
+ snprintf(buf, sizeof(buf), "%d:%d", ev->major, ev->minor);
+ if ((type = devtype(buf)) < 0)
+ return -1;
+
+ /* Parse path and create the directory tree */
+ parsepath(rule, &rpath, ev->devname);
+ if (!(dirc = strdup(rpath.path)))
+ eprintf("strdup:");
+ strlcpy(buf, dirname(dirc), sizeof(buf));
+ free(dirc);
+ umask(022);
+ if (mkpath(buf, 0755) < 0)
+ eprintf("mkdir %s:", buf);
+ umask(0);
+
+ if (mknod(rpath.path, rule->mode | type,
+ makedev(ev->major, ev->minor)) < 0 &&
+ errno != EEXIST)
+ eprintf("mknod %s:", rpath.path);
+
+ errno = 0;
+ pw = getpwnam(rule->user);
+ if (!pw) {
+ if (errno)
+ eprintf("getpwnam %s:", rule->user);
+ else
+ eprintf("getpwnam %s: no such user\n",
+ rule->user);
+ }
+
+ errno = 0;
+ gr = getgrnam(rule->group);
+ if (!gr) {
+ if (errno)
+ eprintf("getgrnam %s:", rule->group);
+ else
+ eprintf("getgrnam %s: no such group\n",
+ rule->group);
+ }
+
+ if (chown(rpath.path, pw->pw_uid, gr->gr_gid) < 0)
+ eprintf("chown %s:", rpath.path);
+
+ if (chmod(rpath.path, rule->mode) < 0)
+ eprintf("chmod %s:", rpath.path);
+
+ if (rule->path && rule->path[0] == '>') {
+ /* ev->devname is the original device name */
+ snprintf(buf, sizeof(buf), "/dev/%s", ev->devname);
+ symlink(rpath.path, buf);
+ }
+
+runrule:
+ if(rule->cmd) {
+ if (chdir("/dev") < 0)
+ eprintf("chdir /dev:");
+ runrulecmd(rule);
+ }
+
+ if (chdir(ocwd) < 0)
+ eprintf("chdir %s:", ocwd);
+
+ free(ocwd);
+
+ return 0;
+}
+
+/* Event dispatcher */
+static int
+doevent(struct event *ev)
+{
+ int i;
+
+ for (i = 0; i < LEN(rules); i++) {
+ if (matchrule(i, ev->devname) < 0)
+ continue;
+ ev->rule = &rules[i];
+ switch (ev->action) {
+ case ADD_ACTION:
+ return createdev(ev);
+ case REMOVE_ACTION:
+ return removedev(ev);
+ default:
+ return 0;
+ }
+ }
+ return 0;
+}
+
+/* Craft a fake event so the rest of the code can cope */
+static int
+craftev(char *sysfspath)
+{
+ char path[PATH_MAX];
+ char *devpath;
+
+ devpath = sysfspath + strlen("/sys");
+ snprintf(path, sizeof(path), "/sys%s/uevent", devpath);
+
+ clearenv();
+ setenv("ACTION", "add", 1);
+ setenv("DEVPATH", devpath, 1);
+ if(readuevent(path) < 0)
+ return -1;
+ return 0;
+}
+
+static void
+populatedev(const char *path)
+{
+ char *cwd;
+
+ recurse(path, populatedev);
+ if (strcmp(path, "dev") == 0) {
+ cwd = agetcwd();
+ if (!craftev(cwd))
+ dohotplug();
+ free(cwd);
+ }
+}
+
+static int
+ifrename(void)
+{
+ struct sockaddr_ll *sa;
+ struct ifaddrs *ifas, *ifa;
+ struct ifreq ifr;
+ int sd;
+ int i;
+ int r;
+ int ok = 0;
+
+ sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sd < 0)
+ eprintf("socket:");
+ r = getifaddrs(&ifas);
+ if (r < 0)
+ eprintf("getifaddrs:");
+ for (ifa = ifas; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_flags & IFF_LOOPBACK)
+ continue;
+ if (ifa->ifa_addr->sa_family != AF_PACKET)
+ continue;
+ sa = (struct sockaddr_ll *)ifa->ifa_addr;
+ for (i = 0; mac2names[i].name; i++) {
+ if (memcmp(mac2names[i].mac, sa->sll_addr, 6) != 0)
+ continue;
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name,
+ ifa->ifa_name, sizeof(ifr.ifr_name));
+ strlcpy(ifr.ifr_newname,
+ mac2names[i].name, sizeof(ifr.ifr_newname));
+ r = ioctl(sd, SIOCSIFNAME, &ifr);
+ if (r < 0) {
+ weprintf("SIOCSIFNAME:");
+ ok = -1;
+ }
+ }
+ }
+ freeifaddrs(ifas);
+ close(sd);
+ return ok;
+}
diff --git a/smdev/util.h b/smdev/util.h
@@ -0,0 +1,18 @@
+/* See LICENSE file for copyright and license details. */
+#include "arg.h"
+
+#define LEN(x) (sizeof (x) / sizeof *(x))
+
+extern char *argv0;
+
+char *agetcwd(void);
+void apathmax(char **, long *);
+int readuevent(const char *);
+int devtype(const char *);
+void enprintf(int, const char *, ...);
+void eprintf(const char *, ...);
+void weprintf(const char *, ...);
+long estrtol(const char *, int);
+void recurse(const char *, void (*)(const char *));
+#undef strlcpy
+size_t strlcpy(char *, const char *, size_t);
diff --git a/smdev/util/agetcwd.c b/smdev/util/agetcwd.c
@@ -0,0 +1,18 @@
+/* See LICENSE file for copyright and license details. */
+#include <unistd.h>
+
+#include "../util.h"
+
+char *
+agetcwd(void)
+{
+ char *buf;
+ long size;
+
+ apathmax(&buf, &size);
+ if(!getcwd(buf, size))
+ eprintf("getcwd:");
+
+ return buf;
+}
+
diff --git a/smdev/util/apathmax.c b/smdev/util/apathmax.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "../util.h"
+
+void
+apathmax(char **p, long *size)
+{
+ errno = 0;
+
+ if((*size = pathconf("/", _PC_PATH_MAX)) == -1) {
+ if(errno == 0) {
+ *size = BUFSIZ;
+ } else {
+ eprintf("pathconf:");
+ }
+ }
+
+ if(!(*p = malloc(*size)))
+ eprintf("malloc:");
+}
+
diff --git a/smdev/util/dev.c b/smdev/util/dev.c
@@ -0,0 +1,55 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "../util.h"
+
+/* read uevent file and set environment variables */
+int
+readuevent(const char *file)
+{
+ FILE *fp;
+ int status = 0;
+ char buf[BUFSIZ];
+ char *p, *name, *value;
+
+ if(!(fp = fopen(file, "r")))
+ return -1;
+ while(!feof(fp)) {
+ fgets(buf, sizeof(buf) - 1, fp);
+ if(ferror(fp)) {
+ status = -2;
+ break;
+ }
+ if((p = strchr(buf, '\n')))
+ *p = '\0';
+ if(!(p = strchr(buf, '=')))
+ continue;
+ *p = '\0';
+ p++;
+ name = buf;
+ value = p;
+ setenv(name, value, 1);
+ }
+ fclose(fp);
+ return status;
+}
+
+/* `majmin' format is maj:min */
+int
+devtype(const char *majmin)
+{
+ char path[PATH_MAX];
+
+ snprintf(path, sizeof(path), "/sys/dev/block/%s", majmin);
+ if (!access(path, F_OK))
+ return S_IFBLK;
+ snprintf(path, sizeof(path), "/sys/dev/char/%s", majmin);
+ if (!access(path, F_OK))
+ return S_IFCHR;
+ return -1;
+}
diff --git a/smdev/util/eprintf.c b/smdev/util/eprintf.c
@@ -0,0 +1,59 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../util.h"
+
+char *argv0;
+
+static void venprintf(int, const char *, va_list);
+
+void
+eprintf(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ venprintf(EXIT_FAILURE, fmt, ap);
+ va_end(ap);
+}
+
+void
+enprintf(int status, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ venprintf(status, fmt, ap);
+ va_end(ap);
+}
+
+void
+venprintf(int status, const char *fmt, va_list ap)
+{
+ vfprintf(stderr, fmt, ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ }
+
+ exit(status);
+}
+
+void
+weprintf(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ }
+}
diff --git a/smdev/util/estrtol.c b/smdev/util/estrtol.c
@@ -0,0 +1,27 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../util.h"
+
+long
+estrtol(const char *s, int base)
+{
+ char *end;
+ long n;
+
+ errno = 0;
+ n = strtol(s, &end, base);
+ if(*end != '\0') {
+ if(base == 0)
+ eprintf("%s: not an integer\n", s);
+ else
+ eprintf("%s: not a base %d integer\n", s, base);
+ }
+ if(errno != 0)
+ eprintf("%s:", s);
+
+ return n;
+}
+
diff --git a/smdev/util/mkpath.c b/smdev/util/mkpath.c
@@ -0,0 +1,29 @@
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+
+int
+mkpath(const char *path, mode_t mode)
+{
+ char tmp[PATH_MAX];
+ char *p = NULL;
+ size_t len;
+
+ snprintf(tmp, sizeof(tmp),"%s",path);
+ len = strlen(tmp);
+ if(tmp[len - 1] == '/')
+ tmp[len - 1] = 0;
+ for(p = tmp + 1; *p; p++)
+ if(*p == '/') {
+ *p = 0;
+ if (mkdir(tmp, mode) < 0 && errno != EEXIST)
+ return -1;
+ *p = '/';
+ }
+ if (mkdir(tmp, mode) < 0 && errno != EEXIST)
+ return -1;
+ return 0;
+}
+
diff --git a/smdev/util/recurse.c b/smdev/util/recurse.c
@@ -0,0 +1,40 @@
+/* See LICENSE file for copyright and license details. */
+#include <dirent.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "../util.h"
+
+void
+recurse(const char *path, void (*fn)(const char *))
+{
+ char *cwd;
+ struct dirent *d;
+ struct stat st;
+ DIR *dp;
+
+ if(lstat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
+ return;
+ } else if(!(dp = opendir(path))) {
+ eprintf("opendir %s:", path);
+ }
+
+ cwd = agetcwd();
+ if(chdir(path) == -1)
+ eprintf("chdir %s:", path);
+
+ while((d = readdir(dp))) {
+ if(strcmp(d->d_name, ".") && strcmp(d->d_name, ".."))
+ fn(d->d_name);
+ }
+
+ closedir(dp);
+ if(chdir(cwd) == -1)
+ eprintf("chdir %s:", cwd);
+
+ free(cwd);
+}
+
diff --git a/smdev/util/strlcpy.c b/smdev/util/strlcpy.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+#include "../util.h"
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0')
+ break;
+ }
+ }
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+ return(s - src - 1); /* count does not include NUL */
+}
diff --git a/std.mk b/std.mk
@@ -0,0 +1,12 @@
+$(TARG): $(OBJ)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJ) $(LDLIBS)
+
+install:
+ mkdir -p $(DESTDIR)/$(PREFIX)/bin
+ cp -f $(TARG) $(DESTDIR)/$(PREFIX)/bin
+ mkdir -p $(DESTDIR)/$(MANPREFIX)/man1
+ cp -f $(TARG).1 $(DESTDIR)/$(PREFIX)/man1
+
+uninstall:
+ cd $(DESTDIR)/$(PREFIX)/bin && rm $(TARG)
+ cd $(DESTDIR)/$(MANPREFIX)/man1 && rm $(TARG).1
diff --git a/ubase/LICENSE b/ubase/LICENSE
@@ -0,0 +1,33 @@
+MIT/X Consortium License
+
+© 2013-2015 Dimitris Papapastamos <sin@2f30.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+Authors/contributors include:
+
+© 2013 oblique <psyberbits@gmail.com>
+© 2013 s-p-k <mr.dwts@gmail.com>
+© 2013 Jakob Kramer <jakob.kramer@gmx.de>
+© 2013 David Galos <galosd83@students.rowan.edu>
+© 2014 Carlos J. Torres <vlaadbrain@gmail.com>
+© 2014 Hiltjo Posthuma <hiltjo@codemadness.org>
+© 2014 Laslo Hunhold <dev@frign.de>
+© 2014 Roberto E. Vargas Caballero <k0ga@shike2.com>
+© 2014 Jan Tatje <jan@jnt.io>
diff --git a/ubase/Makefile b/ubase/Makefile
@@ -0,0 +1,210 @@
+include config.mk
+
+.SUFFIXES:
+.SUFFIXES: .o .c
+
+HDR = \
+ arg.h \
+ config.h \
+ passwd.h \
+ proc.h \
+ queue.h \
+ reboot.h \
+ rtc.h \
+ text.h \
+ util.h
+
+LIBUTIL = libutil.a
+LIBUTILSRC = \
+ libutil/agetcwd.c \
+ libutil/agetline.c \
+ libutil/apathmax.c \
+ libutil/concat.c \
+ libutil/ealloc.c \
+ libutil/eprintf.c \
+ libutil/estrtol.c \
+ libutil/estrtoul.c \
+ libutil/explicit_bzero.c \
+ libutil/passwd.c \
+ libutil/proc.c \
+ libutil/putword.c \
+ libutil/recurse.c \
+ libutil/strlcat.c \
+ libutil/strlcpy.c \
+ libutil/tty.c
+
+LIB = $(LIBUTIL)
+
+BIN = \
+ chvt \
+ clear \
+ ctrlaltdel \
+ dd \
+ df \
+ dmesg \
+ eject \
+ fallocate \
+ free \
+ freeramdisk \
+ fsfreeze \
+ getty \
+ halt \
+ hwclock \
+ id \
+ insmod \
+ killall5 \
+ last \
+ lastlog \
+ login \
+ lsmod \
+ lsusb \
+ mesg \
+ mknod \
+ mkswap \
+ mount \
+ mountpoint \
+ pagesize \
+ passwd \
+ pidof \
+ pivot_root \
+ ps \
+ readahead \
+ respawn \
+ rmmod \
+ stat \
+ su \
+ swaplabel \
+ swapoff \
+ swapon \
+ switch_root \
+ sysctl \
+ truncate \
+ umount \
+ unshare \
+ uptime \
+ vtallow \
+ watch \
+ who
+
+MAN1 = \
+ chvt.1 \
+ clear.1 \
+ dd.1 \
+ df.1 \
+ dmesg.1 \
+ eject.1 \
+ fallocate.1 \
+ free.1 \
+ id.1 \
+ login.1 \
+ mesg.1 \
+ mknod.1 \
+ mountpoint.1 \
+ pagesize.1 \
+ passwd.1 \
+ pidof.1 \
+ ps.1 \
+ respawn.1 \
+ stat.1 \
+ su.1 \
+ truncate.1 \
+ unshare.1 \
+ uptime.1 \
+ vtallow.1 \
+ watch.1 \
+ who.1
+
+MAN8 = \
+ ctrlaltdel.8 \
+ freeramdisk.8 \
+ fsfreeze.8 \
+ getty.8 \
+ halt.8 \
+ hwclock.8 \
+ insmod.8 \
+ killall5.8 \
+ lastlog.8 \
+ lsmod.8 \
+ lsusb.8 \
+ mkswap.8 \
+ mount.8 \
+ pivot_root.8 \
+ readahead.8 \
+ rmmod.8 \
+ swaplabel.8 \
+ swapoff.8 \
+ swapon.8 \
+ switch_root.8 \
+ sysctl.8 \
+ umount.8
+
+LIBUTILOBJ = $(LIBUTILSRC:.c=.o)
+OBJ = $(BIN:=.o) $(LIBUTILOBJ)
+SRC = $(BIN:=.c)
+
+all: $(BIN)
+
+$(BIN): $(LIB)
+
+$(OBJ): $(HDR) config.mk
+
+config.h:
+ cp config.def.h $@
+
+.o:
+ $(LD) $(LDFLAGS) -o $@ $< $(LIB) $(LDLIBS)
+
+.c.o:
+ $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -c $<
+
+$(LIBUTIL): $(LIBUTILOBJ)
+ $(AR) -r -c $@ $?
+ $(RANLIB) $@
+
+install: all
+ mkdir -p $(DESTDIR)$(PREFIX)/bin
+ cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin
+ cd $(DESTDIR)$(PREFIX)/bin && chmod 755 $(BIN)
+ mkdir -p $(DESTDIR)$(MANPREFIX)/man1
+ for m in $(MAN1); do sed "s/VERSION/$(VERSION)/g" < "$$m" > $(DESTDIR)$(MANPREFIX)/man1/"$$m"; done
+ mkdir -p $(DESTDIR)$(MANPREFIX)/man8
+ for m in $(MAN8); do sed "s/VERSION/$(VERSION)/g" < "$$m" > $(DESTDIR)$(MANPREFIX)/man8/"$$m"; done
+ cd $(DESTDIR)$(MANPREFIX)/man1 && chmod 644 $(MAN1)
+ cd $(DESTDIR)$(MANPREFIX)/man8 && chmod 644 $(MAN8)
+
+uninstall:
+ cd $(DESTDIR)$(PREFIX)/bin && rm -f $(BIN)
+ cd $(DESTDIR)$(MANPREFIX)/man1 && rm -f $(MAN1)
+ cd $(DESTDIR)$(MANPREFIX)/man8 && rm -f $(MAN8)
+
+dist: clean
+ mkdir -p ubase-$(VERSION)
+ cp -r LICENSE Makefile README TODO config.mk $(SRC) $(MAN1) $(MAN8) libutil $(HDR) config.def.h ubase-$(VERSION)
+ tar -cf ubase-$(VERSION).tar ubase-$(VERSION)
+ gzip ubase-$(VERSION).tar
+ rm -rf ubase-$(VERSION)
+
+ubase-box: $(LIB) $(SRC)
+ mkdir -p build
+ cp $(HDR) build
+ cp config.h build
+ for f in $(SRC); do sed "s/^main(/`basename $$f .c`_&/" < $$f > build/$$f; done
+ echo '#include <libgen.h>' > build/$@.c
+ echo '#include <stdio.h>' >> build/$@.c
+ echo '#include <stdlib.h>' >> build/$@.c
+ echo '#include <string.h>' >> build/$@.c
+ echo '#include "util.h"' >> build/$@.c
+ for f in $(SRC); do echo "int `basename $$f .c`_main(int, char **);" >> build/$@.c; done
+ echo 'int main(int argc, char *argv[]) { char *s = basename(argv[0]); if(!strcmp(s,"ubase-box")) { argc--; argv++; s = basename(argv[0]); } if(0) ;' >> build/$@.c
+ for f in $(SRC); do echo "else if(!strcmp(s, \"`basename $$f .c`\")) return `basename $$f .c`_main(argc, argv);" >> build/$@.c; done
+ echo 'else {' >> build/$@.c
+ for f in $(SRC); do echo "printf(\"`basename $$f .c`\"); putchar(' ');" >> build/$@.c; done
+ echo "putchar(0xa); }; return 0; }" >> build/$@.c
+ $(LD) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ build/*.c $(LIB) $(LDLIBS)
+ rm -r build
+
+clean:
+ rm -f $(BIN) $(OBJ) $(LIB) ubase-box ubase-$(VERSION).tar.gz
+
+.PHONY:
+ all install uninstall dist ubase-box clean
diff --git a/ubase/README b/ubase/README
@@ -0,0 +1,29 @@
+ubase - suckless linux base utils
+=================================
+
+ubase is a collection of tools similar in spirit to util-linux but
+much simpler.
+
+The complement of ubase is sbase[1] which mostly follows POSIX and
+provides all the portable tools. Together they are intended to form a
+base system similar to busybox but much smaller and suckless.
+
+Building
+--------
+
+To build ubase, simply type make. You may have to fiddle with
+config.mk and config.h depending on your system.
+
+You can also build ubase-box, which generates a single binary
+containing all the required tools. You can then symlink the
+individual tools to ubase-box or run them directly as follows:
+
+ ubase-box cmd [args]
+
+Ideally you will want to statically link ubase. We highly recommend
+using musl-libc[2].
+
+ubase is known to compile with gcc, clang and tcc.
+
+[1] http://git.suckless.org/sbase/
+[2] http://www.musl-libc.org/
diff --git a/ubase/TODO b/ubase/TODO
@@ -0,0 +1,35 @@
+Tools
+=====
+
+vmstat
+top
+Better ps support
+losetup
+lspci
+mkswap [-L]
+adduser
+addgroup
+rmuser
+rmgroup
+ifconfig
+partprobe
+rfkill
+taskset
+acpi
+blkid
+lsattr
+pmap
+pwdx
+setcap
+getcap
+capchroot
+fakeroot
+less or pg
+ionice
+fuser
+
+Misc
+====
+
+Beautify passwd(1).
+last(1) manpage.
diff --git a/ubase/arg.h b/ubase/arg.h
@@ -0,0 +1,63 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef ARG_H__
+#define ARG_H__
+
+extern char *argv0;
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
+ argv[0] && argv[0][1]\
+ && argv[0][0] == '-';\
+ argc--, argv++) {\
+ char argc_;\
+ char **argv_;\
+ int brk_;\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ for (brk_ = 0, argv[0]++, argv_ = argv;\
+ argv[0][0] && !brk_;\
+ argv[0]++) {\
+ if (argv_ != argv)\
+ break;\
+ argc_ = argv[0][0];\
+ switch (argc_)
+
+/* Handles obsolete -NUM syntax */
+#define ARGNUM case '0':\
+ case '1':\
+ case '2':\
+ case '3':\
+ case '4':\
+ case '5':\
+ case '6':\
+ case '7':\
+ case '8':\
+ case '9'
+
+#define ARGEND }\
+ }
+
+#define ARGC() argc_
+
+#define ARGNUMF(base) (brk_ = 1, estrtol(argv[0], (base)))
+
+#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ ((x), abort(), (char *)0) :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ (char *)0 :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#endif
diff --git a/ubase/chvt.1 b/ubase/chvt.1
@@ -0,0 +1,8 @@
+.TH CHVT 1 ubase-VERSION
+.SH NAME
+\fBchvt\fR - Change foreground virtual terminal
+.SH SYNOPSIS
+\fBchvt\fI N
+.SH DESCRIPTION
+\fBchvt\fR brings /dev/ttyN to the foreground. This has the
+same effect as Ctrl-Alt-FN.
diff --git a/ubase/chvt.c b/ubase/chvt.c
@@ -0,0 +1,61 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+#define KDGKBTYPE 0x4B33 /* get keyboard type */
+
+#define VT_ACTIVATE 0x5606 /* make vt active */
+#define VT_WAITACTIVE 0x5607 /* wait for vt active */
+
+static char *vts[] = {
+ "/proc/self/fd/0",
+ "/dev/console",
+ "/dev/tty",
+ "/dev/tty0",
+};
+
+static void
+usage(void)
+{
+ eprintf("usage: chvt N\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ unsigned int n, i;
+ int fd;
+ char c;
+
+ if (argc != 2 || strspn(argv[1], "1234567890") != strlen(argv[1]))
+ usage();
+
+ n = estrtol(argv[1], 10);
+ for (i = 0; i < LEN(vts); i++) {
+ fd = open(vts[i], O_RDONLY);
+ if (fd < 0)
+ continue;
+ c = 0;
+ if (ioctl(fd, KDGKBTYPE, &c) == 0)
+ goto VTfound;
+ close(fd);
+ }
+
+ eprintf("couldn't find a console.\n");
+VTfound:
+ if (ioctl(fd, VT_ACTIVATE, n) == -1)
+ eprintf("VT_ACTIVATE %d:", n);
+ if (ioctl(fd, VT_WAITACTIVE, n) == -1)
+ eprintf("VT_WAITACTIVE %d:", n);
+ close(fd);
+
+ return 0;
+}
diff --git a/ubase/clear.1 b/ubase/clear.1
@@ -0,0 +1,3 @@
+.TH CLEAR 1 ubase-VERSION
+.SH NAME
+\fBclear\fR - Clear the screen
diff --git a/ubase/clear.c b/ubase/clear.c
@@ -0,0 +1,10 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+main(void)
+{
+ printf("\x1b[2J\x1b[H");
+ return 0;
+}
diff --git a/ubase/config.def.h b/ubase/config.def.h
@@ -0,0 +1,11 @@
+/* See LICENSE file for copyright and license details. */
+
+#define ENV_SUPATH "/bin"
+#define ENV_PATH "/bin"
+#define PW_CIPHER "$6$" /* SHA-512 */
+#undef UTMP_PATH
+#define UTMP_PATH "/var/run/utmp"
+#undef BTMP_PATH
+#define BTMP_PATH "/var/log/btmp"
+#undef WTMP_PATH
+#define WTMP_PATH "/var/log/wtmp"
diff --git a/ubase/config.h b/ubase/config.h
@@ -0,0 +1,11 @@
+/* See LICENSE file for copyright and license details. */
+
+#define ENV_SUPATH "/bin"
+#define ENV_PATH "/bin"
+#define PW_CIPHER "$6$" /* SHA-512 */
+#undef UTMP_PATH
+#define UTMP_PATH "/var/run/utmp"
+#undef BTMP_PATH
+#define BTMP_PATH "/var/log/btmp"
+#undef WTMP_PATH
+#define WTMP_PATH "/var/log/wtmp"
diff --git a/ubase/config.mk b/ubase/config.mk
@@ -0,0 +1,16 @@
+# ubase version
+VERSION = 0.1
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = $(PREFIX)/share/man
+
+CC = cc
+LD = $(CC)
+AR = ar
+RANLIB = ranlib
+
+CPPFLAGS = -D_FILE_OFFSET_BITS=64 -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS = -std=c99 -Wall -Wextra
+LDLIBS = -lcrypt
+LDFLAGS = -s
diff --git a/ubase/ctrlaltdel.8 b/ubase/ctrlaltdel.8
@@ -0,0 +1,20 @@
+.TH CTRLALTDEL 8 ubase-VERSION
+.SH NAME
+\fBctrlaltdel\fR - Set the function of Ctrl-Alt-Del combination
+.SH SYNOPSIS
+\fBctrlaltdel\fR [\fB-hs\fR]
+.SH DESCRIPTION
+Based on examination of the linux/kernel/sys.c code, it is clear that
+there are two supported functions that the Ctrl-Alt-Del sequence can
+perform: a hard reset, which immediately reboots the computer without
+calling sync(2) and without any other preparation; and a soft reset,
+which sends the SIGINT (interrupt) signal to the init process (this is
+always the process with PID 1). If this option is used, the init(8)
+program must support this feature.
+.SH OPTIONS
+.TP
+\fB-h\fR
+Perform a hard reset
+.TP
+\fB-s\fR
+Perform a soft reset
diff --git a/ubase/ctrlaltdel.c b/ubase/ctrlaltdel.c
@@ -0,0 +1,44 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/syscall.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "reboot.h"
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-hs]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int hflag = 0;
+ int sflag = 0;
+ int cmd;
+
+ ARGBEGIN {
+ case 'h':
+ hflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc > 0 || (hflag ^ sflag) == 0)
+ usage();
+
+ cmd = hflag ? LINUX_REBOOT_CMD_CAD_ON : LINUX_REBOOT_CMD_CAD_OFF;
+
+ if (syscall(__NR_reboot, LINUX_REBOOT_MAGIC1,
+ LINUX_REBOOT_MAGIC2, cmd, NULL) < 0)
+ eprintf("reboot:");
+ return 0;
+}
diff --git a/ubase/dd.1 b/ubase/dd.1
@@ -0,0 +1,45 @@
+.TH DD 1 ubase-VERSION
+.SH NAME
+\fBdd\fR - Copy a file
+.SH SYNOPSIS
+\fBdd\fR [\fIoperand...\fR]
+.SH DESCRIPTION
+\fBdd\fR copies the standard input to the standard output. By default input
+data is read and written in 64kB blocks. When finished, \fBdd\fR displays the
+number of records read and written as well as the total number of bytes copied.
+\fBdd\fR syncs the filesystem once it is done copying. If you want
+to disable that use the \fInosync\fR option.
+.SH OPERANDS
+.TP
+if=\fIfile\fR
+Read input from \fIfile\fR instead of the standard input.
+.TP
+of=\fIfile\fR
+Write output to \fIfile\fR instead of the standard output. If an initial
+portion of the output \fIfile\fR is skipped using the seek operand, the output
+file is truncated at that point.
+.TP
+bs[=\fIN\fR]
+If bs is not specified, the default blocksize is 64kB. If bs is specified
+without setting it to a specific value then an optimal value between the source
+and target filesystem will be selected. If this process fails it will fallback
+to the system's pagesize. Adjust \fIN\fR to set the block size of the transfers
+in bytes.
+.TP
+seek=\fIN\fR
+Seek \fIN\fR blocks from the beginning of the output before copying.
+.TP
+skip=\fIN\fR
+Skip \fIN\fR blocks from the beginning of the input before copying.
+.TP
+count=\fIN\fR
+Copy only \fIN\fR input blocks.
+.TP
+direct
+Use direct I/O for data.
+.TP
+quiet
+Enable quiet output.
+.TP
+nosync
+Do not sync the filesystem once we are done copying.
diff --git a/ubase/dd.c b/ubase/dd.c
@@ -0,0 +1,297 @@
+/* (C) 2011-2012 Sebastian Krahmer all rights reserved.
+ *
+ * Optimized dd, to speed up backups etc.
+ *
+ * Permission has been granted to release this code under MIT/X.
+ * The original code is at https://github.com/stealth/odd. This
+ * version of the code has been modified by sin@2f30.org.
+ */
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "util.h"
+
+struct dd_config {
+ const char *in, *out;
+ uint64_t skip, seek, count, b_in, b_out, rec_in, rec_out;
+ off_t fsize;
+ blksize_t bs;
+ char quiet, nosync, direct;
+ int saved_errno;
+ time_t t_start, t_end;
+};
+
+static int sigint = 0;
+
+static int
+prepare_copy(struct dd_config *ddc, int *ifd, int *ofd)
+{
+ struct stat st;
+ int fli = O_RDONLY|O_LARGEFILE|O_NOCTTY, flo = O_WRONLY|O_LARGEFILE|O_NOATIME|O_NOCTTY;
+ uid_t euid = 0;
+ long pagesize;
+
+ if (ddc->direct) {
+ fli |= O_DIRECT;
+ flo |= O_DIRECT;
+ }
+
+ if (stat(ddc->in, &st) < 0) {
+ ddc->saved_errno = errno;
+ return -1;
+ }
+
+ euid = geteuid();
+
+ if (!euid || st.st_uid == euid)
+ fli |= O_NOATIME;
+
+ if ((*ifd = open(ddc->in, fli)) < 0) {
+ ddc->saved_errno = errno;
+ return -1;
+ }
+
+ ddc->fsize = st.st_size;
+
+ /* If "bsize" is not given, use optimum of both FS' */
+ if (!ddc->bs) {
+ struct statfs fst;
+ memset(&fst, 0, sizeof(fst));
+ pagesize = sysconf(_SC_PAGESIZE);
+ if (pagesize <= 0)
+ pagesize = 0x1000;
+ if (statfs(ddc->out, &fst) < 0 || fst.f_bsize == 0)
+ fst.f_bsize = pagesize;
+ if ((unsigned long)fst.f_bsize > (unsigned long)st.st_blksize)
+ ddc->bs = fst.f_bsize;
+ else
+ ddc->bs = st.st_blksize;
+ if (ddc->bs == 0)
+ ddc->bs = pagesize;
+ }
+
+ /* check if device or regular file */
+ if (!S_ISREG(st.st_mode)) {
+ if (S_ISBLK(st.st_mode)) {
+ if (ioctl(*ifd, BLKGETSIZE64, &ddc->fsize) < 0) {
+ ddc->saved_errno = errno;
+ close(*ifd);
+ return -1;
+ }
+ } else {
+ ddc->fsize = (off_t)-1;
+ if (ddc->count)
+ ddc->fsize = ddc->count*ddc->bs;
+ }
+ }
+
+ /* skip and seek are in block items */
+ ddc->skip *= ddc->bs;
+ ddc->seek *= ddc->bs;
+
+ /* skip more bytes than are inside source file? */
+ if (ddc->fsize != (off_t)-1 && ddc->skip >= (uint64_t)ddc->fsize) {
+ ddc->saved_errno = EINVAL;
+ close(*ifd);
+ return -1;
+ }
+
+ if (!ddc->seek)
+ flo |= O_CREAT|O_TRUNC;
+
+ if ((*ofd = open(ddc->out, flo, st.st_mode)) < 0) {
+ ddc->saved_errno = errno;
+ close(*ifd);
+ return -1;
+ }
+
+ lseek(*ifd, ddc->skip, SEEK_SET);
+ lseek(*ofd, ddc->seek, SEEK_SET);
+ posix_fadvise(*ifd, ddc->skip, 0, POSIX_FADV_SEQUENTIAL);
+ posix_fadvise(*ofd, 0, 0, POSIX_FADV_DONTNEED);
+
+ /* count is in block items too */
+ ddc->count *= ddc->bs;
+
+ /* If no count is given, its the filesize minus skip offset */
+ if (ddc->count == 0)
+ ddc->count = ddc->fsize - ddc->skip;
+
+ return 0;
+}
+
+static int
+copy_splice(struct dd_config *ddc)
+{
+ int ifd, ofd, p[2] = {-1, -1};
+ ssize_t r = 0;
+ size_t n = 0;
+ fd_set rfd, wfd;
+
+ if (prepare_copy(ddc, &ifd, &ofd) < 0)
+ return -1;
+ if (pipe(p) < 0) {
+ ddc->saved_errno = errno;
+ close(ifd); close(ofd);
+ close(p[0]); close(p[1]);
+ return -1;
+ }
+#ifdef F_SETPIPE_SZ
+ for (n = 29; n >= 20; --n) {
+ if (fcntl(p[0], F_SETPIPE_SZ, 1<<n) != -1)
+ break;
+ }
+#endif
+ n = ddc->bs;
+ for (;ddc->b_out != ddc->count && !sigint;) {
+ FD_ZERO(&rfd);
+ FD_ZERO(&wfd);
+ FD_SET(ifd, &rfd);
+ FD_SET(ofd, &wfd);
+ r = select(ifd > ofd ? ifd + 1 : ofd + 1, &rfd, &wfd, NULL, NULL);
+ if (r < 0) {
+ ddc->saved_errno = errno;
+ break;
+ }
+ if (FD_ISSET(ifd, &rfd) == 1 && FD_ISSET(ofd, &wfd) == 1) {
+ if (n > ddc->count - ddc->b_out)
+ n = ddc->count - ddc->b_out;
+ r = splice(ifd, NULL, p[1], NULL, n, SPLICE_F_MORE);
+ if (r <= 0) {
+ ddc->saved_errno = errno;
+ break;
+ }
+ ++ddc->rec_in;
+ r = splice(p[0], NULL, ofd, NULL, r, SPLICE_F_MORE);
+ if (r <= 0) {
+ ddc->saved_errno = errno;
+ break;
+ }
+ ddc->b_out += r;
+ ++ddc->rec_out;
+ }
+ }
+ close(ifd);
+ close(ofd);
+ close(p[0]);
+ close(p[1]);
+ if (r < 0)
+ return -1;
+ return 0;
+}
+
+static int
+copy(struct dd_config *ddc)
+{
+ int r = 0;
+
+ ddc->t_start = time(NULL);
+
+ r = copy_splice(ddc);
+ ddc->t_end = time(NULL);
+
+ /* avoid div by zero */
+ if (ddc->t_start == ddc->t_end)
+ ++ddc->t_end;
+ return r;
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-h] [if=infile] [of=outfile] [bs[=N]] [seek=N] [skip=N] [count=N] [direct] [quiet] [nosync]\n", argv0);
+}
+
+static void
+print_stat(const struct dd_config *ddc)
+{
+ if (ddc->quiet)
+ return;
+
+ fprintf(stderr, "%"PRIu64" records in\n", ddc->rec_in);
+ fprintf(stderr, "%"PRIu64" records out\n", ddc->rec_out);
+ fprintf(stderr, "%"PRIu64" bytes (%"PRIu64" MB) copied", ddc->b_out,
+ ddc->b_out/(1<<20));
+ fprintf(stderr, ", %lu s, %f MB/s\n",
+ (unsigned long)ddc->t_end - ddc->t_start,
+ ((double)(ddc->b_out/(1<<20)))/(ddc->t_end - ddc->t_start));
+}
+
+static void
+sig_int(int unused)
+{
+ (void) unused;
+ fprintf(stderr, "SIGINT! Aborting ...\n");
+ sigint = 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i = 0;
+ char buf[1024];
+ struct dd_config config;
+
+ argv0 = argv[0];
+ memset(&config, 0, sizeof(config));
+ config.bs = 1<<16;
+ config.in = "/dev/stdin";
+ config.out = "/dev/stdout";
+
+ /* emulate 'dd' argument parsing */
+ for (i = 1; i < argc; ++i) {
+ memset(buf, 0, sizeof(buf));
+ if (sscanf(argv[i], "if=%1023s", buf) == 1)
+ config.in = strdup(buf);
+ else if (sscanf(argv[i], "of=%1023s", buf) == 1)
+ config.out = strdup(buf);
+ else if (sscanf(argv[i], "skip=%1023s", buf) == 1)
+ config.skip = estrtoul(buf, 0);
+ else if (sscanf(argv[i], "seek=%1023s", buf) == 1)
+ config.seek = estrtoul(buf, 0);
+ else if (sscanf(argv[i], "count=%1023s", buf) == 1)
+ config.count = estrtoul(buf, 0);
+ else if (strcmp(argv[i], "direct") == 0)
+ config.direct = 1;
+ else if (sscanf(argv[i], "bs=%1023s", buf) == 1)
+ config.bs = estrtoul(buf, 0);
+ else if (strcmp(argv[i], "bs") == 0)
+ config.bs = 0;
+ else if (strcmp(argv[i], "quiet") == 0)
+ config.quiet = 1;
+ else if (strcmp(argv[i], "nosync") == 0)
+ config.nosync = 1;
+ else if (strcmp(argv[i], "-h") == 0)
+ usage();
+ }
+
+ if (!config.in || !config.out)
+ usage();
+
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGINT, sig_int);
+
+ if (copy(&config) < 0)
+ weprintf("copy:");
+ print_stat(&config);
+
+ if (config.nosync == 0)
+ sync(); sync();
+ return config.saved_errno;
+}
diff --git a/ubase/df.1 b/ubase/df.1
@@ -0,0 +1,23 @@
+.TH DF 1 ubase-VERSION
+.SH NAME
+\fBdf\fR - Show file system usage
+.SH SYNOPSIS
+\fBdf\fR [\fB-ahsi\fR]
+.SH DESCRIPTION
+\fBdf\fR displays the amount of disk space available on a file system.
+If no arguments are given, df shows all the file systems using 512-byte
+blocks.
+.SH OPTIONS
+.TP
+\fB-a\fR
+Show all file systems including dummy ones. This is the default
+option.
+.TP
+\fB-h\fR
+Not implemented.
+.TP
+\fB-s\fR
+Not implemented.
+.TP
+\fB-i\fR
+Not implemented.
diff --git a/ubase/df.c b/ubase/df.c
@@ -0,0 +1,136 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/statvfs.h>
+
+#include <mntent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+static long blksize = 512;
+static int aflag = 0;
+static int hflag = 0;
+static int kflag = 0;
+
+static void mnt_show(const char *fsname, const char *dir);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-a]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct mntent *me = NULL;
+ FILE *fp;
+
+ ARGBEGIN {
+ case 'a':
+ aflag = 1;
+ break;
+ case 'h':
+ hflag = 1;
+ kflag = 0;
+ break;
+ case 'k':
+ kflag = 1;
+ hflag = 0;
+ blksize = 1024;
+ break;
+ case 's':
+ case 'i':
+ eprintf("not implemented\n");
+ default:
+ usage();
+ } ARGEND;
+
+ if (hflag)
+ printf("Filesystem Size Used "
+ "Avail Capacity Mounted on\n");
+ else
+ printf("Filesystem %ld-blocks Used "
+ "Avail Capacity Mounted on\n", blksize);
+
+ fp = setmntent("/proc/mounts", "r");
+ if (!fp)
+ eprintf("setmntent %s:", "/proc/mounts");
+ while ((me = getmntent(fp)) != NULL) {
+ if (aflag == 0)
+ if (strcmp(me->mnt_type, "rootfs") == 0)
+ continue;
+ mnt_show(me->mnt_fsname, me->mnt_dir);
+ }
+ endmntent(fp);
+
+ return 0;
+}
+
+#define CALC_POWER(n, power, base, i) do { \
+ while (n > power) { \
+ power = power * base; \
+ i++; \
+ } \
+} while(0)
+
+static void
+print_human(
+ const char *fsname,
+ unsigned long long total,
+ unsigned long long used,
+ unsigned long long avail,
+ int capacity,
+ const char *dir)
+{
+ long base = 1024;
+ unsigned long long power_total = base;
+ unsigned long long power_used = base;
+ unsigned long long power_avail = base;
+ char postfixes[] = {'B', 'K', 'M', 'G', 'T', 'P', 'E'};
+ int i = 0, j = 0, k = 0;
+
+ total = total * blksize;
+ used = used * blksize;
+ avail = avail * blksize;
+
+ CALC_POWER(total, power_total, base, i);
+ CALC_POWER(used, power_used, base, j);
+ CALC_POWER(avail, power_avail, base, k);
+
+ total = i ? total / (power_total / base) : total;
+ used = j ? used / (power_used / base) : used;
+ avail = k ? avail / (power_avail / base) : avail;
+ printf("%-12s %9llu%c %9llu%c %9llu%c %7d%% %s\n",
+ fsname, total, postfixes[i], used, postfixes[j],
+ avail, postfixes[k], capacity, dir);
+}
+
+static void
+mnt_show(const char *fsname, const char *dir)
+{
+ struct statvfs s;
+ unsigned long long total, used, avail;
+ int capacity = 0;
+ int bs;
+
+ statvfs(dir, &s);
+
+ bs = s.f_frsize / blksize;
+ total = s.f_blocks * bs;
+ avail = s.f_bfree * bs;
+ used = total - avail;
+
+ if (used + avail) {
+ capacity = (used * 100) / (used + avail);
+ if (used * 100 != capacity * (used + avail))
+ capacity++;
+ }
+
+ if (hflag)
+ print_human(fsname, total, used, avail, capacity, dir);
+ else
+ printf("%-12s %9llu %9llu %9llu %7d%% %s\n",
+ fsname, total, used, avail, capacity, dir);
+}
diff --git a/ubase/dmesg.1 b/ubase/dmesg.1
@@ -0,0 +1,22 @@
+.TH DMESG 1 ubase-VERSION
+.SH NAME
+\fBdmesg\fR - Print or control the kernel ring buffer
+.SH SYNOPSIS
+\fBdmesg\fR [\fB-Ccr\fR] [\fB-n\fR \fIlevel\fR]
+.SH DESCRIPTION
+\fBdmesg\fR examines or controls the kernel ring buffer. By default
+it reads all the messages from the kernel ring buffer and prints them
+on stdout.
+.SH OPTIONS
+.TP
+\fB-C\fR
+Clear the ring buffer.
+.TP
+\fB-c\fR
+Clear the ring buffer after printing its contents.
+.TP
+\fB-n\fR
+Set the console \fIlevel\fR.
+.TP
+\fB-r\fR
+Print the raw message buffer.
diff --git a/ubase/dmesg.c b/ubase/dmesg.c
@@ -0,0 +1,83 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/klog.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void dmesg_show(const void *buf, size_t n);
+
+enum {
+ SYSLOG_ACTION_READ_ALL = 3,
+ SYSLOG_ACTION_CLEAR = 5,
+ SYSLOG_ACTION_CONSOLE_LEVEL = 8,
+ SYSLOG_ACTION_SIZE_BUFFER = 10
+};
+
+static void
+usage(void)
+{
+ eprintf("usage: [-Ccr] [-n level] %s\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int n;
+ char *buf;
+ int cflag = 0;
+ long level;
+
+ ARGBEGIN {
+ case 'C':
+ if (klogctl(SYSLOG_ACTION_CLEAR, NULL, 0) < 0)
+ eprintf("klogctl:");
+ return 0;
+ case 'c':
+ cflag = 1;
+ break;
+ case 'r':
+ break;
+ case 'n':
+ level = estrtol(EARGF(usage()), 10);
+ if (klogctl(SYSLOG_ACTION_CONSOLE_LEVEL, NULL, level) < 0)
+ eprintf("klogctl:");
+ return 0;
+ default:
+ usage();
+ } ARGEND;
+
+ n = klogctl(SYSLOG_ACTION_SIZE_BUFFER, NULL, 0);
+ if (n < 0)
+ eprintf("klogctl:");
+
+ buf = emalloc(n);
+
+ n = klogctl(SYSLOG_ACTION_READ_ALL, buf, n);
+ if (n < 0)
+ eprintf("klogctl:");
+
+ dmesg_show(buf, n);
+
+ if (cflag && klogctl(SYSLOG_ACTION_CLEAR, NULL, 0) < 0)
+ eprintf("klogctl:");
+
+ free(buf);
+ return 0;
+}
+
+static void
+dmesg_show(const void *buf, size_t n)
+{
+ const char *p = buf;
+ ssize_t r;
+
+ r = write(1, p, n);
+ if (r < 0)
+ eprintf("write:");
+ if (r > 0 && p[r - 1] != '\n')
+ putchar('\n');
+}
diff --git a/ubase/eject.1 b/ubase/eject.1
@@ -0,0 +1,12 @@
+.TH EJECT 1 ubase-VERSION
+.SH NAME
+\fBeject\fR - Eject removable media
+.SH SYNOPSIS
+\fBeject\fR [\fB-t\fR]
+.SH DESCRIPTION
+\fBeject\fR allows the CD-ROM tray to be opened or closed under software
+control. If no arguments are given, the CD-ROM tray is opened.
+.SH OPTIONS
+.TP
+\fB-t\fR
+If supported, close the CD-ROM tray.
diff --git a/ubase/eject.c b/ubase/eject.c
@@ -0,0 +1,56 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+enum {
+ CDROM_EJECT = 0x5309,
+ CDROM_CLOSE_TRAY = 0x5319,
+};
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-t] [devname]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fd, out;
+ char *cdrom = "/dev/sr0";
+ int tflag = 0;
+
+ ARGBEGIN {
+ case 't':
+ tflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc > 1)
+ usage();
+ else if (argc == 1)
+ cdrom = argv[0];
+
+ fd = open(cdrom, O_RDONLY | O_NONBLOCK);
+ if (fd < 0)
+ eprintf("open %s:", cdrom);
+ if (tflag) {
+ if (ioctl(fd, CDROM_CLOSE_TRAY, &out) < 0)
+ eprintf("ioctl:");
+ } else {
+ if (ioctl(fd, CDROM_EJECT, &out) < 0)
+ eprintf("ioctl:");
+ }
+ close(fd);
+ return 0;
+}
diff --git a/ubase/fallocate.1 b/ubase/fallocate.1
@@ -0,0 +1,20 @@
+.TH FALLOCATE 1 ubase-VERSION
+.SH NAME
+\fBfallocate\fR - Preallocate blocks to a file
+.SH SYNOPSIS
+\fBfallocate\fR [\fB-o\fI offset\fR] \fB-l\fR \fIlength file\fR
+.SH DESCRIPTION
+\fBfallocate\fR preallocates blocks to a file. Only certain filesystems
+support the fallocate system call. This is a very fast operation to allocate
+uninitialized blocks in a file without doing any IO.
+As of the Linux kernel v2.6.31, the fallocate system call is supported
+by the btrfs, ext4, ocfs2, and xfs filesystems.
+.SH OPTIONS
+.TP
+\fB-o\fR
+Specifies the beginning offset of the allocation, in bytes.
+.TP
+\fB-l\fR
+Specifies the length of the allocation, in bytes.
+.SH SEE ALSO
+fallocate(2)
diff --git a/ubase/fallocate.c b/ubase/fallocate.c
@@ -0,0 +1,46 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-o offset] -l length file\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ off_t size = 0, offset = 0;
+
+ ARGBEGIN {
+ case 'l':
+ size = estrtol(EARGF(usage()), 10);
+ break;
+ case 'o':
+ offset = estrtol(EARGF(usage()), 10);
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc != 1 || !size)
+ usage();
+
+ fd = open(argv[0], O_RDWR | O_CREAT, 0644);
+ if (fd < 0)
+ eprintf("open %s:", argv[0]);
+
+ if (posix_fallocate(fd, offset, size) < 0)
+ eprintf("posix_fallocate:");
+
+ close(fd);
+ return 0;
+}
diff --git a/ubase/free.1 b/ubase/free.1
@@ -0,0 +1,21 @@
+.TH FREE 1 ubase-VERSION
+.SH NAME
+\fBfree\fR - Display amount of free and used memory in the system
+.SH SYNOPSIS
+\fBfree\fR [\fB-bkmg\fR]
+.SH DESCRIPTION
+\fBfree\fR displays the total amount of free and used physical and swap
+memory in the system, as well as the buffers used by the kernel.
+.SH OPTIONS
+.TP
+\fB-b\fR
+Display the amount of memory in bytes. This is the default.
+.TP
+\fB-k\fR
+Display the amount of memory in kilobytes.
+.TP
+\fB-m\fR
+Display the amount of memory in megabytes.
+.TP
+\fB-g\fR
+Display the amount of memory in gigabytes.
diff --git a/ubase/free.c b/ubase/free.c
@@ -0,0 +1,72 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/sysinfo.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-bkmg]\n", argv0);
+}
+
+static unsigned int mem_unit = 1;
+static unsigned int unit_shift;
+
+static unsigned long long
+scale(unsigned long long v)
+{
+ return (v * mem_unit) >> unit_shift;
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sysinfo info;
+
+ if (sysinfo(&info) < 0)
+ eprintf("sysinfo:");
+ mem_unit = info.mem_unit ? info.mem_unit : 1;
+
+ ARGBEGIN {
+ case 'b':
+ unit_shift = 0;
+ break;
+ case 'k':
+ unit_shift = 10;
+ break;
+ case 'm':
+ unit_shift = 20;
+ break;
+ case 'g':
+ unit_shift = 30;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ printf(" %13s%13s%13s%13s%13s\n",
+ "total",
+ "used",
+ "free",
+ "shared", "buffers");
+ printf("Mem: ");
+ printf("%13llu%13llu%13llu%13llu%13llu\n",
+ scale(info.totalram),
+ scale(info.totalram - info.freeram),
+ scale(info.freeram),
+ scale(info.sharedram),
+ scale(info.bufferram));
+ printf("-/+ buffers/cache:");
+ printf("%13llu%13llu\n",
+ scale(info.totalram - info.freeram - info.bufferram),
+ scale(info.freeram + info.bufferram));
+ printf("Swap:");
+ printf("%13llu%13llu%13llu\n",
+ scale(info.totalswap),
+ scale(info.totalswap - info.freeswap),
+ scale(info.freeswap));
+ return 0;
+}
diff --git a/ubase/freeramdisk.8 b/ubase/freeramdisk.8
@@ -0,0 +1,8 @@
+.TH FREERAMDISK 8 ubase-VERSION
+.SH NAME
+\fBfreeramdisk\fR - Free memory used by the loadlin ramdisk
+.SH SYNOPSIS
+\fBfreeramdisk\fR
+.SH DESCRIPTION
+\fBfreeramdisk\fR frees the memory that is used by the ramdisk.
+It uses the \fI/dev/ram\fR device node.
diff --git a/ubase/freeramdisk.c b/ubase/freeramdisk.c
@@ -0,0 +1,39 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *dev = "/dev/ram";
+ int fd;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc != 0)
+ usage();
+
+ if ((fd = open(dev, O_RDWR)) < 0)
+ eprintf("open: %s:", dev);
+ if (ioctl(fd, BLKFLSBUF, dev) < 0)
+ eprintf("BLKFLSBUF %s:", dev);
+ close(fd);
+ return 0;
+}
diff --git a/ubase/fsfreeze.8 b/ubase/fsfreeze.8
@@ -0,0 +1,24 @@
+.TH FSFREEZE 8 ubase-VERSION
+.SH NAME
+\fBfsfreeze\fR - Suspend access to a filesystem
+.SH SYNOPSIS
+\fBfsfreeze\fR [\fB-f\fR] [\fB-u\fR] \fImountpoint\fR
+.SH DESCRIPTION
+\fBfsfreeze\fR suspends and resumes access to a filesystem.
+\fBfsfreeze\fR is intended to be used with hardware RAID
+devices that support the creation of snapshots.
+The \fImountpoint\fR argument is the pathname of the directory
+where the filesystem is mounted. The filesystem must be mounted
+to be frozen.
+.SH OPTIONS
+.TP
+\fB-f\fR
+Freeze the filesystem mounted at \fImountpoint\fR.
+.TP
+\fB-u\fR
+Unfreeze the filesystem mounted at \fImountpoint\fR.
+.SH BUGS
+.TP
+Only works for ext3/4, reiserfs, jfs and xfs.
+.SH SEE ALSO
+mount(8)
diff --git a/ubase/fsfreeze.c b/ubase/fsfreeze.c
@@ -0,0 +1,54 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+#define FIFREEZE _IOWR('X', 119, int) /* Freeze */
+#define FITHAW _IOWR('X', 120, int) /* Thaw */
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-f] [-u] mountpoint\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fflag = 0;
+ int uflag = 0;
+ long p = 1;
+ int fd;
+
+ ARGBEGIN {
+ case 'f':
+ fflag = 1;
+ break;
+ case 'u':
+ uflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc != 1)
+ usage();
+
+ if ((fflag ^ uflag) == 0)
+ usage();
+
+ fd = open(argv[0], O_RDONLY);
+ if (fd < 0)
+ eprintf("open: %s:", argv[0]);
+ if (ioctl(fd, fflag == 1 ? FIFREEZE : FITHAW, &p) < 0)
+ eprintf("%s %s:", fflag == 1 ? "FIFREEZE" : "FITHAW", argv[0]);
+ close(fd);
+ return 0;
+}
diff --git a/ubase/getty.8 b/ubase/getty.8
@@ -0,0 +1,11 @@
+.TH GETTY 8 ubase-VERSION
+.SH NAME
+\fBgetty\fR - Suckless linux getty
+.SH SYNOPSIS
+\fBgetty\fR [\fItty\fR] [\fIterm\fR] [\fIcmd\fR] [\fIargs...\fR]
+.SH DESCRIPTION
+\fBgetty\fR opens a tty device, prompts for a login name and by default
+invokes the /bin/login program. You can start another program instead of
+/bin/login via \fIcmd\fR with \fIargs\fR. The hostname is printed in the
+login name prompt as well. The \fItty\fR should be specified using an
+absolute path.
diff --git a/ubase/getty.c b/ubase/getty.c
@@ -0,0 +1,140 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <utmp.h>
+
+#include "config.h"
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [tty] [term] [cmd] [args...]\n", argv0);
+}
+
+static char *tty = "/dev/tty1";
+static char *defaultterm = "linux";
+
+int
+main(int argc, char *argv[])
+{
+ char term[128], logname[LOGIN_NAME_MAX], c;
+ char hostname[HOST_NAME_MAX + 1];
+ struct utmp usr;
+ struct sigaction sa;
+ FILE *fp;
+ int fd;
+ unsigned int i = 0;
+ ssize_t n;
+ long pos;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ strlcpy(term, defaultterm, sizeof(term));
+ if (argc > 0) {
+ tty = argv[0];
+ if (argc > 1)
+ strlcpy(term, argv[1], sizeof(term));
+ }
+
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sigaction(SIGHUP, &sa, NULL);
+
+ setenv("TERM", term, 1);
+
+ setsid();
+
+ fd = open(tty, O_RDWR);
+ if (fd < 0)
+ eprintf("open %s:", tty);
+ if (isatty(fd) == 0)
+ eprintf("%s is not a tty\n", tty);
+
+ /* steal the controlling terminal if necessary */
+ if (ioctl(fd, TIOCSCTTY, (void *)1) != 0)
+ weprintf("TIOCSCTTY: could not set controlling tty\n");
+ vhangup();
+ close(fd);
+
+ fd = open(tty, O_RDWR);
+ if (fd < 0)
+ eprintf("open %s:", tty);
+ dup2(fd, 0);
+ dup2(fd, 1);
+ dup2(fd, 2);
+ if (fchown(fd, 0, 0) < 0)
+ weprintf("fchown %s:", tty);
+ if (fchmod(fd, 0600) < 0)
+ weprintf("fchmod %s:", tty);
+ if (fd > 2)
+ close(fd);
+
+ sa.sa_handler = SIG_DFL;
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sigaction(SIGHUP, &sa, NULL);
+
+ /* Clear all utmp entries for this tty */
+ fp = fopen(UTMP_PATH, "r+");
+ if (fp) {
+ do {
+ pos = ftell(fp);
+ if (fread(&usr, sizeof(usr), 1, fp) != 1)
+ break;
+ if (usr.ut_line[0] == '\0')
+ continue;
+ if (strcmp(usr.ut_line, tty) != 0)
+ continue;
+ memset(&usr, 0, sizeof(usr));
+ fseek(fp, pos, SEEK_SET);
+ if (fwrite(&usr, sizeof(usr), 1, fp) != 1)
+ break;
+ } while (1);
+ if (ferror(fp))
+ weprintf("%s: I/O error:", UTMP_PATH);
+ fclose(fp);
+ }
+
+ if (argc > 2)
+ return execvp(argv[2], argv + 2);
+
+ if (gethostname(hostname, sizeof(hostname)) == 0)
+ printf("%s ", hostname);
+ printf("login: ");
+ fflush(stdout);
+
+ /* Flush pending input */
+ ioctl(0, TCFLSH, (void *)0);
+ memset(logname, 0, sizeof(logname));
+ while (1) {
+ n = read(0, &c, 1);
+ if (n < 0)
+ eprintf("read:");
+ if (n == 0)
+ return 1;
+ if (i >= sizeof(logname) - 1)
+ eprintf("login name too long\n");
+ if (c == '\n' || c == '\r')
+ break;
+ logname[i++] = c;
+ }
+ if (logname[0] == '-')
+ eprintf("login name cannot start with '-'\n");
+ if (logname[0] == '\0')
+ return 1;
+ return execlp("/bin/login", "login", "-p", logname, NULL);
+}
diff --git a/ubase/halt.8 b/ubase/halt.8
@@ -0,0 +1,16 @@
+.TH HALT 8 ubase-VERSION
+.SH NAME
+\fBhalt\fR - Power-off or reboot the machine
+.SH SYNOPSIS
+\fBhalt\fR [\fB-pr\fR]
+.SH DESCRIPTION
+\fBhalt\fR can be used to power-off or reboot the machine.
+This is a low-level tool and should not be used directly or data-loss
+can happen if the filesystems are not properly unmounted first.
+.SH OPTIONS
+.TP
+\fB-p\fR
+Power-off the machine.
+.TP
+\fB-r\fR
+Reboot the machine.
diff --git a/ubase/halt.c b/ubase/halt.c
@@ -0,0 +1,51 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/syscall.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "reboot.h"
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-pr]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int pflag = 0, rflag = 0;
+ int cmd = LINUX_REBOOT_CMD_HALT;
+
+ ARGBEGIN {
+ case 'p':
+ pflag = 1;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc > 0)
+ usage();
+
+ sync();
+
+ if (pflag && rflag)
+ usage();
+
+ if (pflag)
+ cmd = LINUX_REBOOT_CMD_POWER_OFF;
+ if (rflag)
+ cmd = LINUX_REBOOT_CMD_RESTART;
+
+ if (syscall(__NR_reboot, LINUX_REBOOT_MAGIC1,
+ LINUX_REBOOT_MAGIC2, cmd, NULL) < 0)
+ eprintf("reboot:");
+ return 0;
+}
diff --git a/ubase/hwclock.8 b/ubase/hwclock.8
@@ -0,0 +1,25 @@
+.TH HWCLOCK 8 ubase-VERSION
+.SH NAME
+\fBhwclock\fR - Query or set the hardware clock
+.SH SYNOPSIS
+\fBhwclock\fR [\fB-rsw\fR] [\fB-u\fR] [\fIdev\fR]
+.SH DESCRIPTION
+\fBhwclock\fR is a tool for accessing the hardware clock. You can display
+the current time, set the hardware clock from the System Time, or
+set the System Time from the hardware clock. It currently only works with UTC.
+You can use \fIdev\fR to specify the RTC device node absolute path. By default
+it will use \fI/dev/rtc\fR.
+.SH FUNCTIONS
+.TP
+\fB-r\fR
+Read the hardware clock and print the time on stdout.
+.TP
+\fB-s\fR
+Set the system time from the hardware clock.
+.TP
+\fB-w\fR
+Set the hardware clock to the system time.
+.SH OPTIONS
+.TP
+\fB-u\fR
+Use UTC. This is the default and only option.
diff --git a/ubase/hwclock.c b/ubase/hwclock.c
@@ -0,0 +1,165 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "rtc.h"
+#include "util.h"
+
+static void readrtctm(struct tm *, int);
+static void writertctm(struct tm *, int);
+static void show(char *);
+static void hctosys(char *);
+static void systohc(char *);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-rsw] [-u] [dev]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *dev = "/dev/rtc";
+ int rflag = 0;
+ int sflag = 0;
+ int wflag = 0;
+
+ ARGBEGIN {
+ case 'r':
+ rflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 'w':
+ wflag = 1;
+ break;
+ case 'u':
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc > 1)
+ usage();
+ else if (argc == 1)
+ dev = argv[0];
+
+ if ((rflag ^ sflag ^ wflag) == 0)
+ eprintf("missing or incompatible function\n");
+
+ /* Only UTC support at the moment */
+ setenv("TZ", "UTC0", 1);
+ tzset();
+
+ if (rflag == 1)
+ show(dev);
+ else if (sflag == 1)
+ hctosys(dev);
+ else if (wflag == 1)
+ systohc(dev);
+
+ return 0;
+}
+
+static void
+readrtctm(struct tm *tm, int fd)
+{
+ struct rtc_time rt;
+
+ memset(&rt, 0, sizeof(rt));
+ if (ioctl(fd, RTC_RD_TIME, &rt) < 0)
+ eprintf("RTC_RD_TIME:");
+ tm->tm_sec = rt.tm_sec;
+ tm->tm_min = rt.tm_min;
+ tm->tm_hour = rt.tm_hour;
+ tm->tm_mday = rt.tm_mday;
+ tm->tm_mon = rt.tm_mon;
+ tm->tm_year = rt.tm_year;
+ tm->tm_wday = rt.tm_wday;
+ tm->tm_yday = rt.tm_yday;
+ tm->tm_isdst = rt.tm_isdst;
+}
+
+static void
+writertctm(struct tm *tm, int fd)
+{
+ struct rtc_time rt;
+
+ rt.tm_sec = tm->tm_sec;
+ rt.tm_min = tm->tm_min;
+ rt.tm_hour = tm->tm_hour;
+ rt.tm_mday = tm->tm_mday;
+ rt.tm_mon = tm->tm_mon;
+ rt.tm_year = tm->tm_year;
+ rt.tm_wday = tm->tm_wday;
+ rt.tm_yday = tm->tm_yday;
+ rt.tm_isdst = tm->tm_isdst;
+ if (ioctl(fd, RTC_SET_TIME, &rt) < 0)
+ eprintf("RTC_SET_TIME:");
+}
+
+static void
+show(char *dev)
+{
+ struct tm tm;
+ time_t t;
+ int fd;
+
+ fd = open(dev, O_RDONLY);
+ if (fd < 0)
+ eprintf("open %s:", dev);
+ readrtctm(&tm, fd);
+ t = mktime(&tm);
+ printf("%s", asctime(localtime(&t)));
+ close(fd);
+}
+
+static void
+hctosys(char *dev)
+{
+ struct timeval tv;
+ struct tm tm;
+ int r;
+ int fd;
+
+ fd = open(dev, O_RDONLY);
+ if (fd < 0)
+ eprintf("open %s:", dev);
+ readrtctm(&tm, fd);
+ tv.tv_sec = mktime(&tm);
+ tv.tv_usec = 0;
+ r = settimeofday(&tv, NULL);
+ if (r < 0)
+ eprintf("settimeofday:");
+ close(fd);
+}
+
+static void
+systohc(char *dev)
+{
+ struct timeval tv;
+ struct tm *tm;
+ time_t t;
+ int fd;
+
+ fd = open(dev, O_WRONLY);
+ if (fd < 0)
+ eprintf("open %s:", dev);
+ gettimeofday(&tv, NULL);
+ t = tv.tv_sec;
+ tm = gmtime(&t);
+ weprintf("warning: assuming UTC for systohc\n");
+ writertctm(tm, fd);
+ close(fd);
+}
diff --git a/ubase/id.1 b/ubase/id.1
@@ -0,0 +1,21 @@
+.TH ID 1 ubase-VERSION
+.SH NAME
+\fBid\fR - Print real and effective user and group IDs
+.SH SYNOPSIS
+\fBid\fR [\fB-g\fR] [\fB-u\fR] [\fB-G\fR] \fR[\fIuser\fR|\fIuid\fR]
+.SH DESCRIPTION
+\fBid\fR prints user and group information of the calling process to standard output.
+If a login name or uid is specified, the user and group information of that
+user is displayed.
+.SH OPTIONS
+.TP
+\fB-g\fR
+Print only the effective group ID.
+.TP
+\fB-u\fR
+Print only the effective user ID.
+.TP
+\fB-G\fR
+Display group information as whitespace separated numbers, in no particular order.
+.SH SEE ALSO
+who(1)
diff --git a/ubase/id.c b/ubase/id.c
@@ -0,0 +1,143 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <grp.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void groupid(struct passwd *pw);
+static void user(struct passwd *pw);
+static void userid(uid_t id);
+static void usernam(const char *nam);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-g] [-u] [-G] [user | uid]\n", argv0);
+}
+
+static int Gflag = 0;
+
+int
+main(int argc, char *argv[])
+{
+ ARGBEGIN {
+ case 'g':
+ printf("%d\n", getegid());
+ return 0;
+ case 'u':
+ printf("%d\n", geteuid());
+ return 0;
+ case 'G':
+ Gflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ switch (argc) {
+ case 0:
+ userid(getuid());
+ break;
+ case 1:
+ /* user names can't begin [0-9] */
+ if (isdigit(argv[0][0]))
+ userid(estrtol(argv[0], 0));
+ else
+ usernam(argv[0]);
+ break;
+ default:
+ usage();
+ }
+
+ return 0;
+}
+
+static void
+groupid(struct passwd *pw)
+{
+ gid_t gid, groups[NGROUPS_MAX];
+ int ngroups;
+ int i;
+
+ ngroups = NGROUPS_MAX;
+ getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups);
+ for (i = 0; i < ngroups; i++) {
+ gid = groups[i];
+ printf("%u", gid);
+ if (i < ngroups - 1)
+ putchar(' ');
+ }
+ putchar('\n');
+}
+
+static void
+usernam(const char *nam)
+{
+ struct passwd *pw;
+
+ errno = 0;
+ pw = getpwnam(nam);
+ if (!pw) {
+ if (errno)
+ eprintf("getpwnam %s:", nam);
+ else
+ eprintf("getpwnam %s: no such user\n", nam);
+ }
+ if (Gflag)
+ groupid(pw);
+ else
+ user(pw);
+}
+
+static void
+userid(uid_t id)
+{
+ struct passwd *pw;
+
+ errno = 0;
+ pw = getpwuid(id);
+ if (!pw) {
+ if (errno)
+ eprintf("getpwuid %d:", id);
+ else
+ eprintf("getpwuid %d: no such user\n", id);
+ }
+ if (Gflag)
+ groupid(pw);
+ else
+ user(pw);
+}
+
+static void
+user(struct passwd *pw)
+{
+ struct group *gr;
+ gid_t gid, groups[NGROUPS_MAX];
+ int ngroups;
+ int i;
+
+ printf("uid=%u(%s)", pw->pw_uid, pw->pw_name);
+ printf(" gid=%u", pw->pw_gid);
+ if (!(gr = getgrgid(pw->pw_gid)))
+ eprintf("getgrgid:");
+ printf("(%s)", gr->gr_name);
+
+ ngroups = NGROUPS_MAX;
+ getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups);
+ for (i = 0; i < ngroups; i++) {
+ gid = groups[i];
+ printf("%s%u", !i ? " groups=" : ",", gid);
+ if (!(gr = getgrgid(gid)))
+ eprintf("getgrgid:");
+ printf("(%s)", gr->gr_name);
+ }
+ putchar('\n');
+}
diff --git a/ubase/insmod.8 b/ubase/insmod.8
@@ -0,0 +1,10 @@
+.TH INSMOD 8 ubase-VERSION
+.SH NAME
+\fBinsmod\fR - Insert a module into the Linux kernel
+.SH SYNOPSIS
+\fBinsmod\fR \fIfilename\fR [\fIargs...\fR]
+.SH DESCRIPTION
+\fBinsmod\fR inserts the module specified by \fIfilename\fR
+into the kernel. It does not handle module dependencies.
+.SH SEE ALSO
+rmmod(8), lsmod(8)
diff --git a/ubase/insmod.c b/ubase/insmod.c
@@ -0,0 +1,69 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+#include <sys/syscall.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s filename [args...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *buf = NULL, *opts = NULL;
+ size_t blen, plen = 0;
+ int i, fd;
+ ssize_t n;
+ struct stat sb;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ fd = open(argv[0], O_RDONLY);
+ if (fd < 0)
+ eprintf("open %s:", argv[0]);
+ if (fstat(fd, &sb) < 0)
+ eprintf("stat %s:", argv[0]);
+ blen = sb.st_size;
+ buf = emalloc(blen);
+
+ n = read(fd, buf, blen);
+ if (n < 0 || (size_t)n != blen)
+ eprintf("read:");
+
+ argc--;
+ argv++;
+
+ for (i = 0; i < argc; i++)
+ plen += strlen(argv[i]);
+ if (plen > 0) {
+ plen += argc;
+ opts = ecalloc(1, plen);
+ for (i = 0; i < argc; i++) {
+ strcat(opts, argv[i]);
+ if (i + 1 < argc)
+ strcat(opts, " ");
+ }
+ }
+
+ if (syscall(__NR_init_module, buf, blen, !opts ? "" : opts) < 0)
+ eprintf("init_module:");
+
+ free(opts);
+ free(buf);
+ return 0;
+}
diff --git a/ubase/killall5.8 b/ubase/killall5.8
@@ -0,0 +1,18 @@
+.TH KILLALL5 8 ubase-VERSION
+.SH NAME
+\fBkillall\fR - Send a signal to all processes
+.SH SYNOPSIS
+\fBkillall5\fR [\fB-o\fI pid1,pid2,...,pidN\fR] [\fB-s\fI signal\fR]
+.SH DESCRIPTION
+\fBkillall5\fR is an implementation of the SystemV killall command.
+It sends a signal to all processes except kernel threads and the processes
+in its own session. It is primarily used by the system's init scripts.
+.SH OPTIONS
+.TP
+\fB-o\fR
+Tell killall5 to omit processes with that process id.
+.TP
+\fB-s\fR
+Send \fIsignal\fR instead of the default SIGTERM.
+.SH SEE ALSO
+halt(8), reboot(8)
diff --git a/ubase/killall5.c b/ubase/killall5.c
@@ -0,0 +1,115 @@
+/* See LICENSE file for copyright and license details. */
+#include <dirent.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "proc.h"
+#include "queue.h"
+#include "util.h"
+
+struct {
+ const char *name;
+ int sig;
+} sigs[] = {
+#define SIG(n) { #n, SIG##n }
+ SIG(ABRT), SIG(ALRM), SIG(BUS), SIG(CHLD), SIG(CONT), SIG(FPE), SIG(HUP),
+ SIG(ILL), SIG(INT), SIG(KILL), SIG(PIPE), SIG(QUIT), SIG(SEGV), SIG(STOP),
+ SIG(TERM), SIG(TSTP), SIG(TTIN), SIG(TTOU), SIG(USR1), SIG(USR2), SIG(URG),
+#undef SIG
+};
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-o pid1,pid2,..,pidN] [-s signal]\n", argv0);
+}
+
+struct pidentry {
+ pid_t pid;
+ TAILQ_ENTRY(pidentry) entry;
+};
+
+static TAILQ_HEAD(omitpid_head, pidentry) omitpid_head;
+
+int
+main(int argc, char *argv[])
+{
+ struct pidentry *pe, *tmp;
+ int oflag = 0;
+ char *p, *arg = NULL;
+ DIR *dp;
+ struct dirent *entry;
+ char *end, *v;
+ int sig = SIGTERM;
+ pid_t pid;
+ size_t i;
+
+ ARGBEGIN {
+ case 's':
+ v = EARGF(usage());
+ sig = strtol(v, &end, 0);
+ if (*end == '\0')
+ break;
+ for (i = 0; i < LEN(sigs); i++) {
+ if (strcasecmp(v, sigs[i].name) == 0) {
+ sig = sigs[i].sig;
+ break;
+ }
+ }
+ if (i == LEN(sigs))
+ eprintf("%s: unknown signal\n", v);
+ break;
+ case 'o':
+ oflag = 1;
+ arg = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ TAILQ_INIT(&omitpid_head);
+
+ for (p = strtok(arg, ","); p; p = strtok(NULL, ",")) {
+ pe = emalloc(sizeof(*pe));
+ pe->pid = estrtol(p, 10);
+ TAILQ_INSERT_TAIL(&omitpid_head, pe, entry);
+ }
+
+ if (sig != SIGSTOP && sig != SIGCONT)
+ kill(-1, SIGSTOP);
+
+ if (!(dp = opendir("/proc")))
+ eprintf("opendir /proc:");
+ while ((entry = readdir(dp))) {
+ if (pidfile(entry->d_name) == 0)
+ continue;
+ pid = estrtol(entry->d_name, 10);
+ if (pid == 1 || pid == getpid() ||
+ getsid(pid) == getsid(0) || getsid(pid) == 0)
+ continue;
+ if (oflag == 1) {
+ TAILQ_FOREACH(pe, &omitpid_head, entry)
+ if (pe->pid == pid)
+ break;
+ if (pe)
+ continue;
+ }
+ kill(pid, sig);
+ }
+ closedir(dp);
+
+ if (sig != SIGSTOP && sig != SIGCONT)
+ kill(-1, SIGCONT);
+
+ for (pe = TAILQ_FIRST(&omitpid_head); pe; pe = tmp) {
+ tmp = TAILQ_NEXT(pe, entry);
+ TAILQ_REMOVE(&omitpid_head, pe, entry);
+ free(pe);
+ }
+
+ return 0;
+}
diff --git a/ubase/last.c b/ubase/last.c
@@ -0,0 +1,64 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <libgen.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <utmp.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [user]\n", argv0);
+}
+
+int
+main(int argc, char **argv)
+{
+ FILE *fp;
+ struct utmp ut;
+ char *user, *file, *prog;
+ time_t t;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ switch (argc) {
+ case 0:
+ user = NULL;
+ break;
+ case 1:
+ user = argv[0];
+ break;
+ default:
+ usage();
+ }
+
+ prog = basename(argv0);
+ file = (!strcmp(prog, "last")) ? WTMP_PATH : BTMP_PATH;
+ if ((fp = fopen(file, "r")) == NULL)
+ eprintf("fopen %s:", file);
+
+ while (fread(&ut, sizeof(ut), 1, fp) == 1) {
+ if (ut.ut_type != USER_PROCESS ||
+ (user && strcmp(user, ut.ut_name))) {
+ continue;
+ }
+
+ t = ut.ut_time;
+ printf("%-8.8s %-8.8s %-16.16s %s",
+ ut.ut_user, ut.ut_line, ut.ut_host, ctime(&t));
+ }
+ if (fclose(fp))
+ eprintf("fclose %s:", file);
+ return 0;
+}
diff --git a/ubase/lastlog.8 b/ubase/lastlog.8
@@ -0,0 +1,11 @@
+.TH LASTLOG 8 ubase-VERSION
+.SH NAME
+\fBlastlog\fR - Show last login of users
+.SH SYPNOSIS
+\fBlastlog\fI [user ...]
+.SH DESCRIPTION
+\fBlastlog\fR Show time, tty, and host (if it was a remote
+connection) of last login of users. If some user names are passed
+as parameter then information about last login of these users is
+shown, otherwise is shown for all the users in /etc/passwd in the
+order they appear in it.
diff --git a/ubase/lastlog.c b/ubase/lastlog.c
@@ -0,0 +1,78 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <utmp.h>
+
+#include "text.h"
+#include "util.h"
+
+#define PASSWD "/etc/passwd"
+
+static FILE *last;
+
+static void
+lastlog(char *user)
+{
+ struct passwd *pwd;
+ struct lastlog ll;
+ time_t lltime;
+
+ errno = 0;
+ if ((pwd = getpwnam(user)) == NULL) {
+ if (errno)
+ weprintf("getpwnam %s:", user);
+ else
+ weprintf("unknown user: %s\n", user);
+ return;
+ }
+
+ fseek(last, pwd->pw_uid * sizeof(struct lastlog), 0);
+ fread(&ll, sizeof(struct lastlog), 1, last);
+
+ if (ferror(last))
+ eprintf("%s: read error:", _PATH_LASTLOG);
+
+ /* on glibc `ll_time' can be an int32_t with compat32
+ * avoid compiler warning when calling ctime() */
+ lltime = ll.ll_time;
+ printf("%-8.8s %-8.8s %-16.16s %s",
+ user, ll.ll_line, ll.ll_host, ctime(&lltime));
+}
+
+int
+main(int argc, char **argv)
+{
+ FILE *fp;
+ char *line = NULL, *p;
+ size_t sz = 0;
+
+ if ((last = fopen(_PATH_LASTLOG, "r")) == NULL)
+ eprintf("fopen %s:", _PATH_LASTLOG);
+
+ if (argc > 1) {
+ while (*++argv)
+ lastlog(*argv);
+ } else {
+ if ((fp = fopen(PASSWD, "r")) == NULL)
+ eprintf("fopen %s:", PASSWD);
+ while (agetline(&line, &sz, fp) != -1) {
+ if ((p = strchr(line, ':')) == NULL)
+ eprintf("invalid passwd entry\n");
+ *p = '\0';
+ lastlog(line);
+ }
+ if (fclose(fp))
+ eprintf("fclose %s:", PASSWD);
+ free(line);
+ }
+
+ if (fclose(last))
+ eprintf("fclose %s:", _PATH_LASTLOG);
+
+ return 0;
+}
diff --git a/ubase/libutil/agetcwd.c b/ubase/libutil/agetcwd.c
@@ -0,0 +1,18 @@
+/* See LICENSE file for copyright and license details. */
+#include <unistd.h>
+
+#include "../util.h"
+
+char *
+agetcwd(void)
+{
+ char *buf;
+ long size;
+
+ apathmax(&buf, &size);
+ if (!getcwd(buf, size))
+ eprintf("getcwd:");
+
+ return buf;
+}
+
diff --git a/ubase/libutil/agetline.c b/ubase/libutil/agetline.c
@@ -0,0 +1,13 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../text.h"
+#include "../util.h"
+
+ssize_t
+agetline(char **p, size_t *size, FILE *fp)
+{
+ return getline(p, size, fp);
+}
diff --git a/ubase/libutil/apathmax.c b/ubase/libutil/apathmax.c
@@ -0,0 +1,22 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "../util.h"
+
+void
+apathmax(char **p, long *size)
+{
+ errno = 0;
+
+ if ((*size = pathconf("/", _PC_PATH_MAX)) == -1) {
+ if (errno == 0) {
+ *size = BUFSIZ;
+ } else {
+ eprintf("pathconf:");
+ }
+ }
+ *p = emalloc(*size);
+}
diff --git a/ubase/libutil/concat.c b/ubase/libutil/concat.c
@@ -0,0 +1,20 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <unistd.h>
+
+#include "../text.h"
+#include "../util.h"
+
+void
+concat(FILE *fp1, const char *s1, FILE *fp2, const char *s2)
+{
+ char buf[BUFSIZ];
+ ssize_t n;
+
+ while ((n = read(fileno(fp1), buf, sizeof buf)) > 0) {
+ if (write(fileno(fp2), buf, n) != n)
+ eprintf("%s: write error:", s2);
+ }
+ if (n < 0)
+ eprintf("%s: read error:", s1);
+}
diff --git a/ubase/libutil/ealloc.c b/ubase/libutil/ealloc.c
@@ -0,0 +1,47 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdlib.h>
+#include <string.h>
+
+#include "../util.h"
+
+void *
+ecalloc(size_t nmemb, size_t size)
+{
+ void *p;
+
+ p = calloc(nmemb, size);
+ if (!p)
+ eprintf("calloc: out of memory\n");
+ return p;
+}
+
+void *
+emalloc(size_t size)
+{
+ void *p;
+
+ p = malloc(size);
+ if (!p)
+ eprintf("malloc: out of memory\n");
+ return p;
+}
+
+void *
+erealloc(void *p, size_t size)
+{
+ p = realloc(p, size);
+ if (!p)
+ eprintf("realloc: out of memory\n");
+ return p;
+}
+
+char *
+estrdup(const char *s)
+{
+ char *p;
+
+ p = strdup(s);
+ if (!p)
+ eprintf("strdup: out of memory\n");
+ return p;
+}
diff --git a/ubase/libutil/eprintf.c b/ubase/libutil/eprintf.c
@@ -0,0 +1,67 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../util.h"
+
+char *argv0;
+
+static void venprintf(int, const char *, va_list);
+
+void
+eprintf(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ venprintf(1, fmt, ap);
+ va_end(ap);
+}
+
+void
+enprintf(int status, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ venprintf(status, fmt, ap);
+ va_end(ap);
+}
+
+void
+venprintf(int status, const char *fmt, va_list ap)
+{
+#ifdef DEBUG
+ fprintf(stderr, "%s: ", argv0);
+#endif
+
+ vfprintf(stderr, fmt, ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ }
+
+ exit(status);
+}
+
+void
+weprintf(const char *fmt, ...)
+{
+ va_list ap;
+
+#ifdef DEBUG
+ fprintf(stderr, "%s: ", argv0);
+#endif
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ }
+}
diff --git a/ubase/libutil/estrtol.c b/ubase/libutil/estrtol.c
@@ -0,0 +1,27 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../util.h"
+
+long
+estrtol(const char *s, int base)
+{
+ char *end;
+ long n;
+
+ errno = 0;
+ n = strtol(s, &end, base);
+ if (*end != '\0') {
+ if (base == 0)
+ eprintf("%s: not an integer\n", s);
+ else
+ eprintf("%s: not a base %d integer\n", s, base);
+ }
+ if (errno != 0)
+ eprintf("%s:", s);
+
+ return n;
+}
+
diff --git a/ubase/libutil/estrtoul.c b/ubase/libutil/estrtoul.c
@@ -0,0 +1,26 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../util.h"
+
+unsigned long
+estrtoul(const char *s, int base)
+{
+ char *end;
+ unsigned long n;
+
+ errno = 0;
+ n = strtoul(s, &end, base);
+ if (*end != '\0') {
+ if (base == 0)
+ eprintf("%s: not an integer\n", s);
+ else
+ eprintf("%s: not a base %d integer\n", s, base);
+ }
+ if (errno != 0)
+ eprintf("%s:", s);
+
+ return n;
+}
diff --git a/ubase/libutil/explicit_bzero.c b/ubase/libutil/explicit_bzero.c
@@ -0,0 +1,12 @@
+/* See LICENSE file for copyright and license details. */
+#include <string.h>
+
+#include "../util.h"
+
+static void *(*volatile explicit_memset)(void *, int, size_t) = memset;
+
+void
+explicit_bzero(void *b, size_t len)
+{
+ (*explicit_memset)(b, 0, len);
+}
diff --git a/ubase/libutil/passwd.c b/ubase/libutil/passwd.c
@@ -0,0 +1,77 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/resource.h>
+#include <sys/time.h>
+
+#include <errno.h>
+#include <pwd.h>
+#include <shadow.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../passwd.h"
+#include "../text.h"
+#include "../util.h"
+
+/* Returns -1 on error, 0 for incorrect password
+ * and 1 if all went OK */
+int
+pw_check(const struct passwd *pw, const char *pass)
+{
+ char *cryptpass, *p;
+ struct spwd *spw;
+
+ p = pw->pw_passwd;
+ if (p[0] == '!' || p[0] == '*') {
+ weprintf("denied\n");
+ return -1;
+ }
+
+ if (pw->pw_passwd[0] == '\0') {
+ if (pass[0] == '\0')
+ return 1;
+ weprintf("incorrect password\n");
+ return 0;
+ }
+
+ if (pw->pw_passwd[0] == 'x' && pw->pw_passwd[1] == '\0') {
+ errno = 0;
+ spw = getspnam(pw->pw_name);
+ if (!spw) {
+ if (errno)
+ weprintf("getspnam: %s:", pw->pw_name);
+ else
+ weprintf("who are you?\n");
+ return -1;
+ }
+ p = spw->sp_pwdp;
+ if (p[0] == '!' || p[0] == '*') {
+ weprintf("denied\n");
+ return -1;
+ }
+ }
+
+ cryptpass = crypt(pass, p);
+ if (!cryptpass) {
+ weprintf("crypt:");
+ return -1;
+ }
+ if (strcmp(cryptpass, p) != 0) {
+ weprintf("incorrect password\n");
+ return 0;
+ }
+ return 1;
+}
+
+int
+pw_init(void)
+{
+ struct rlimit rlim;
+
+ rlim.rlim_cur = 0;
+ rlim.rlim_max = 0;
+ if (setrlimit(RLIMIT_CORE, &rlim) < 0)
+ eprintf("setrlimit:");
+ return 0;
+}
diff --git a/ubase/libutil/proc.c b/ubase/libutil/proc.c
@@ -0,0 +1,117 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../proc.h"
+#include "../util.h"
+
+int
+parsecmdline(pid_t pid, char *buf, size_t siz)
+{
+ int fd;
+ char path[PATH_MAX];
+ ssize_t n, i;
+
+ snprintf(path, sizeof(path), "/proc/%ld/cmdline", (long)pid);
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return -1;
+ n = read(fd, buf, siz > 0 ? siz - 1 : 0);
+ if (n < 0) {
+ weprintf("read %s:", path);
+ close(fd);
+ return -1;
+ }
+ if (!n) {
+ close(fd);
+ return -1;
+ }
+ buf[n] = '\0';
+ for (i = 0; i < n; i++)
+ if (buf[i] == '\0')
+ buf[i] = ' ';
+ close(fd);
+ return 0;
+}
+
+int
+parsestat(pid_t pid, struct procstat *ps)
+{
+ char path[PATH_MAX];
+ FILE *fp;
+ size_t len;
+
+ snprintf(path, sizeof(path), "/proc/%d/stat", pid);
+ if (!(fp = fopen(path, "r")))
+ return -1;
+ fscanf(fp, "%d %s %c %d %d %d %d %d %u %lu %lu %lu %lu %lu %lu",
+ &ps->pid, ps->comm,
+ &ps->state, &ps->ppid, &ps->pgrp,
+ &ps->sid, &ps->tty_nr, &ps->tpgid, &ps->flags,
+ &ps->minflt, &ps->cminflt, &ps->majflt, &ps->cmajflt,
+ &ps->utime, &ps->stime);
+ fscanf(fp, "%ld %ld %ld %ld %ld %ld %llu %lu %ld %ld",
+ &ps->cutime, &ps->cstime, &ps->priority, &ps->nice,
+ &ps->num_threads, &ps->itrealvalue, &ps->starttime,
+ &ps->vsize, &ps->rss, &ps->rsslim);
+ /* Filter out '(' and ')' from comm */
+ if ((len = strlen(ps->comm)) > 0)
+ len--;
+ ps->comm[len] = '\0';
+ memmove(ps->comm, ps->comm + 1, len);
+ fclose(fp);
+ return 0;
+}
+
+int
+parsestatus(pid_t pid, struct procstatus *pstatus)
+{
+ char path[PATH_MAX];
+ char buf[BUFSIZ], *off;
+ int fd;
+ ssize_t n;
+
+ snprintf(path, sizeof(path), "/proc/%d/status", pid);
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return -1;
+ n = read(fd, buf, sizeof(buf) - 1);
+ if (n < 0)
+ eprintf("%s: read error:", path);
+ if (!n) {
+ close(fd);
+ return -1;
+ }
+ buf[n] = '\0';
+ close(fd);
+ off = strstr(buf, "Uid:");
+ if (!off)
+ return -1;
+ sscanf(off, "Uid: %u %u", &pstatus->uid, &pstatus->euid);
+ off = strstr(buf, "Gid:");
+ if (!off)
+ return -1;
+ sscanf(off, "Gid: %u %u", &pstatus->gid, &pstatus->egid);
+ return 0;
+}
+
+int
+pidfile(const char *file)
+{
+ char *end;
+
+ errno = 0;
+ strtol(file, &end, 10);
+ if (*end != '\0')
+ return 0;
+ if (errno != 0)
+ return 0;
+ return 1;
+}
diff --git a/ubase/libutil/putword.c b/ubase/libutil/putword.c
@@ -0,0 +1,16 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+
+#include "../util.h"
+
+void
+putword(const char *s)
+{
+ static int first = 1;
+
+ if (!first)
+ putchar(' ');
+
+ fputs(s, stdout);
+ first = 0;
+}
diff --git a/ubase/libutil/recurse.c b/ubase/libutil/recurse.c
@@ -0,0 +1,42 @@
+/* See LICENSE file for copyright and license details. */
+#include <dirent.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "../util.h"
+
+void
+recurse(const char *path, void (*fn)(const char *))
+{
+ char buf[PATH_MAX];
+ struct dirent *d;
+ struct stat st;
+ DIR *dp;
+
+ if (lstat(path, &st) == -1 || S_ISDIR(st.st_mode) == 0)
+ return;
+
+ if (!(dp = opendir(path)))
+ eprintf("opendir %s:", path);
+
+ while ((d = readdir(dp))) {
+ if (strcmp(d->d_name, ".") == 0 ||
+ strcmp(d->d_name, "..") == 0)
+ continue;
+ if (strlcpy(buf, path, sizeof(buf)) >= sizeof(buf))
+ eprintf("path too long\n");
+ if (buf[strlen(buf) - 1] != '/')
+ if (strlcat(buf, "/", sizeof(buf)) >= sizeof(buf))
+ eprintf("path too long\n");
+ if (strlcat(buf, d->d_name, sizeof(buf)) >= sizeof(buf))
+ eprintf("path too long\n");
+ fn(buf);
+ }
+
+ closedir(dp);
+}
diff --git a/ubase/libutil/strlcat.c b/ubase/libutil/strlcat.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+#include <sys/types.h>
+
+#include "../util.h"
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+ return(dlen + (s - src)); /* count does not include NUL */
+}
diff --git a/ubase/libutil/strlcpy.c b/ubase/libutil/strlcpy.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+#include <sys/types.h>
+
+#include "../util.h"
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0')
+ break;
+ }
+ }
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+ return(s - src - 1); /* count does not include NUL */
+}
diff --git a/ubase/libutil/tty.c b/ubase/libutil/tty.c
@@ -0,0 +1,39 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../util.h"
+
+void
+devtotty(int dev, int *tty_maj, int *tty_min)
+{
+ *tty_maj = (dev >> 8) & 0xfff;
+ *tty_min = (dev & 0xff) | ((dev >> 12) & 0xfff00);
+}
+
+char *
+ttytostr(int tty_maj, int tty_min)
+{
+ const char *pts = "pts/";
+ const char *tty = "tty";
+ char *ttystr;
+ size_t len;
+
+ /* Up to 10k ttys */
+ len = strlen(pts) + 4 + 1;
+ ttystr = emalloc(len);
+ switch (tty_maj) {
+ case 136:
+ snprintf(ttystr, len, "%s%d", pts, tty_min);
+ break;
+ case 4:
+ snprintf(ttystr, len, "%s%d", tty, tty_min);
+ break;
+ default:
+ ttystr[0] = '?';
+ ttystr[1] = '\0';
+ break;
+ }
+ return ttystr;
+}
diff --git a/ubase/login.1 b/ubase/login.1
@@ -0,0 +1,13 @@
+.TH LOGIN 1 ubase-VERSION
+.SH NAME
+\fBlogin\fR - Log into the system
+.SH SYNOPSIS
+\fBlogin\fR [\fB-p\fR] \fIusername\fR
+.SH DESCRIPTION
+\fBlogin\fR logs the \fIusername\fR into the system. It sets \fBHOME\fR,
+\fBSHELL\fR, \fBUSER\fR, \fBLOGNAME\fR and the \fBPATH\fR environment
+variables and invokes the login shell as specified in \fI/etc/passwd\fR.
+.SH OPTIONS
+.TP
+\fB-p\fR
+Preserve the environment.
diff --git a/ubase/login.c b/ubase/login.c
@@ -0,0 +1,132 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <utmp.h>
+
+#include "config.h"
+#include "passwd.h"
+#include "util.h"
+
+static int dologin(struct passwd *, int);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-p] username\n", argv0);
+}
+
+/* Write utmp entry */
+static void
+writeutmp(const char *user, const char *tty)
+{
+ struct utmp usr;
+ FILE *fp;
+
+ memset(&usr, 0, sizeof(usr));
+
+ usr.ut_type = USER_PROCESS;
+ usr.ut_pid = getpid();
+ strlcpy(usr.ut_user, user, sizeof(usr.ut_user));
+ strlcpy(usr.ut_line, tty, sizeof(usr.ut_line));
+ usr.ut_tv.tv_sec = time(NULL);
+
+ fp = fopen(UTMP_PATH, "a");
+ if (fp) {
+ if (fwrite(&usr, sizeof(usr), 1, fp) != 1)
+ if (ferror(fp))
+ weprintf("%s: write error:", UTMP_PATH);
+ fclose(fp);
+ } else {
+ weprintf("fopen %s:", UTMP_PATH);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct passwd *pw;
+ char *pass, *user;
+ char *tty;
+ uid_t uid;
+ gid_t gid;
+ int pflag = 0;
+
+ ARGBEGIN {
+ case 'p':
+ pflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ if (isatty(0) == 0 || isatty(1) == 0 || isatty(2) == 0)
+ eprintf("no tty");
+
+ user = argv[0];
+ errno = 0;
+ pw = getpwnam(user);
+ if (!pw) {
+ if (errno)
+ eprintf("getpwnam %s:", user);
+ else
+ eprintf("who are you?\n");
+ }
+
+ uid = pw->pw_uid;
+ gid = pw->pw_gid;
+
+ /* Flush pending input */
+ ioctl(0, TCFLSH, (void *)0);
+
+ pass = getpass("Password: ");
+ if (!pass)
+ eprintf("getpass:");
+ if (pw_check(pw, pass) <= 0)
+ exit(1);
+
+ tty = ttyname(0);
+ if (!tty)
+ eprintf("ttyname:");
+
+ writeutmp(user, tty);
+
+ if (initgroups(user, gid) < 0)
+ eprintf("initgroups:");
+ if (setgid(gid) < 0)
+ eprintf("setgid:");
+ if (setuid(uid) < 0)
+ eprintf("setuid:");
+
+ return dologin(pw, pflag);
+}
+
+static int
+dologin(struct passwd *pw, int preserve)
+{
+ char *shell = pw->pw_shell[0] == '\0' ? "/bin/sh" : pw->pw_shell;
+
+ if (preserve == 0)
+ clearenv();
+ setenv("HOME", pw->pw_dir, 1);
+ setenv("SHELL", shell, 1);
+ setenv("USER", pw->pw_name, 1);
+ setenv("LOGNAME", pw->pw_name, 1);
+ setenv("PATH", ENV_PATH, 1);
+ if (chdir(pw->pw_dir) < 0)
+ eprintf("chdir %s:", pw->pw_dir);
+ execlp(shell, shell, "-l", NULL);
+ weprintf("execlp %s:", shell);
+ return (errno == ENOENT) ? 127 : 126;
+}
diff --git a/ubase/lsmod.8 b/ubase/lsmod.8
@@ -0,0 +1,6 @@
+.TH lsmod 8 ubase-VERSION
+.SH NAME
+\fBlsmod\fR - List loaded kernel modules
+.SH DESCRIPTION
+\fBlsmod\fR parses `/proc/modules' and shows the loadable kernel modules that are
+currently loaded.
diff --git a/ubase/lsmod.c b/ubase/lsmod.c
@@ -0,0 +1,67 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "text.h"
+#include "util.h"
+
+static void parse_modline(char *buf, char **name, char **size,
+ char **refcount, char **users);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *modfile = "/proc/modules";
+ FILE *fp;
+ char *buf = NULL;
+ char *name, *size, *refcount, *users;
+ size_t bufsize = 0;
+ size_t len;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc > 0)
+ usage();
+
+ fp = fopen(modfile, "r");
+ if (!fp)
+ eprintf("fopen %s:", modfile);
+
+ printf("%-23s Size Used by\n", "Module");
+
+ while (agetline(&buf, &bufsize, fp) != -1) {
+ parse_modline(buf, &name, &size, &refcount, &users);
+ if (!name || !size || !refcount || !users)
+ eprintf("invalid format: %s\n", modfile);
+ len = strlen(users) - 1;
+ if (users[len] == ',' || users[len] == '-')
+ users[len] = '\0';
+ printf("%-20s%8s%3s %s\n", name, size, refcount,
+ users);
+ }
+ if (ferror(fp))
+ eprintf("%s: read error:", modfile);
+ free(buf);
+ fclose(fp);
+ return 0;
+}
+
+static void
+parse_modline(char *buf, char **name, char **size,
+ char **refcount, char **users)
+{
+ *name = strtok(buf, " ");
+ *size = strtok(NULL, " ");
+ *refcount = strtok(NULL, " ");
+ *users = strtok(NULL, " ");
+}
diff --git a/ubase/lsusb.8 b/ubase/lsusb.8
@@ -0,0 +1,6 @@
+.TH LSUSB 8 ubase-VERSION
+.SH NAME
+\fBlsusb\fR - List USB devices
+.SH DESCRIPTION
+\fBlsusb\fR searches in `/sys/bus/usb/devices' for usb buses and connected devices and prints
+them one by one.
diff --git a/ubase/lsusb.c b/ubase/lsusb.c
@@ -0,0 +1,60 @@
+/* See LICENSE file for copyright and license details. */
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "text.h"
+#include "util.h"
+
+static void lsusb(const char *file);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ recurse("/sys/bus/usb/devices", lsusb);
+ return 0;
+}
+
+static void
+lsusb(const char *file)
+{
+ FILE *fp;
+ char path[PATH_MAX];
+ char *buf = NULL;
+ size_t size = 0;
+ unsigned int i = 0, busnum = 0, devnum = 0, pid = 0, vid = 0;
+
+ if (strlcpy(path, file, sizeof(path)) >= sizeof(path))
+ eprintf("path too long\n");
+ if (strlcat(path, "/uevent", sizeof(path)) >= sizeof(path))
+ eprintf("path too long\n");
+
+ if (!(fp = fopen(path, "r")))
+ return;
+ while (agetline(&buf, &size, fp) != -1) {
+ if (sscanf(buf, "BUSNUM=%u\n", &busnum) ||
+ sscanf(buf, "DEVNUM=%u\n", &devnum) ||
+ sscanf(buf, "PRODUCT=%x/%x/", &pid, &vid))
+ i++;
+ if (i == 3) {
+ printf("Bus %03d Device %03d: ID %04x:%04x\n", busnum, devnum,
+ pid, vid);
+ break;
+ }
+ }
+ if (ferror(fp))
+ eprintf("%s: read error:", path);
+ free(buf);
+ fclose(fp);
+}
diff --git a/ubase/mesg.1 b/ubase/mesg.1
@@ -0,0 +1,19 @@
+.TH MESG 1 ubase-VERSION
+.SH NAME
+\fBmesg\fR - Display (do not display) messages from other users
+.SH SYNOPSIS
+\fBmesg\fR [\fBn\fR|\fBy\fR]
+.SH DESCRIPTION
+\fBmesg\fR controls write access others have to the terminal device associated
+with standard error output. If write access is allowed, then programs such as \fBtalk\fR(1)
+and \fBwrite\fR(1) may display messages on the terminal.
+.SH OPTIONS
+.TP
+\fBn\fR
+Disallow messages.
+.TP
+\fBy\fR
+Allow messages.
+.SH SEE ALSO
+.TP
+write(1)
diff --git a/ubase/mesg.c b/ubase/mesg.c
@@ -0,0 +1,53 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [n|y]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ mode_t mode;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc > 1)
+ usage();
+
+ if (isatty(2) == 0)
+ eprintf("stderr: not a tty\n");
+
+ if (fstat(2, &sb) < 0)
+ eprintf("fstat stderr:");
+
+ if (argc == 0) {
+ puts(sb.st_mode & (S_IWGRP | S_IWOTH) ? "is y" : "is n");
+ return 0;
+ }
+
+ if (argv[0][0] == 'y' && argv[0][1] == '\0')
+ mode = sb.st_mode | S_IWGRP | S_IWOTH;
+ else if (argv[0][0] == 'n' && argv[0][1] == '\0')
+ mode = sb.st_mode & ~(S_IWGRP | S_IWOTH);
+ else
+ usage();
+
+ if (fchmod(2, mode) < 0)
+ eprintf("fchmod stderr:");
+
+ return 0;
+}
diff --git a/ubase/mknod.1 b/ubase/mknod.1
@@ -0,0 +1,23 @@
+.TH MKNOD 1 ubase-VERSION
+.SH NAME
+\fBmknod\fR - Create a special device file
+.SH SYNOPSIS
+\fBmknod\fR [\fB-m \fImode\fR] \fIname type major minor
+.SH DESCRIPTION
+\fBmknod\fR reates a special device file named \fIname\fR
+with major number \fImajor\fR, and minor number \fIminor\fR.
+\fItype\fR specifies what kind of special file will be created
+and must be one of:
+.TP
+\fBu|c\fR
+A character device.
+.TP
+\fBb\fR
+A block device.
+.SH OPTIONS
+.TP
+\fB-m\fR
+Set the mode of the new file based on the octal value of
+\fImode\fR.
+.SH SEE ALSO
+mknod (2)
diff --git a/ubase/mknod.c b/ubase/mknod.c
@@ -0,0 +1,45 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: mknod [-m mode] name type major minor\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ mode_t type, mode = 0644;
+ dev_t dev;
+
+ ARGBEGIN {
+ case 'm':
+ mode = estrtol(EARGF(usage()), 8);
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc != 4)
+ usage();
+
+ if (strlen(argv[1]) != 1 || !strchr("ucb", argv[1][0]))
+ eprintf("mknod: '%s': invalid type\n", argv[1]);
+ type = (argv[1][0] == 'b') ? S_IFBLK : S_IFCHR;
+
+ dev = makedev(estrtol(argv[2], 0), estrtol(argv[3], 0));
+
+ if (mknod(argv[0], type|mode, dev) == -1)
+ eprintf("mknod: '%s':", argv[0]);
+ return 0;
+}
diff --git a/ubase/mkswap.8 b/ubase/mkswap.8
@@ -0,0 +1,12 @@
+.TH MKSWAP 8 ubase-VERSION
+.SH NAME
+\fBmkswap\fR - Set up a Linux swap area
+.SH SYNOPSIS
+\fBmkswap\fR \fIdevice\fR
+.SH DESCRIPTION
+\fBmkswap\fR sets up a Linux swap area on a device or in a file. The
+\fIdevice\fR argument will usually be a disk-partition but it can also be a
+file. After creating the swap area you will typically need to use the
+\fBswapon\fR command to start using it.
+.SH SEE ALSO
+swapon(8)
diff --git a/ubase/mkswap.c b/ubase/mkswap.c
@@ -0,0 +1,89 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+#define SWAP_UUID_LENGTH 16
+#define SWAP_LABEL_LENGTH 16
+#define SWAP_MIN_PAGES 10
+
+struct swap_hdr {
+ char bootbits[1024];
+ unsigned int version;
+ unsigned int last_page;
+ unsigned int nr_badpages;
+ unsigned char uuid[SWAP_UUID_LENGTH];
+ char volume_name[SWAP_LABEL_LENGTH];
+ unsigned int padding[117];
+ unsigned int badpages[1];
+};
+
+static void
+usage(void)
+{
+ eprintf("usage: %s device\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ unsigned int pages;
+ long pagesize;
+ struct stat sb;
+ char *buf;
+ struct swap_hdr *hdr;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ pagesize = sysconf(_SC_PAGESIZE);
+ if (pagesize <= 0) {
+ pagesize = sysconf(_SC_PAGE_SIZE);
+ if (pagesize <= 0)
+ eprintf("can't determine pagesize\n");
+ }
+
+ fd = open(argv[0], O_RDWR);
+ if (fd < 0)
+ eprintf("open %s:", argv[0]);
+ if (fstat(fd, &sb) < 0)
+ eprintf("stat %s:", argv[0]);
+
+ buf = ecalloc(1, pagesize);
+
+ pages = sb.st_size / pagesize;
+ if (pages < SWAP_MIN_PAGES)
+ eprintf("swap space needs to be at least %ldKiB\n",
+ SWAP_MIN_PAGES * pagesize / 1024);
+
+ /* Fill up the swap header */
+ hdr = (struct swap_hdr *)buf;
+ hdr->version = 1;
+ hdr->last_page = pages - 1;
+ strncpy(buf + pagesize - 10, "SWAPSPACE2", 10);
+
+ printf("Setting up swapspace version 1, size = %luKiB\n",
+ (pages - 1) * pagesize / 1024);
+
+ /* Write out the signature page */
+ if (write(fd, buf, pagesize) != pagesize)
+ eprintf("unable to write signature page\n");
+
+ fsync(fd);
+ close(fd);
+ free(buf);
+
+ return 0;
+}
diff --git a/ubase/mount.8 b/ubase/mount.8
@@ -0,0 +1,33 @@
+.TH MOUNT 8 ubase-VERSION
+.SH NAME
+\fBmount\fR - Mount a filesystem
+.SH SYNOPSIS
+\fBmount\fR [\fB-BMRan\fR] [\fB-t\fI fstype\fR] [\fB-o\fI options\fR] [\fIsource\fR] [\fItarget\fR]
+.SH DESCRIPTION
+\fBmount\fR attaches the filesystem specified to the filesystem hierarchy. The \fBumount(8)\fR command will detach it again.
+.SH OPTIONS
+.TP
+\fB-B\fR
+Remount a subtree somewhere else (so that its contents are
+visible in both places).
+.TP
+\fB-M\fR
+Move a subtree to some other place.
+.TP
+\fB-R\fR
+Remount a subtree and all possible submounts somewhere else (so
+that its contents are available in both places).
+.TP
+\fB-a\fR
+Mount all filesystems mentioned in /etc/fstab.
+.TP
+\fB-n\fR
+Mount without writing in /etc/mtab. This is the default action.
+.TP
+\fB-t\fR
+Set the filesystem type.
+.TP
+\fB-o\fR
+Specify a comma separated string of filesystem specific options.
+.SH SEE ALSO
+mount(2), umount(2), umount(8), swapon(8)
diff --git a/ubase/mount.c b/ubase/mount.c
@@ -0,0 +1,239 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <limits.h>
+#include <mntent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+struct {
+ const char *opt;
+ const char *notopt;
+ unsigned long v;
+} optnames[] = {
+ { "defaults", NULL, 0 },
+ { "remount", NULL, MS_REMOUNT },
+ { "ro", "rw", MS_RDONLY },
+ { "sync", "async", MS_SYNCHRONOUS },
+ { "dirsync", NULL, MS_DIRSYNC },
+ { "nodev", "dev", MS_NODEV },
+ { "noatime", "atime", MS_NOATIME },
+ { "nodiratime", "diratime", MS_NODIRATIME },
+ { "noexec", "exec", MS_NOEXEC },
+ { "nosuid", "suid", MS_NOSUID },
+ { "mand", "nomand", MS_MANDLOCK },
+ { "relatime", "norelatime", MS_RELATIME },
+ { "bind", NULL, MS_BIND },
+ { NULL, NULL, 0 }
+};
+
+static void
+parseopts(char *popts, unsigned long *flags, char *data, size_t datasiz)
+{
+ unsigned int i, validopt;
+ size_t optlen, dlen = 0;
+ char *name;
+
+ data[0] = '\0';
+ for (name = strtok(popts, ","); name; name = strtok(NULL, ",")) {
+ validopt = 0;
+ for (i = 0; optnames[i].opt; i++) {
+ if (optnames[i].opt && strcmp(name, optnames[i].opt) == 0) {
+ *flags |= optnames[i].v;
+ validopt = 1;
+ break;
+ }
+ if (optnames[i].notopt && strcmp(name, optnames[i].notopt) == 0) {
+ *flags &= ~optnames[i].v;
+ validopt = 1;
+ break;
+ }
+ }
+ if (!validopt) {
+ /* unknown option, pass as data option to mount() */
+ if ((optlen = strlen(name))) {
+ if (dlen + optlen + 2 >= datasiz)
+ return; /* prevent overflow */
+ if (dlen)
+ data[dlen++] = ',';
+ memcpy(&data[dlen], name, optlen);
+ dlen += optlen;
+ data[dlen] = '\0';
+ }
+ }
+ }
+}
+
+static int
+mounted(const char *dir)
+{
+ FILE *fp;
+ struct mntent *me;
+ struct stat st1, st2;
+
+ if (stat(dir, &st1) < 0) {
+ weprintf("stat %s:", dir);
+ return 0;
+ }
+ fp = setmntent("/proc/mounts", "r");
+ if (!fp)
+ eprintf("setmntent %s:", "/proc/mounts");
+ while ((me = getmntent(fp)) != NULL) {
+ if (stat(me->mnt_dir, &st2) < 0) {
+ weprintf("stat %s:", me->mnt_dir);
+ continue;
+ }
+ if (st1.st_dev == st2.st_dev &&
+ st1.st_ino == st2.st_ino)
+ return 1;
+ }
+ endmntent(fp);
+ return 0;
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-BMRan] [-t fstype] [-o options] [source] [target]\n",
+ argv0);
+}
+
+static int
+catfile(FILE *in, FILE *out)
+{
+ char buf[BUFSIZ];
+ size_t bytesread;
+
+ while (!feof(in)) {
+ bytesread = fread(buf, 1, sizeof(buf), in);
+ if (ferror(in))
+ return 0;
+ fwrite(buf, 1, bytesread, out);
+ }
+ return 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int aflag = 0, oflag = 0, status = 0, i;
+ unsigned long flags = 0;
+ char *types = NULL, data[512] = "", *resolvpath = NULL;
+ char *files[] = { "/proc/mounts", "/etc/fstab", NULL };
+ size_t datasiz = sizeof(data);
+ const char *source, *target;
+ struct mntent *me = NULL;
+ FILE *fp;
+
+ ARGBEGIN {
+ case 'B':
+ flags |= MS_BIND;
+ break;
+ case 'M':
+ flags |= MS_MOVE;
+ break;
+ case 'R':
+ flags |= MS_REC;
+ break;
+ case 'a':
+ aflag = 1;
+ break;
+ case 'o':
+ oflag = 1;
+ parseopts(EARGF(usage()), &flags, data, datasiz);
+ break;
+ case 't':
+ types = EARGF(usage());
+ break;
+ case 'n':
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1 && aflag == 0) {
+ if (!(fp = fopen(files[0], "r")))
+ eprintf("fopen %s:", files[0]);
+ if (catfile(fp, stdout) != 1) {
+ weprintf("error while reading %s:", files[0]);
+ status = 1;
+ }
+ fclose(fp);
+ return status;
+ }
+
+ if (aflag == 1)
+ goto mountall;
+
+ source = argv[0];
+ target = argv[1];
+
+ if (!target) {
+ target = argv[0];
+ source = NULL;
+ if (!(resolvpath = realpath(target, NULL)))
+ eprintf("realpath %s:", target);
+ target = resolvpath;
+ }
+
+ for (i = 0; files[i]; i++) {
+ if (!(fp = setmntent(files[i], "r"))) {
+ if (strcmp(files[i], "/proc/mounts") != 0)
+ weprintf("setmntent %s:", files[i]);
+ continue;
+ }
+ while ((me = getmntent(fp))) {
+ if (strcmp(me->mnt_dir, target) == 0 ||
+ strcmp(me->mnt_fsname, target) == 0 ||
+ (source && strcmp(me->mnt_dir, source) == 0) ||
+ (source && strcmp(me->mnt_fsname, source) == 0)) {
+ if (!source) {
+ target = me->mnt_dir;
+ source = me->mnt_fsname;
+ }
+ if (!oflag)
+ parseopts(me->mnt_opts, &flags, data, datasiz);
+ if (!types)
+ types = me->mnt_type;
+ goto mountsingle;
+ }
+ }
+ endmntent(fp);
+ fp = NULL;
+ }
+ if (!source)
+ eprintf("can't find %s in /etc/fstab\n", target);
+
+mountsingle:
+ if (mount(source, target, types, flags, data) < 0) {
+ weprintf("mount: %s:", source);
+ status = 1;
+ }
+ if (fp)
+ endmntent(fp);
+ free(resolvpath);
+ return status;
+
+mountall:
+ if (!(fp = setmntent("/etc/fstab", "r")))
+ eprintf("setmntent %s:", "/etc/fstab");
+ while ((me = getmntent(fp))) {
+ flags = 0;
+ parseopts(me->mnt_opts, &flags, data, datasiz);
+ if (mount(me->mnt_fsname, me->mnt_dir, me->mnt_type, flags, data) < 0) {
+ if (mounted(me->mnt_dir) == 0) {
+ weprintf("mount: %s:", me->mnt_fsname);
+ status = 1;
+ }
+ }
+ }
+ endmntent(fp);
+
+ return status;
+}
diff --git a/ubase/mountpoint.1 b/ubase/mountpoint.1
@@ -0,0 +1,22 @@
+.TH MOUNTPOINT 1 ubase-VERSION
+.SH NAME
+\fBmountpoint\fR - Check if a directory is a mountpoint
+.SH SYNOPSIS
+\fBmountpoint\fR [\fB-dq\fR] \fIdirectory\fR
+.TP
+\fBmountpoint\fR \fB-x\fR \fIdevice\fR
+.SH DESCRIPTION
+\fBmountpoint\fR checks if the \fIdirectory\fR is mentioned in the
+/proc/mounts file.
+.SH OPTIONS
+.TP
+\fB-d\fR
+Print the major/minor device number of the filesystem on stdout.
+.TP
+\fB-q\fR
+Be quiet, don't print anything.
+.TP
+\fB-x\fR
+Print the major/minor device number of the \fIdevice\fR on stdout.
+.SH SEE ALSO
+mount(8)
diff --git a/ubase/mountpoint.c b/ubase/mountpoint.c
@@ -0,0 +1,86 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <mntent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-dqx] target\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int qflag = 0, dflag = 0, xflag = 0;
+ struct mntent *me = NULL;
+ FILE *fp;
+ int ret = 0;
+ struct stat st1, st2;
+
+ ARGBEGIN {
+ case 'q':
+ qflag = 1;
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'x':
+ xflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ if (stat(argv[0], &st1) < 0)
+ eprintf("stat %s:", argv[0]);
+
+ if (xflag) {
+ if (!S_ISBLK(st1.st_mode))
+ eprintf("stat: %s: not a block device\n",
+ argv[0]);
+ printf("%u:%u\n", major(st1.st_rdev),
+ minor(st1.st_rdev));
+ return 0;
+ }
+
+ if (!S_ISDIR(st1.st_mode))
+ eprintf("stat %s: not a directory\n", argv[0]);
+
+ if (dflag) {
+ printf("%u:%u\n", major(st1.st_dev),
+ minor(st1.st_dev));
+ return 0;
+ }
+
+ fp = setmntent("/proc/mounts", "r");
+ if (!fp)
+ eprintf("setmntent %s:", "/proc/mounts");
+ while ((me = getmntent(fp)) != NULL) {
+ if (stat(me->mnt_dir, &st2) < 0)
+ eprintf("stat %s:", me->mnt_dir);
+ if (st1.st_dev == st2.st_dev &&
+ st1.st_ino == st2.st_ino)
+ break;
+ }
+ endmntent(fp);
+
+ if (me == NULL)
+ ret = 1;
+
+ if (!qflag)
+ printf("%s %s a mountpoint\n", argv[0],
+ !ret ? "is" : "is not");
+
+ return ret;
+}
diff --git a/ubase/pagesize.1 b/ubase/pagesize.1
@@ -0,0 +1,8 @@
+.TH PAGESIZE 1 ubase-VERSION
+.SH NAME
+\fBpagesize\fR - Print system page size
+.SH SYNOPSIS
+\fBpagesize\fR
+.SH DESCRIPTION
+\fBpagesize\fR prints the size of a page of memory in bytes. This program is
+useful in constructing portable shell scripts.
diff --git a/ubase/pagesize.c b/ubase/pagesize.c
@@ -0,0 +1,32 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ long pagesz;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ pagesz = sysconf(_SC_PAGESIZE);
+ if (pagesz <= 0) {
+ pagesz = sysconf(_SC_PAGE_SIZE);
+ if (pagesz <= 0)
+ eprintf("can't determine pagesize\n");
+ }
+ printf("%ld\n", pagesz);
+ return 0;
+}
diff --git a/ubase/passwd.1 b/ubase/passwd.1
@@ -0,0 +1,11 @@
+.TH PASSWD 1 ubase-VERSION
+.SH NAME
+\fBpasswd\fR - Change a user's password
+.SH SYNOPSIS
+\fBpasswd\fR [\fIusername\fR]
+.SH DESCRIPTION
+\fBpasswd\fR changes the user's password. The user is prompted
+for their current password. If the current password is correctly typed,
+a new password is requested. The new password must be entered twice to
+avoid typing errors. The superuser is not required to provide a user's
+current password.
diff --git a/ubase/passwd.c b/ubase/passwd.c
@@ -0,0 +1,257 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pwd.h>
+#include <shadow.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "passwd.h"
+#include "text.h"
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [username]\n", argv0);
+}
+
+static FILE *
+spw_get_file(const char *user)
+{
+ FILE *fp = NULL;
+ char file[PATH_MAX];
+ int r;
+
+ r = snprintf(file, sizeof(file), "/etc/tcb/%s/shadow", user);
+ if (r < 0 || (size_t)r >= sizeof(file))
+ eprintf("snprintf:");
+ fp = fopen(file, "r+");
+ if (!fp)
+ fp = fopen("/etc/shadow", "r+");
+ return fp;
+}
+
+static int
+spw_write_file(FILE *fp, const struct spwd *spw, char *pwhash)
+{
+ struct spwd *spwent;
+ int r = -1, w = 0;
+ FILE *tfp = NULL;
+
+ /* write to temporary file. */
+ tfp = tmpfile();
+ if (!tfp) {
+ weprintf("tmpfile:");
+ goto cleanup;
+ }
+ while ((spwent = fgetspent(fp))) {
+ /* update entry on name match */
+ if (strcmp(spwent->sp_namp, spw->sp_namp) == 0) {
+ spwent->sp_pwdp = pwhash;
+ w++;
+ }
+ errno = 0;
+ if (putspent(spwent, tfp) == -1) {
+ weprintf("putspent:");
+ goto cleanup;
+ }
+ }
+ if (!w) {
+ weprintf("shadow: no matching entry to write to\n");
+ goto cleanup;
+ }
+ fflush(tfp);
+
+ if (fseek(fp, 0, SEEK_SET) == -1 || fseek(tfp, 0, SEEK_SET) == -1) {
+ weprintf("fseek:");
+ goto cleanup;
+ }
+
+ /* write temporary file to (tcb) shadow file */
+ concat(tfp, "tmpfile", fp, "shadow");
+ ftruncate(fileno(fp), ftell(tfp));
+
+ r = 0; /* success */
+cleanup:
+ if (tfp)
+ fclose(tfp);
+ return r;
+}
+
+static
+int pw_write_file(FILE *fp, const struct passwd *pw, char *pwhash) {
+ struct passwd *pwent;
+ int r = -1, w = 0;
+ FILE *tfp = NULL;
+
+ /* write to temporary file. */
+ tfp = tmpfile();
+ if (!tfp) {
+ weprintf("tmpfile:");
+ goto cleanup;
+ }
+ while ((pwent = fgetpwent(fp))) {
+ /* update entry on name match */
+ if (strcmp(pwent->pw_name, pw->pw_name) == 0) {
+ pwent->pw_passwd = pwhash;
+ w++;
+ }
+ errno = 0;
+ if (putpwent(pwent, tfp) == -1) {
+ weprintf("putpwent:");
+ goto cleanup;
+ }
+ }
+ if (!w) {
+ weprintf("passwd: no matching entry to write to\n");
+ goto cleanup;
+ }
+ fflush(tfp);
+
+ if (fseek(fp, 0, SEEK_SET) == -1 || fseek(tfp, 0, SEEK_SET) == -1) {
+ weprintf("fseek:");
+ goto cleanup;
+ }
+
+ /* write to passwd file. */
+ concat(tfp, "tmpfile", fp, "passwd");
+ ftruncate(fileno(fp), ftell(tfp));
+
+ r = 0; /* success */
+cleanup:
+ if (tfp)
+ fclose(tfp);
+ return r;
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *cryptpass1 = NULL, *cryptpass2 = NULL, *cryptpass3 = NULL;
+ char *inpass, *p, *salt = PW_CIPHER, *prevhash = NULL;
+ struct passwd *pw;
+ struct spwd *spw = NULL;
+ FILE *fp = NULL;
+ int r = -1, status = 1;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ pw_init();
+ umask(077);
+
+ errno = 0;
+ if (argc == 0)
+ pw = getpwuid(getuid());
+ else
+ pw = getpwnam(argv[0]);
+ if (!pw) {
+ if (errno)
+ eprintf("getpwnam: %s:", argv[0]);
+ else
+ eprintf("who are you?\n");
+ }
+
+ /* is using shadow entry ? */
+ if (pw->pw_passwd[0] == 'x' && pw->pw_passwd[1] == '\0') {
+ errno = 0;
+ spw = getspnam(pw->pw_name);
+ if (!spw) {
+ if (errno)
+ eprintf("getspnam: %s:", pw->pw_name);
+ else
+ eprintf("who are you?\n");
+ }
+ }
+
+ /* Flush pending input */
+ ioctl(0, TCFLSH, (void *)0);
+
+ if (getuid() == 0) {
+ goto newpass;
+ } else {
+ if (pw->pw_passwd[0] == '!' ||
+ pw->pw_passwd[0] == '*')
+ eprintf("denied\n");
+ if (pw->pw_passwd[0] == '\0') {
+ goto newpass;
+ }
+ if (pw->pw_passwd[0] == 'x')
+ prevhash = salt = spw->sp_pwdp;
+ else
+ prevhash = salt = pw->pw_passwd;
+ }
+
+ printf("Changing password for %s\n", pw->pw_name);
+ inpass = getpass("Old password: ");
+ if (!inpass)
+ eprintf("getpass:");
+ if (inpass[0] == '\0')
+ eprintf("no password supplied\n");
+ p = crypt(inpass, salt);
+ if (!p)
+ eprintf("crypt:");
+ cryptpass1 = estrdup(p);
+ if (strcmp(cryptpass1, prevhash) != 0)
+ eprintf("incorrect password\n");
+
+newpass:
+ inpass = getpass("Enter new password: ");
+ if (!inpass)
+ eprintf("getpass:");
+ if (inpass[0] == '\0')
+ eprintf("no password supplied\n");
+ p = crypt(inpass, salt);
+ if (!p)
+ eprintf("crypt:");
+ cryptpass2 = estrdup(p);
+ if (cryptpass1 && strcmp(cryptpass1, cryptpass2) == 0)
+ eprintf("password left unchanged\n");
+
+ /* Flush pending input */
+ ioctl(0, TCFLSH, (void *)0);
+
+ inpass = getpass("Retype new password: ");
+ if (!inpass)
+ eprintf("getpass:");
+ if (inpass[0] == '\0')
+ eprintf("no password supplied\n");
+ p = crypt(inpass, salt);
+ if (!p)
+ eprintf("crypt:");
+ cryptpass3 = estrdup(p);
+ if (strcmp(cryptpass2, cryptpass3) != 0)
+ eprintf("passwords don't match\n");
+
+ fp = spw_get_file(pw->pw_name);
+ if (fp) {
+ r = spw_write_file(fp, spw, cryptpass3);
+ } else {
+ fp = fopen("/etc/passwd", "r+");
+ if (fp)
+ r = pw_write_file(fp, pw, cryptpass3);
+ else
+ weprintf("fopen:");
+ }
+ if (!r)
+ status = 0;
+
+ if (fp)
+ fclose(fp);
+ free(cryptpass3);
+ free(cryptpass2);
+ free(cryptpass1);
+
+ return status;
+}
diff --git a/ubase/passwd.h b/ubase/passwd.h
@@ -0,0 +1,4 @@
+/* See LICENSE file for copyright and license details. */
+/* passwd.c */
+int pw_check(const struct passwd *, const char *);
+int pw_init(void);
diff --git a/ubase/pidof.1 b/ubase/pidof.1
@@ -0,0 +1,18 @@
+.TH PIDOF 1 ubase-VERSION
+.SH NAME
+\fBpidof\fR - Find the process ID of a running program
+.SH SYNOPSIS
+\fBpidof\fR [\fB-o\fI pid1,pid2,...,pidN\fR] [\fB-s\fR] [\fIprogram...\fR]
+.SH DESCRIPTION
+\fBpidof\fR finds the process id's of the named programs and prints them on
+stdout.
+.SH OPTIONS
+.TP
+\fB-o\fR
+Tell pidof to omit processes with that process id. The special pid
+%PPID can be used to name the parent process of the pidof program.
+.TP
+\fB-s\fR
+Single shot - this instructs the program to only return one process id.
+.SH SEE ALSO
+killall5(8)
diff --git a/ubase/pidof.c b/ubase/pidof.c
@@ -0,0 +1,120 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/types.h>
+
+#include <dirent.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "proc.h"
+#include "queue.h"
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-o pid1,pid2,...pidN] [-s] [program...]\n", argv0);
+}
+
+struct pidentry {
+ pid_t pid;
+ TAILQ_ENTRY(pidentry) entry;
+};
+
+static TAILQ_HEAD(omitpid_head, pidentry) omitpid_head;
+
+int
+main(int argc, char *argv[])
+{
+ DIR *dp;
+ struct dirent *entry;
+ pid_t pid;
+ struct procstat ps;
+ char cmdline[BUFSIZ], *cmd, *cmdbase = NULL, *p, *arg = NULL;
+ int i, found = 0;
+ int sflag = 0, oflag = 0;
+ struct pidentry *pe, *tmp;
+
+ ARGBEGIN {
+ case 's':
+ sflag = 1;
+ break;
+ case 'o':
+ oflag = 1;
+ arg = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (!argc)
+ return 1;
+
+ TAILQ_INIT(&omitpid_head);
+
+ for (p = strtok(arg, ","); p; p = strtok(NULL, ",")) {
+ pe = emalloc(sizeof(*pe));
+ if (strcmp(p, "%PPID") == 0)
+ pe->pid = getppid();
+ else
+ pe->pid = estrtol(p, 10);
+ TAILQ_INSERT_TAIL(&omitpid_head, pe, entry);
+ }
+
+ if (!(dp = opendir("/proc")))
+ eprintf("opendir /proc:");
+
+ while ((entry = readdir(dp))) {
+ if (!pidfile(entry->d_name))
+ continue;
+ pid = estrtol(entry->d_name, 10);
+ if (oflag) {
+ TAILQ_FOREACH(pe, &omitpid_head, entry)
+ if (pe->pid == pid)
+ break;
+ if (pe)
+ continue;
+ }
+ if (parsestat(pid, &ps) < 0)
+ continue;
+ if (parsecmdline(ps.pid, cmdline,
+ sizeof(cmdline)) < 0) {
+ cmd = ps.comm;
+ cmdbase = cmd;
+ } else {
+ if ((p = strchr(cmdline, ' ')))
+ *p = '\0';
+ cmd = cmdline;
+ cmdbase = basename(cmdline);
+ }
+ /* Workaround for login shells */
+ if (cmd[0] == '-')
+ cmd++;
+ for (i = 0; i < argc; i++) {
+ if (strcmp(cmd, argv[i]) == 0 ||
+ strcmp(cmdbase, argv[i]) == 0) {
+ putword(entry->d_name);
+ found++;
+ if (sflag)
+ goto out;
+ }
+ }
+ }
+
+out:
+ if (found)
+ putchar('\n');
+
+ closedir(dp);
+
+ for (pe = TAILQ_FIRST(&omitpid_head); pe; pe = tmp) {
+ tmp = TAILQ_NEXT(pe, entry);
+ TAILQ_REMOVE(&omitpid_head, pe, entry);
+ free(pe);
+ }
+
+ return 0;
+}
diff --git a/ubase/pivot_root.8 b/ubase/pivot_root.8
@@ -0,0 +1,8 @@
+.TH PIVOT_ROOT 8 ubase-VERSION
+.SH NAME
+\fBpivot_root\fR - change the root filesystem
+.SH SYNOPSIS
+\fBpivot_root\fI new_root put_old\fR
+.SH DESCRIPTION
+\fBpivot_root\fR moves the root file system of the current process to the
+directory \fIput_old\fR and makes \fInew_root\fR the new root file system.
diff --git a/ubase/pivot_root.c b/ubase/pivot_root.c
@@ -0,0 +1,31 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/syscall.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s new-root put-old\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 2)
+ usage();
+
+ if (syscall(SYS_pivot_root, argv[0], argv[1]) < 0)
+ eprintf("pivot_root:");
+
+ return 0;
+}
diff --git a/ubase/proc.h b/ubase/proc.h
@@ -0,0 +1,42 @@
+/* See LICENSE file for copyright and license details. */
+struct procstat {
+ int pid;
+ char comm[PATH_MAX + 2]; /* + 2 for '(' and ')' */
+ unsigned char state;
+ int ppid;
+ int pgrp;
+ int sid;
+ int tty_nr;
+ int tpgid;
+ unsigned flags;
+ unsigned long minflt;
+ unsigned long cminflt;
+ unsigned long majflt;
+ unsigned long cmajflt;
+ unsigned long utime;
+ unsigned long stime;
+ long cutime;
+ long cstime;
+ long priority;
+ long nice;
+ long num_threads;
+ long itrealvalue;
+ unsigned long long starttime;
+ unsigned long vsize;
+ long rss;
+ long rsslim;
+};
+
+struct procstatus {
+ uid_t uid;
+ uid_t euid;
+ gid_t gid;
+ gid_t egid;
+};
+
+int parsecmdline(pid_t pid, char *buf, size_t siz);
+int parsestat(pid_t pid, struct procstat *ps);
+int parsestatus(pid_t pid, struct procstatus *pstatus);
+int proceuid(pid_t pid, uid_t *euid);
+int procuid(pid_t pid, uid_t *euid);
+int pidfile(const char *file);
diff --git a/ubase/ps.1 b/ubase/ps.1
@@ -0,0 +1,26 @@
+.TH PS 1 ubase-VERSION
+.SH NAME
+\fBps\fR - Display process status
+.SH SYNOPSIS
+\fBps\fR [\fB-aAdef\fR]
+.SH DESCRIPTION
+\fBps\fR displays information about active processes. When given no options,
+\fBps\fR prints information about processes of the current user that has a
+controlling terminal.
+.SH OPTIONS
+.TP
+\fB-a\fR
+Select all processes except both session leaders and processes not
+associated with a terminal.
+.TP
+\fB-A\fR
+Select all processes. Identical to \fB-e\fR.
+.TP
+\fB-d\fR
+Select all processes except session leaders.
+.TP
+\fB-e\fR
+Select all processes. Identical to \fB-A\fR.
+.TP
+\fB-f\fR
+Do full-format listing.
diff --git a/ubase/ps.c b/ubase/ps.c
@@ -0,0 +1,188 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/ioctl.h>
+#include <sys/sysinfo.h>
+
+#include <errno.h>
+#include <libgen.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "proc.h"
+#include "util.h"
+
+static void psout(struct procstat *ps);
+static void psr(const char *file);
+
+enum {
+ PS_aflag = 1 << 0,
+ PS_Aflag = 1 << 1,
+ PS_dflag = 1 << 2,
+ PS_fflag = 1 << 3
+};
+
+static void
+usage(void)
+{
+ eprintf("usage: [-aAdef] %s\n", argv0);
+}
+
+static int flags;
+
+int
+main(int argc, char *argv[])
+{
+ ARGBEGIN {
+ case 'a':
+ flags |= PS_aflag;
+ break;
+ case 'A':
+ flags |= PS_Aflag;
+ break;
+ case 'd':
+ flags |= PS_dflag;
+ break;
+ case 'e':
+ flags |= PS_Aflag;
+ break;
+ case 'f':
+ flags |= PS_fflag;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (!(flags & PS_fflag))
+ printf(" PID TTY TIME CMD\n");
+ else
+ printf("UID PID PPID C STIME TTY TIME CMD\n");
+ recurse("/proc", psr);
+ return 0;
+}
+
+static void
+psout(struct procstat *ps)
+{
+ struct procstatus pstatus;
+ char cmdline[BUFSIZ], *cmd;
+ char buf[BUFSIZ];
+ char *ttystr, *myttystr;
+ int tty_maj, tty_min;
+ uid_t myeuid;
+ unsigned sutime;
+ time_t start;
+ char stimestr[sizeof("%H:%M")];
+ struct sysinfo info;
+ struct passwd *pw;
+ struct winsize w;
+
+ /* Ignore session leaders */
+ if (flags & PS_dflag)
+ if (ps->pid == ps->sid)
+ return;
+
+ devtotty(ps->tty_nr, &tty_maj, &tty_min);
+ ttystr = ttytostr(tty_maj, tty_min);
+
+ /* Only print processes that are associated with
+ * a terminal and they are not session leaders */
+ if (flags & PS_aflag) {
+ if (ps->pid == ps->sid || ttystr[0] == '?') {
+ free(ttystr);
+ return;
+ }
+ }
+
+ if (parsestatus(ps->pid, &pstatus) < 0)
+ return;
+
+ /* This is the default case, only print processes that have
+ * the same controlling terminal as the invoker and the same
+ * euid as the current user */
+ if (!(flags & (PS_aflag | PS_Aflag | PS_dflag))) {
+ myttystr = ttyname(0);
+ if (myttystr) {
+ if (strcmp(myttystr + strlen("/dev/"), ttystr)) {
+ free(ttystr);
+ return;
+ }
+ } else {
+ /* The invoker has no controlling terminal - just
+ * go ahead and print the processes anyway */
+ ttystr[0] = '?';
+ ttystr[1] = '\0';
+ }
+ myeuid = geteuid();
+ if (myeuid != pstatus.euid) {
+ free(ttystr);
+ return;
+ }
+ }
+
+ sutime = (ps->stime + ps->utime) / sysconf(_SC_CLK_TCK);
+
+ ioctl(1, TIOCGWINSZ, &w);
+ if (!(flags & PS_fflag)) {
+ snprintf(buf, sizeof(buf), "%5d %-6s %02u:%02u:%02u %s", ps->pid, ttystr,
+ sutime / 3600, (sutime % 3600) / 60, sutime % 60,
+ ps->comm);
+ if (w.ws_col)
+ printf("%.*s\n", w.ws_col, buf);
+ else
+ printf("%s\n", buf);
+ } else {
+ errno = 0;
+ pw = getpwuid(pstatus.uid);
+ if (!pw)
+ eprintf("getpwuid %d:", pstatus.uid);
+
+ if (sysinfo(&info) < 0)
+ eprintf("sysinfo:");
+
+ start = time(NULL) - info.uptime;
+ start += (ps->starttime / sysconf(_SC_CLK_TCK));
+ strftime(stimestr, sizeof(stimestr),
+ "%H:%M", localtime(&start));
+
+ /* For kthreads/zombies /proc/<pid>/cmdline will be
+ * empty so use ps->comm in that case */
+ if (parsecmdline(ps->pid, cmdline, sizeof(cmdline)) < 0)
+ cmd = ps->comm;
+ else
+ cmd = cmdline;
+
+ snprintf(buf, sizeof(buf), "%-8s %5d %5d ? %5s %-5s %02u:%02u:%02u %s%s%s",
+ pw->pw_name, ps->pid,
+ ps->ppid, stimestr, ttystr,
+ sutime / 3600, (sutime % 3600) / 60, sutime % 60,
+ (cmd == ps->comm) ? "[" : "", cmd,
+ (cmd == ps->comm) ? "]" : "");
+ if (w.ws_col)
+ printf("%.*s\n", w.ws_col, buf);
+ else
+ printf("%s\n", buf);
+ }
+ free(ttystr);
+}
+
+static void
+psr(const char *file)
+{
+ char path[PATH_MAX], *p;
+ struct procstat ps;
+ pid_t pid;
+
+ if (strlcpy(path, file, sizeof(path)) >= sizeof(path))
+ eprintf("path too long\n");
+ p = basename(path);
+ if (pidfile(p) == 0)
+ return;
+ pid = estrtol(p, 10);
+ if (parsestat(pid, &ps) < 0)
+ return;
+ psout(&ps);
+}
diff --git a/ubase/queue.h b/ubase/queue.h
@@ -0,0 +1,648 @@
+/* $OpenBSD: queue.h,v 1.38 2013/07/03 15:05:21 fgsch Exp $ */
+/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */
+
+/*
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)queue.h 8.5 (Berkeley) 8/20/94
+ */
+
+#ifndef _SYS_QUEUE_H_
+#define _SYS_QUEUE_H_
+
+/*
+ * This file defines five types of data structures: singly-linked lists,
+ * lists, simple queues, tail queues, and circular queues.
+ *
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction. Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A simple queue is headed by a pair of pointers, one the head of the
+ * list and the other to the tail of the list. The elements are singly
+ * linked to save space, so elements can only be removed from the
+ * head of the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the
+ * list. A simple queue may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * A circle queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the list.
+ * A circle queue may be traversed in either direction, but has a more
+ * complex end of list detection.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC))
+#define _Q_INVALIDATE(a) (a) = ((void *)-1)
+#else
+#define _Q_INVALIDATE(a)
+#endif
+
+/*
+ * Singly-linked List definitions.
+ */
+#define SLIST_HEAD(name, type) \
+struct name { \
+ struct type *slh_first; /* first element */ \
+}
+
+#define SLIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define SLIST_ENTRY(type) \
+struct { \
+ struct type *sle_next; /* next element */ \
+}
+
+/*
+ * Singly-linked List access methods.
+ */
+#define SLIST_FIRST(head) ((head)->slh_first)
+#define SLIST_END(head) NULL
+#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head))
+#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
+
+#define SLIST_FOREACH(var, head, field) \
+ for((var) = SLIST_FIRST(head); \
+ (var) != SLIST_END(head); \
+ (var) = SLIST_NEXT(var, field))
+
+#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SLIST_FIRST(head); \
+ (var) && ((tvar) = SLIST_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Singly-linked List functions.
+ */
+#define SLIST_INIT(head) { \
+ SLIST_FIRST(head) = SLIST_END(head); \
+}
+
+#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
+ (elm)->field.sle_next = (slistelm)->field.sle_next; \
+ (slistelm)->field.sle_next = (elm); \
+} while (0)
+
+#define SLIST_INSERT_HEAD(head, elm, field) do { \
+ (elm)->field.sle_next = (head)->slh_first; \
+ (head)->slh_first = (elm); \
+} while (0)
+
+#define SLIST_REMOVE_AFTER(elm, field) do { \
+ (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \
+} while (0)
+
+#define SLIST_REMOVE_HEAD(head, field) do { \
+ (head)->slh_first = (head)->slh_first->field.sle_next; \
+} while (0)
+
+#define SLIST_REMOVE(head, elm, type, field) do { \
+ if ((head)->slh_first == (elm)) { \
+ SLIST_REMOVE_HEAD((head), field); \
+ } else { \
+ struct type *curelm = (head)->slh_first; \
+ \
+ while (curelm->field.sle_next != (elm)) \
+ curelm = curelm->field.sle_next; \
+ curelm->field.sle_next = \
+ curelm->field.sle_next->field.sle_next; \
+ _Q_INVALIDATE((elm)->field.sle_next); \
+ } \
+} while (0)
+
+/*
+ * List definitions.
+ */
+#define LIST_HEAD(name, type) \
+struct name { \
+ struct type *lh_first; /* first element */ \
+}
+
+#define LIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define LIST_ENTRY(type) \
+struct { \
+ struct type *le_next; /* next element */ \
+ struct type **le_prev; /* address of previous next element */ \
+}
+
+/*
+ * List access methods
+ */
+#define LIST_FIRST(head) ((head)->lh_first)
+#define LIST_END(head) NULL
+#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head))
+#define LIST_NEXT(elm, field) ((elm)->field.le_next)
+
+#define LIST_FOREACH(var, head, field) \
+ for((var) = LIST_FIRST(head); \
+ (var)!= LIST_END(head); \
+ (var) = LIST_NEXT(var, field))
+
+#define LIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = LIST_FIRST(head); \
+ (var) && ((tvar) = LIST_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * List functions.
+ */
+#define LIST_INIT(head) do { \
+ LIST_FIRST(head) = LIST_END(head); \
+} while (0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do { \
+ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \
+ (listelm)->field.le_next->field.le_prev = \
+ &(elm)->field.le_next; \
+ (listelm)->field.le_next = (elm); \
+ (elm)->field.le_prev = &(listelm)->field.le_next; \
+} while (0)
+
+#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
+ (elm)->field.le_prev = (listelm)->field.le_prev; \
+ (elm)->field.le_next = (listelm); \
+ *(listelm)->field.le_prev = (elm); \
+ (listelm)->field.le_prev = &(elm)->field.le_next; \
+} while (0)
+
+#define LIST_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.le_next = (head)->lh_first) != NULL) \
+ (head)->lh_first->field.le_prev = &(elm)->field.le_next;\
+ (head)->lh_first = (elm); \
+ (elm)->field.le_prev = &(head)->lh_first; \
+} while (0)
+
+#define LIST_REMOVE(elm, field) do { \
+ if ((elm)->field.le_next != NULL) \
+ (elm)->field.le_next->field.le_prev = \
+ (elm)->field.le_prev; \
+ *(elm)->field.le_prev = (elm)->field.le_next; \
+ _Q_INVALIDATE((elm)->field.le_prev); \
+ _Q_INVALIDATE((elm)->field.le_next); \
+} while (0)
+
+#define LIST_REPLACE(elm, elm2, field) do { \
+ if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \
+ (elm2)->field.le_next->field.le_prev = \
+ &(elm2)->field.le_next; \
+ (elm2)->field.le_prev = (elm)->field.le_prev; \
+ *(elm2)->field.le_prev = (elm2); \
+ _Q_INVALIDATE((elm)->field.le_prev); \
+ _Q_INVALIDATE((elm)->field.le_next); \
+} while (0)
+
+/*
+ * Simple queue definitions.
+ */
+#define SIMPLEQ_HEAD(name, type) \
+struct name { \
+ struct type *sqh_first; /* first element */ \
+ struct type **sqh_last; /* addr of last next element */ \
+}
+
+#define SIMPLEQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).sqh_first }
+
+#define SIMPLEQ_ENTRY(type) \
+struct { \
+ struct type *sqe_next; /* next element */ \
+}
+
+/*
+ * Simple queue access methods.
+ */
+#define SIMPLEQ_FIRST(head) ((head)->sqh_first)
+#define SIMPLEQ_END(head) NULL
+#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
+#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next)
+
+#define SIMPLEQ_FOREACH(var, head, field) \
+ for((var) = SIMPLEQ_FIRST(head); \
+ (var) != SIMPLEQ_END(head); \
+ (var) = SIMPLEQ_NEXT(var, field))
+
+#define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SIMPLEQ_FIRST(head); \
+ (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Simple queue functions.
+ */
+#define SIMPLEQ_INIT(head) do { \
+ (head)->sqh_first = NULL; \
+ (head)->sqh_last = &(head)->sqh_first; \
+} while (0)
+
+#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+ (head)->sqh_first = (elm); \
+} while (0)
+
+#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.sqe_next = NULL; \
+ *(head)->sqh_last = (elm); \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+} while (0)
+
+#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+ (listelm)->field.sqe_next = (elm); \
+} while (0)
+
+#define SIMPLEQ_REMOVE_HEAD(head, field) do { \
+ if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
+ (head)->sqh_last = &(head)->sqh_first; \
+} while (0)
+
+#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \
+ if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \
+ == NULL) \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+} while (0)
+
+/*
+ * XOR Simple queue definitions.
+ */
+#define XSIMPLEQ_HEAD(name, type) \
+struct name { \
+ struct type *sqx_first; /* first element */ \
+ struct type **sqx_last; /* addr of last next element */ \
+ unsigned long sqx_cookie; \
+}
+
+#define XSIMPLEQ_ENTRY(type) \
+struct { \
+ struct type *sqx_next; /* next element */ \
+}
+
+/*
+ * XOR Simple queue access methods.
+ */
+#define XSIMPLEQ_XOR(head, ptr) ((__typeof(ptr))((head)->sqx_cookie ^ \
+ (unsigned long)(ptr)))
+#define XSIMPLEQ_FIRST(head) XSIMPLEQ_XOR(head, ((head)->sqx_first))
+#define XSIMPLEQ_END(head) NULL
+#define XSIMPLEQ_EMPTY(head) (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head))
+#define XSIMPLEQ_NEXT(head, elm, field) XSIMPLEQ_XOR(head, ((elm)->field.sqx_next))
+
+
+#define XSIMPLEQ_FOREACH(var, head, field) \
+ for ((var) = XSIMPLEQ_FIRST(head); \
+ (var) != XSIMPLEQ_END(head); \
+ (var) = XSIMPLEQ_NEXT(head, var, field))
+
+#define XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = XSIMPLEQ_FIRST(head); \
+ (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * XOR Simple queue functions.
+ */
+#define XSIMPLEQ_INIT(head) do { \
+ arc4random_buf(&(head)->sqx_cookie, sizeof((head)->sqx_cookie)); \
+ (head)->sqx_first = XSIMPLEQ_XOR(head, NULL); \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \
+} while (0)
+
+#define XSIMPLEQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.sqx_next = (head)->sqx_first) == \
+ XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+ (head)->sqx_first = XSIMPLEQ_XOR(head, (elm)); \
+} while (0)
+
+#define XSIMPLEQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL); \
+ *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = XSIMPLEQ_XOR(head, (elm)); \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+} while (0)
+
+#define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.sqx_next = (listelm)->field.sqx_next) == \
+ XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+ (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm)); \
+} while (0)
+
+#define XSIMPLEQ_REMOVE_HEAD(head, field) do { \
+ if (((head)->sqx_first = XSIMPLEQ_XOR(head, \
+ (head)->sqx_first)->field.sqx_next) == XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \
+} while (0)
+
+#define XSIMPLEQ_REMOVE_AFTER(head, elm, field) do { \
+ if (((elm)->field.sqx_next = XSIMPLEQ_XOR(head, \
+ (elm)->field.sqx_next)->field.sqx_next) \
+ == XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = \
+ XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+} while (0)
+
+
+/*
+ * Tail queue definitions.
+ */
+#define TAILQ_HEAD(name, type) \
+struct name { \
+ struct type *tqh_first; /* first element */ \
+ struct type **tqh_last; /* addr of last next element */ \
+}
+
+#define TAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).tqh_first }
+
+#define TAILQ_ENTRY(type) \
+struct { \
+ struct type *tqe_next; /* next element */ \
+ struct type **tqe_prev; /* address of previous next element */ \
+}
+
+/*
+ * tail queue access methods
+ */
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+#define TAILQ_END(head) NULL
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+#define TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+/* XXX */
+#define TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+#define TAILQ_EMPTY(head) \
+ (TAILQ_FIRST(head) == TAILQ_END(head))
+
+#define TAILQ_FOREACH(var, head, field) \
+ for((var) = TAILQ_FIRST(head); \
+ (var) != TAILQ_END(head); \
+ (var) = TAILQ_NEXT(var, field))
+
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = TAILQ_FIRST(head); \
+ (var) != TAILQ_END(head) && \
+ ((tvar) = TAILQ_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
+ for((var) = TAILQ_LAST(head, headname); \
+ (var) != TAILQ_END(head); \
+ (var) = TAILQ_PREV(var, headname, field))
+
+#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
+ for ((var) = TAILQ_LAST(head, headname); \
+ (var) != TAILQ_END(head) && \
+ ((tvar) = TAILQ_PREV(var, headname, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Tail queue functions.
+ */
+#define TAILQ_INIT(head) do { \
+ (head)->tqh_first = NULL; \
+ (head)->tqh_last = &(head)->tqh_first; \
+} while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \
+ (head)->tqh_first->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (head)->tqh_first = (elm); \
+ (elm)->field.tqe_prev = &(head)->tqh_first; \
+} while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.tqe_next = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
+ (elm)->field.tqe_next->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (listelm)->field.tqe_next = (elm); \
+ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
+ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
+ (elm)->field.tqe_next = (listelm); \
+ *(listelm)->field.tqe_prev = (elm); \
+ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_REMOVE(head, elm, field) do { \
+ if (((elm)->field.tqe_next) != NULL) \
+ (elm)->field.tqe_next->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \
+ _Q_INVALIDATE((elm)->field.tqe_prev); \
+ _Q_INVALIDATE((elm)->field.tqe_next); \
+} while (0)
+
+#define TAILQ_REPLACE(head, elm, elm2, field) do { \
+ if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \
+ (elm2)->field.tqe_next->field.tqe_prev = \
+ &(elm2)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm2)->field.tqe_next; \
+ (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \
+ *(elm2)->field.tqe_prev = (elm2); \
+ _Q_INVALIDATE((elm)->field.tqe_prev); \
+ _Q_INVALIDATE((elm)->field.tqe_next); \
+} while (0)
+
+/*
+ * Circular queue definitions.
+ */
+#define CIRCLEQ_HEAD(name, type) \
+struct name { \
+ struct type *cqh_first; /* first element */ \
+ struct type *cqh_last; /* last element */ \
+}
+
+#define CIRCLEQ_HEAD_INITIALIZER(head) \
+ { CIRCLEQ_END(&head), CIRCLEQ_END(&head) }
+
+#define CIRCLEQ_ENTRY(type) \
+struct { \
+ struct type *cqe_next; /* next element */ \
+ struct type *cqe_prev; /* previous element */ \
+}
+
+/*
+ * Circular queue access methods
+ */
+#define CIRCLEQ_FIRST(head) ((head)->cqh_first)
+#define CIRCLEQ_LAST(head) ((head)->cqh_last)
+#define CIRCLEQ_END(head) ((void *)(head))
+#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next)
+#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev)
+#define CIRCLEQ_EMPTY(head) \
+ (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head))
+
+#define CIRCLEQ_FOREACH(var, head, field) \
+ for((var) = CIRCLEQ_FIRST(head); \
+ (var) != CIRCLEQ_END(head); \
+ (var) = CIRCLEQ_NEXT(var, field))
+
+#define CIRCLEQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = CIRCLEQ_FIRST(head); \
+ (var) != CIRCLEQ_END(head) && \
+ ((tvar) = CIRCLEQ_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \
+ for((var) = CIRCLEQ_LAST(head); \
+ (var) != CIRCLEQ_END(head); \
+ (var) = CIRCLEQ_PREV(var, field))
+
+#define CIRCLEQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
+ for ((var) = CIRCLEQ_LAST(head, headname); \
+ (var) != CIRCLEQ_END(head) && \
+ ((tvar) = CIRCLEQ_PREV(var, headname, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Circular queue functions.
+ */
+#define CIRCLEQ_INIT(head) do { \
+ (head)->cqh_first = CIRCLEQ_END(head); \
+ (head)->cqh_last = CIRCLEQ_END(head); \
+} while (0)
+
+#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ (elm)->field.cqe_next = (listelm)->field.cqe_next; \
+ (elm)->field.cqe_prev = (listelm); \
+ if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \
+ (head)->cqh_last = (elm); \
+ else \
+ (listelm)->field.cqe_next->field.cqe_prev = (elm); \
+ (listelm)->field.cqe_next = (elm); \
+} while (0)
+
+#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \
+ (elm)->field.cqe_next = (listelm); \
+ (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \
+ if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \
+ (head)->cqh_first = (elm); \
+ else \
+ (listelm)->field.cqe_prev->field.cqe_next = (elm); \
+ (listelm)->field.cqe_prev = (elm); \
+} while (0)
+
+#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \
+ (elm)->field.cqe_next = (head)->cqh_first; \
+ (elm)->field.cqe_prev = CIRCLEQ_END(head); \
+ if ((head)->cqh_last == CIRCLEQ_END(head)) \
+ (head)->cqh_last = (elm); \
+ else \
+ (head)->cqh_first->field.cqe_prev = (elm); \
+ (head)->cqh_first = (elm); \
+} while (0)
+
+#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.cqe_next = CIRCLEQ_END(head); \
+ (elm)->field.cqe_prev = (head)->cqh_last; \
+ if ((head)->cqh_first == CIRCLEQ_END(head)) \
+ (head)->cqh_first = (elm); \
+ else \
+ (head)->cqh_last->field.cqe_next = (elm); \
+ (head)->cqh_last = (elm); \
+} while (0)
+
+#define CIRCLEQ_REMOVE(head, elm, field) do { \
+ if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \
+ (head)->cqh_last = (elm)->field.cqe_prev; \
+ else \
+ (elm)->field.cqe_next->field.cqe_prev = \
+ (elm)->field.cqe_prev; \
+ if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \
+ (head)->cqh_first = (elm)->field.cqe_next; \
+ else \
+ (elm)->field.cqe_prev->field.cqe_next = \
+ (elm)->field.cqe_next; \
+ _Q_INVALIDATE((elm)->field.cqe_prev); \
+ _Q_INVALIDATE((elm)->field.cqe_next); \
+} while (0)
+
+#define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \
+ if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \
+ CIRCLEQ_END(head)) \
+ (head)->cqh_last = (elm2); \
+ else \
+ (elm2)->field.cqe_next->field.cqe_prev = (elm2); \
+ if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \
+ CIRCLEQ_END(head)) \
+ (head)->cqh_first = (elm2); \
+ else \
+ (elm2)->field.cqe_prev->field.cqe_next = (elm2); \
+ _Q_INVALIDATE((elm)->field.cqe_prev); \
+ _Q_INVALIDATE((elm)->field.cqe_next); \
+} while (0)
+
+#endif /* !_SYS_QUEUE_H_ */
diff --git a/ubase/readahead.8 b/ubase/readahead.8
@@ -0,0 +1,11 @@
+.TH READAHEAD 8 ubase-VERSION
+.SH NAME
+\fBreadahead\fR - Preload files into disk cache
+.SH SYNOPSIS
+\fBreadahead\fR \fIfile...\fR
+.SH DESCRIPTION
+\fBreadahead\fR preloads files into the kernel's disk cache. The
+number of pages preloaded depends on the kernel but it is usually around
+2MB.
+.SH SEE ALSO
+readahead(2)
diff --git a/ubase/readahead.c b/ubase/readahead.c
@@ -0,0 +1,38 @@
+/* See LICENSE file for copyright and license details. */
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s file...\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0)
+ usage();
+
+ for (; argc > 0; argc--, argv++) {
+ if (!(fp = fopen(argv[0], "r"))) {
+ weprintf("fopen %s:", argv[0]);
+ continue;
+ }
+ if (readahead(fileno(fp), 0, -1) < 0)
+ weprintf("readahead %s:", argv[0]);
+ fclose(fp);
+ }
+ return 0;
+}
diff --git a/ubase/reboot.h b/ubase/reboot.h
@@ -0,0 +1,32 @@
+/*
+ * Magic values required to use _reboot() system call.
+ */
+
+#define LINUX_REBOOT_MAGIC1 0xfee1dead
+#define LINUX_REBOOT_MAGIC2 672274793
+#define LINUX_REBOOT_MAGIC2A 85072278
+#define LINUX_REBOOT_MAGIC2B 369367448
+#define LINUX_REBOOT_MAGIC2C 537993216
+
+
+/*
+ * Commands accepted by the _reboot() system call.
+ *
+ * RESTART Restart system using default command and mode.
+ * HALT Stop OS and give system control to ROM monitor, if any.
+ * CAD_ON Ctrl-Alt-Del sequence causes RESTART command.
+ * CAD_OFF Ctrl-Alt-Del sequence sends SIGINT to init task.
+ * POWER_OFF Stop OS and remove all power from system, if possible.
+ * RESTART2 Restart system using given command string.
+ * SW_SUSPEND Suspend system using software suspend if compiled in.
+ * KEXEC Restart system using a previously loaded Linux kernel
+ */
+
+#define LINUX_REBOOT_CMD_RESTART 0x01234567
+#define LINUX_REBOOT_CMD_HALT 0xCDEF0123
+#define LINUX_REBOOT_CMD_CAD_ON 0x89ABCDEF
+#define LINUX_REBOOT_CMD_CAD_OFF 0x00000000
+#define LINUX_REBOOT_CMD_POWER_OFF 0x4321FEDC
+#define LINUX_REBOOT_CMD_RESTART2 0xA1B2C3D4
+#define LINUX_REBOOT_CMD_SW_SUSPEND 0xD000FCE2
+#define LINUX_REBOOT_CMD_KEXEC 0x45584543
diff --git a/ubase/respawn.1 b/ubase/respawn.1
@@ -0,0 +1,18 @@
+.TH RESPAWN 1 ubase-VERSION
+.SH NAME
+\fBrespawn\fR - Spawn the given command repeatedly
+.SH SYNOPSIS
+\fBrespawn\fR [\fB-l\fI fifo\fR] [\fB-d\fI N\fR] \fIcmd\fR [\fIargs...\fR]
+.SH DESCRIPTION
+\fBrespawn\fR spawns the given \fIcmd\fR in a new session
+repeatedly.
+.SH OPTIONS
+.TP
+\fB-d\fR
+Set the delay between invocations of \fIcmd\fR. It defaults to 0.
+.TP
+\fB-l\fR
+Listen on the specified \fIfifo\fR for writes. For each write
+spawn a new instance of \fIcmd\fR. This can be used in conjunction
+with a process supervisor to restart a particular program. The \fB-l\fR
+and \fB-d\fR options are incompatible. All writes are discarded.
diff --git a/ubase/respawn.c b/ubase/respawn.c
@@ -0,0 +1,106 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+sigterm(int sig)
+{
+ if (sig == SIGTERM) {
+ kill(0, SIGTERM);
+ _exit(0);
+ }
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: respawn [-l fifo] [-d N] cmd [args...]\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *fifo = NULL;
+ unsigned int delay = 0;
+ pid_t pid;
+ char buf[BUFSIZ];
+ int savederrno;
+ int fd;
+ ssize_t n;
+ fd_set rdfd;
+
+ ARGBEGIN {
+ case 'd':
+ delay = estrtol(EARGF(usage()), 0);
+ break;
+ case 'l':
+ fifo = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ if (fifo && delay > 0)
+ usage();
+
+ setsid();
+
+ signal(SIGTERM, sigterm);
+
+ if (fifo) {
+ /* TODO: we should use O_RDONLY and re-open the fd on EOF */
+ fd = open(fifo, O_RDWR | O_NONBLOCK);
+ if (fd < 0)
+ eprintf("open %s:", fifo);
+ }
+
+ while (1) {
+ if (fifo) {
+ FD_ZERO(&rdfd);
+ FD_SET(fd, &rdfd);
+ n = select(fd + 1, &rdfd, NULL, NULL, NULL);
+ if (n < 0)
+ eprintf("select:");
+ if (n == 0 || FD_ISSET(fd, &rdfd) == 0)
+ continue;
+ while ((n = read(fd, buf, sizeof(buf))) > 0)
+ ;
+ if (n < 0)
+ if (errno != EAGAIN)
+ eprintf("read %s:", fifo);
+ }
+ pid = fork();
+ if (pid < 0)
+ eprintf("fork:");
+ switch (pid) {
+ case 0:
+ execvp(argv[0], argv);
+ savederrno = errno;
+ weprintf("execvp %s:", argv[0]);
+ _exit(savederrno == ENOENT ? 127 : 126);
+ break;
+ default:
+ waitpid(pid, NULL, 0);
+ break;
+ }
+ if (!fifo)
+ sleep(delay);
+ }
+ /* not reachable */
+ return 0;
+}
diff --git a/ubase/rmmod.8 b/ubase/rmmod.8
@@ -0,0 +1,23 @@
+.TH RMMOD 8 ubase-VERSION
+.SH NAME
+\fBrmmod\fR - Remove a module from the Linux kernel
+.SH SYNOPSIS
+\fBrmmod\fR [\fB-fw\fR] \fImodule...\fR
+.SH DESCRIPTION
+\fBrmmod\fR removes one or more modules from the kernel.
+.SH OPTIONS
+.TP
+\fB-f\fR
+This option can be extremely dangerous: it has no effect unless
+CONFIG_MODULE_FORCE_UNLOAD was set when the kernel was compiled.
+With this option, you can remove modules which are being used, or
+which are not designed to be removed, or have been marked as unsafe.
+.TP
+\fB-w\fR
+Normally, \fBrmmod\fR will refuse to unload modules which are in
+use. With this option, \fBrmmod\fR will isolate the module, and
+wait until the module is no longer used. Noone new will be
+able to use the module, but s up to you to make sure the
+current users eventually finish with it.
+.SH SEE ALSO
+insmod(8), lsmod(8)
diff --git a/ubase/rmmod.c b/ubase/rmmod.c
@@ -0,0 +1,50 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/syscall.h>
+
+#include <fcntl.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-fw] module...\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *mod, *p;
+ int i;
+ int flags = O_NONBLOCK;
+
+ ARGBEGIN {
+ case 'f':
+ flags |= O_TRUNC;
+ break;
+ case 'w':
+ flags &= ~O_NONBLOCK;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ for (i = 0; i < argc; i++) {
+ mod = argv[i];
+ p = strrchr(mod, '.');
+ if (p && !strcmp(p, ".ko"))
+ *p = '\0';
+ if (syscall(__NR_delete_module, mod, flags) < 0)
+ eprintf("delete_module:");
+ }
+
+ return 0;
+}
diff --git a/ubase/rtc.h b/ubase/rtc.h
@@ -0,0 +1,20 @@
+/*
+ * The struct used to pass data via the following ioctl. Similar to the
+ * struct tm in <time.h>, but it needs to be here so that the kernel
+ * source is self contained, allowing cross-compiles, etc. etc.
+ */
+
+struct rtc_time {
+ int tm_sec;
+ int tm_min;
+ int tm_hour;
+ int tm_mday;
+ int tm_mon;
+ int tm_year;
+ int tm_wday;
+ int tm_yday;
+ int tm_isdst;
+};
+
+#define RTC_RD_TIME _IOR('p', 0x09, struct rtc_time) /* Read RTC time */
+#define RTC_SET_TIME _IOW('p', 0x0a, struct rtc_time) /* Set RTC time */
diff --git a/ubase/stat.1 b/ubase/stat.1
@@ -0,0 +1,14 @@
+.TH STAT 1 ubase-VERSION
+.SH NAME
+\fBstat\fR - Display file status
+.SH SYNOPSIS
+\fBstat\fR [\fB-L\fR] [\fIfile...\fR]
+.SH DESCRIPTION
+\fBstat\fR displays information about the given files or stdin if no files
+are specified.
+.SH OPTIONS
+.TP
+\fB-L\fR
+Follow links.
+.SH SEE ALSO
+stat (2)
diff --git a/ubase/stat.c b/ubase/stat.c
@@ -0,0 +1,92 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void show_stat(const char *file, struct stat *st);
+static void show_stat_terse(const char *file, struct stat *st);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-L] [-t] [file...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct stat st;
+ int i, ret = 0;
+ int (*fn)(const char *, struct stat *) = lstat;
+ char *fnname = "lstat";
+ void (*showstat)(const char *, struct stat *) = show_stat;
+
+ ARGBEGIN {
+ case 'L':
+ fn = stat;
+ fnname = "stat";
+ break;
+ case 't':
+ showstat = show_stat_terse;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc == 0) {
+ if (fstat(0, &st) < 0)
+ eprintf("stat <stdin>:");
+ show_stat("<stdin>", &st);
+ }
+
+ for (i = 0; i < argc; i++) {
+ if (fn(argv[i], &st) == -1) {
+ weprintf("%s %s:", fnname, argv[i]);
+ ret = 1;
+ continue;
+ }
+ showstat(argv[i], &st);
+ }
+
+ return ret;
+}
+
+static void
+show_stat_terse(const char *file, struct stat *st)
+{
+ printf("%s ", file);
+ printf("%lu %lu ", (unsigned long)st->st_size,
+ (unsigned long)st->st_blocks);
+ printf("%04o %u %u ", st->st_mode & 0777, st->st_uid, st->st_gid);
+ printf("%llx ", (unsigned long long)st->st_dev);
+ printf("%lu %lu ", (unsigned long)st->st_ino, (unsigned long)st->st_nlink);
+ printf("%d %d ", major(st->st_rdev), minor(st->st_rdev));
+ printf("%ld %ld %ld ", st->st_atime, st->st_mtime, st->st_ctime);
+ printf("%lu\n", (unsigned long)st->st_blksize);
+}
+
+static void
+show_stat(const char *file, struct stat *st)
+{
+ char buf[100];
+
+ printf(" File: ‘%s’\n", file);
+ printf(" Size: %lu\tBlocks: %lu\tIO Block: %lu\n", (unsigned long)st->st_size,
+ (unsigned long)st->st_blocks, (unsigned long)st->st_blksize);
+ printf("Device: %xh/%ud\tInode: %lu\tLinks %lu\n", major(st->st_dev),
+ minor(st->st_dev), (unsigned long)st->st_ino, (unsigned long)st->st_nlink);
+ printf("Access: %04o\tUid: %u\tGid: %u\n", st->st_mode & 0777, st->st_uid, st->st_gid);
+ strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&st->st_atime));
+ printf("Access: %s\n", buf);
+ strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&st->st_mtime));
+ printf("Modify: %s\n", buf);
+ strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&st->st_ctime));
+ printf("Change: %s\n", buf);
+}
diff --git a/ubase/su.1 b/ubase/su.1
@@ -0,0 +1,20 @@
+.TH SU 1 ubase-VERSION
+.SH NAME
+\fBsu\fR - Run a command with a substitute user and group ID
+.SH SYNOPSIS
+\fBsu\fR [\fB-lp\fR] [\fIusername\fR]
+.SH DESCRIPTION
+\fBsu\fR allows to run commands with a substitute user and group ID.
+When called without arguments, su defaults to running an interactive shell
+as root. For backward compatibility su defaults to not change the current
+directory and to only set the environment variables \fBHOME\fR and \fBSHELL\fR
+(plus \fBUSER\fR and \fBLOGNAME\fR if the target \fIusername\fR is not root).
+.SH OPTIONS
+.TP
+\fB-l\fR
+Starts the shell as login shell with an environment similar to a real
+login.
+.TP
+\fB-p\fR
+Preserves the whole environment. This option is ignored if the \fB-l\fR
+option is specified.
diff --git a/ubase/su.c b/ubase/su.c
@@ -0,0 +1,127 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/types.h>
+
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "passwd.h"
+#include "util.h"
+
+extern char **environ;
+
+static int dologin(struct passwd *);
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-lp] [username]\n", argv0);
+}
+
+static int lflag = 0;
+static int pflag = 0;
+
+int
+main(int argc, char *argv[])
+{
+ char *usr = "root", *pass;
+ char *shell;
+ struct passwd *pw;
+ char *newargv[2];
+ uid_t uid;
+
+ ARGBEGIN {
+ case 'l':
+ lflag = 1;
+ break;
+ case 'p':
+ pflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ ;
+ else if (argc == 1)
+ usr = argv[0];
+ else
+ usage();
+
+ errno = 0;
+ pw = getpwnam(usr);
+ if (!pw) {
+ if (errno)
+ eprintf("getpwnam: %s:", usr);
+ else
+ eprintf("who are you?\n");
+ }
+
+ uid = getuid();
+ if (uid) {
+ pass = getpass("Password: ");
+ if (!pass)
+ eprintf("getpass:");
+ if (pw_check(pw, pass) <= 0)
+ exit(1);
+ }
+
+ if (initgroups(usr, pw->pw_gid) < 0)
+ eprintf("initgroups:");
+ if (setgid(pw->pw_gid) < 0)
+ eprintf("setgid:");
+ if (setuid(pw->pw_uid) < 0)
+ eprintf("setuid:");
+
+ if (lflag) {
+ return dologin(pw);
+ } else {
+ shell = pw->pw_shell[0] == '\0' ? "/bin/sh" : pw->pw_shell;
+ newargv[0] = shell;
+ newargv[1] = NULL;
+ if (!pflag) {
+ setenv("HOME", pw->pw_dir, 1);
+ setenv("SHELL", shell, 1);
+ if (strcmp(pw->pw_name, "root") != 0) {
+ setenv("USER", pw->pw_name, 1);
+ setenv("LOGNAME", pw->pw_name, 1);
+ }
+ }
+ if (strcmp(pw->pw_name, "root") == 0)
+ setenv("PATH", ENV_SUPATH, 1);
+ else
+ setenv("PATH", ENV_PATH, 1);
+ execve(pflag ? getenv("SHELL") : shell,
+ newargv, environ);
+ weprintf("execve %s:", shell);
+ return (errno == ENOENT) ? 127 : 126;
+ }
+ return 0;
+}
+
+static int
+dologin(struct passwd *pw)
+{
+ char *shell = pw->pw_shell[0] == '\0' ? "/bin/sh" : pw->pw_shell;
+ char *term = getenv("TERM");
+ clearenv();
+ setenv("HOME", pw->pw_dir, 1);
+ setenv("SHELL", shell, 1);
+ setenv("USER", pw->pw_name, 1);
+ setenv("LOGNAME", pw->pw_name, 1);
+ setenv("TERM", term ? term : "linux", 1);
+ if (strcmp(pw->pw_name, "root") == 0)
+ setenv("PATH", ENV_SUPATH, 1);
+ else
+ setenv("PATH", ENV_PATH, 1);
+ if (chdir(pw->pw_dir) < 0)
+ eprintf("chdir %s:", pw->pw_dir);
+ execlp(shell, shell, "-l", NULL);
+ weprintf("execlp %s:", shell);
+ return (errno == ENOENT) ? 127 : 126;
+}
diff --git a/ubase/swaplabel.8 b/ubase/swaplabel.8
@@ -0,0 +1,11 @@
+.TH SWAPLABEL 8 ubase-VERSION
+.SH NAME
+\fBswaplabel\fR - set the label of a swap filesystem
+.SH SYNOPSIS
+\fBswaplabel\fR [\fB-L\fI label\fR] \fIdevice\fR
+.SH DESCRIPTION
+\fBswaplabel\fR is used to change the label of a swap device or file.
+.SH OPTIONS
+.TP
+\fB-L\fR
+Change the label.
diff --git a/ubase/swaplabel.c b/ubase/swaplabel.c
@@ -0,0 +1,80 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+#define SWAP_MAGIC1 "SWAPSPACE2"
+#define SWAP_MAGIC2 "SWAP-SPACE"
+#define SWAP_MAGIC_LENGTH (10)
+#define SWAP_MAGIC_OFFSET (sysconf(_SC_PAGESIZE) - SWAP_MAGIC_LENGTH)
+#define SWAP_LABEL_LENGTH (16)
+#define SWAP_LABEL_OFFSET (1024 + 4 + 4 + 4 + 16)
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-L label] device\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int setlabel = 0;
+ int fd;
+ char magic[SWAP_MAGIC_LENGTH];
+ char *label;
+ char *device;
+ int i;
+
+ ARGBEGIN {
+ case 'L':
+ setlabel = 1;
+ label = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+ device = argv[0];
+
+ fd = open(device, O_RDWR);
+ if (fd < 0)
+ eprintf("open %s:", device);
+
+ if (lseek(fd, SWAP_MAGIC_OFFSET, SEEK_SET) != SWAP_MAGIC_OFFSET)
+ eprintf("failed seeking to magic position:");
+ if (read(fd, magic, SWAP_MAGIC_LENGTH) != SWAP_MAGIC_LENGTH)
+ eprintf("reading magic failed:");
+ if (memcmp(magic, SWAP_MAGIC1, 10) && memcmp(magic, SWAP_MAGIC2, 10))
+ eprintf("%s: is not a swap partition\n", device);
+ if (lseek(fd, SWAP_LABEL_OFFSET, SEEK_SET) != SWAP_LABEL_OFFSET)
+ eprintf("failed seeking to label position:");
+
+ if (!setlabel) {
+ label = emalloc(SWAP_LABEL_LENGTH);
+ if (read(fd, label, SWAP_LABEL_LENGTH) != SWAP_LABEL_LENGTH)
+ eprintf("reading label failed:");
+ for (i = 0; i < SWAP_LABEL_LENGTH && label[i] != '\0'; i++)
+ if (i == (SWAP_LABEL_LENGTH - 1) && label[i] != '\0')
+ eprintf("invalid label\n");
+ printf("label: %s\n", label);
+ free(label);
+ } else {
+ if (strlen(label) + 1 > SWAP_LABEL_LENGTH)
+ eprintf("label too long\n");
+ if (write(fd, label, strlen(label) + 1) != (ssize_t)strlen(label) + 1)
+ eprintf("writing label failed:");
+ }
+
+ fsync(fd);
+ close(fd);
+ return 0;
+}
diff --git a/ubase/swapoff.8 b/ubase/swapoff.8
@@ -0,0 +1,11 @@
+.TH SWAPOFF 8 ubase-VERSION
+.SH NAME
+\fBswapoff\fR - disable devices and files for paging and swapping
+.SH SYNOPSIS
+\fBswapoff\fR [\fB-a\fI device\fR]
+.SH DESCRIPTION
+\fBswapoff\fR disables swapping on the specified devices and files.
+.SH OPTIONS
+.TP
+\fB-a\fR
+Disable swapping on all known swap devices and files as found in /etc/fstab.
diff --git a/ubase/swapoff.c b/ubase/swapoff.c
@@ -0,0 +1,60 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/swap.h>
+
+#include <mntent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-a] device\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i;
+ int ret = 0;
+ int all = 0;
+
+ ARGBEGIN {
+ case 'a':
+ all = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (!all && argc < 1)
+ usage();
+
+ if (all) {
+ struct mntent *me = NULL;
+ FILE *fp;
+
+ fp = setmntent("/etc/fstab", "r");
+ if (!fp)
+ eprintf("setmntent %s:", "/etc/fstab");
+ while ((me = getmntent(fp)) != NULL) {
+ if (strcmp(me->mnt_type, MNTTYPE_SWAP) == 0) {
+ if (swapoff(me->mnt_fsname) < 0) {
+ weprintf("swapoff %s:", me->mnt_fsname);
+ ret = 1;
+ }
+ }
+ }
+ endmntent(fp);
+ } else {
+ for (i = 0; i < argc; i++) {
+ if (swapoff(argv[i]) < 0) {
+ weprintf("swapoff %s:", argv[i]);
+ ret = 1;
+ }
+ }
+ }
+ return ret;
+}
diff --git a/ubase/swapon.8 b/ubase/swapon.8
@@ -0,0 +1,19 @@
+.TH SWAPON 8 ubase-VERSION
+.SH NAME
+\fBswapon\fR - enable devices and files for paging and swapping
+.SH SYNOPSIS
+\fBswapon\fR [\fB-dp\fR] [\fB-a\fI device\fR]
+.SH DESCRIPTION
+\fBswapon\fR is used to specify devices on which paging and
+swapping are to take place.
+.SH OPTIONS
+.TP
+\fB-d\fR
+Discard freed swap pages before they are reused.
+.TP
+\fB-p\fR
+Set higher priority than the default to the new swap area.
+.TP
+\fB-a\fR
+Make all devices marked as ``swap'' in /etc/fstab are made
+available, except for those with the ``noauto'' option.
diff --git a/ubase/swapon.c b/ubase/swapon.c
@@ -0,0 +1,68 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/swap.h>
+
+#include <mntent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-dp] [-a] device\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i;
+ int ret = 0;
+ int flags = 0;
+ int all = 0;
+
+ ARGBEGIN {
+ case 'a':
+ all = 1;
+ break;
+ case 'd':
+ flags |= SWAP_FLAG_DISCARD;
+ break;
+ case 'p':
+ flags |= SWAP_FLAG_PREFER;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (!all && argc < 1)
+ usage();
+
+ if (all) {
+ struct mntent *me = NULL;
+ FILE *fp;
+
+ fp = setmntent("/etc/fstab", "r");
+ if (!fp)
+ eprintf("setmntent %s:", "/etc/fstab");
+ while ((me = getmntent(fp)) != NULL) {
+ if (strcmp(me->mnt_type, MNTTYPE_SWAP) == 0
+ && (hasmntopt(me, MNTOPT_NOAUTO) == NULL)) {
+ if (swapon(me->mnt_fsname, flags) < 0) {
+ weprintf("swapon %s:", me->mnt_fsname);
+ ret = 1;
+ }
+ }
+ }
+ endmntent(fp);
+ } else {
+ for (i = 0; i < argc; i++) {
+ if (swapon(argv[i], flags) < 0) {
+ weprintf("swapon %s:", argv[i]);
+ ret = 1;
+ }
+ }
+ }
+ return ret;
+}
diff --git a/ubase/switch_root.8 b/ubase/switch_root.8
@@ -0,0 +1,15 @@
+.TH SWITCH_ROOT 8 ubase-VERSION
+.SH NAME
+\fBswitch_root\fR - Switch to another filesystem as the root of the mount tree
+.SH SYNOPSIS
+\fBswitch_root\fR [\fB-c \fIconsole\fR] \fInewroot init
+.SH DESCRIPTION
+\fBswitch_root\fR removes all files and directories on the current root filesystem and overmounts it with \fInewroot\fR.
+If a \fIconsole\fR is specified, redirect stdio and stderr to it.
+After the switch, execute \fIinit\fR.
+.TP
+\fBswitch_root\fR can only be run as PID 1 in an initramfs or tmpfs with a regular and executable /init.
+.SH OPTIONS
+.TP
+\fB-c\fR
+Redirect stdio and stderr to \fIconsole\fR after switching to \fInewroot\fR.
diff --git a/ubase/switch_root.c b/ubase/switch_root.c
@@ -0,0 +1,131 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+#define RAMFS_MAGIC 0x858458f6 /* some random number */
+#define TMPFS_MAGIC 0x01021994
+
+static void
+delete_content(const char *dir, dev_t curdevice)
+{
+ char path[PATH_MAX];
+ DIR *d;
+ struct stat st;
+ struct dirent *dent;
+
+ /* don't dive into other filesystems */
+ if (lstat(dir, &st) < 0 || st.st_dev != curdevice)
+ return;
+ if (!(d = opendir(dir)))
+ return;
+ while ((dent = readdir(d))) {
+ if (strcmp(dent->d_name, ".") == 0 ||
+ strcmp(dent->d_name, "..") == 0)
+ continue;
+
+ /* build path and dive deeper */
+ if (strlcpy(path, dir, sizeof(path)) >= sizeof(path))
+ eprintf("path too long\n");
+ if (path[strlen(path) - 1] != '/')
+ if (strlcat(path, "/", sizeof(path)) >= sizeof(path))
+ eprintf("path too long\n");
+ if (strlcat(path, dent->d_name, sizeof(path)) >= sizeof(path))
+ eprintf("path too long\n");
+
+ if (lstat(path, &st) < 0)
+ weprintf("lstat %s:", path);
+
+ if (S_ISDIR(st.st_mode)) {
+ delete_content(path, curdevice);
+ if (rmdir(path) < 0)
+ weprintf("rmdir %s:", path);
+ } else {
+ if (unlink(path) < 0)
+ weprintf("unlink %s:", path);
+ }
+ }
+ closedir(d);
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-c console] [newroot] [init] (PID 1)\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *console = NULL;
+ dev_t curdev;
+ struct stat st;
+ struct statfs stfs;
+
+ ARGBEGIN {
+ case 'c':
+ console = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ /* check number of args and if we are PID 1 */
+ if (argc != 2 || getpid() != 1)
+ usage();
+
+ /* chdir to newroot and make sure it's a different fs */
+ if (chdir(argv[0]))
+ eprintf("chdir %s:", argv[0]);
+
+ if (stat("/", &st))
+ eprintf("stat %s:", "/");
+
+ curdev = st.st_dev;
+ if (stat(".", &st))
+ eprintf("stat %s:", ".");
+ if (st.st_dev == curdev)
+ usage();
+
+ /* avoids trouble with real filesystems */
+ if (stat("/init", &st) || !S_ISREG(st.st_mode))
+ eprintf("/init is not a regular file\n");
+
+ statfs("/", &stfs);
+ if ((unsigned)stfs.f_type != RAMFS_MAGIC && (unsigned)stfs.f_type != TMPFS_MAGIC)
+ eprintf("current filesystem is not a RAMFS or TMPFS\n");
+
+ /* wipe / */
+ delete_content("/", curdev);
+
+ /* overmount / with newroot and chroot into it */
+ if (mount(".", "/", NULL, MS_MOVE, NULL))
+ eprintf("mount %s:", ".");
+
+ if (chroot("."))
+ eprintf("chroot failed\n");
+
+ /* if -c is set, redirect stdin/stdout/stderr to console */
+ if (console) {
+ close(0);
+ if (open(console, O_RDWR) == -1)
+ eprintf("open %s:", console);
+ dup2(0, 1);
+ dup2(0, 2);
+ }
+
+ /* execute init */
+ execv(argv[1], argv);
+ eprintf("can't execute '%s'\n", argv[1]);
+ return 1;
+}
diff --git a/ubase/sysctl.8 b/ubase/sysctl.8
@@ -0,0 +1,13 @@
+.TH SYSCTL 8 ubase-VERSION
+.SH NAME
+\fBsysctl\fR - Configure kernel parameters at runtime
+.SH SYNOPSIS
+\fBsysctl\fR [\fB-p\fR \fIfile\fR] \fIvariable\fR[=\fIvalue\fR]...
+.SH DESCRIPTION
+\fBsysctl\fR modifies kernel parameters at runtime. The parameters available
+are those listed under \fI/proc/sys/\fR. Procfs is required for sysctl support
+in Linux. You can use \fBsysctl\fR to both read and write sysctl data.
+.SH OPTIONS
+.TP
+\fB-p\fR
+Load the sysctl key:value pairs from \fIfile\fR.
diff --git a/ubase/sysctl.c b/ubase/sysctl.c
@@ -0,0 +1,213 @@
+/* See LICENSE file for copyright and license details. */
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "text.h"
+#include "util.h"
+
+static void
+replacestr(char *s, int a, int b)
+{
+ for (; *s; s++)
+ if (*s == a)
+ *s = b;
+}
+
+static int
+getsysctl(char *variable, char **value)
+{
+ char path[PATH_MAX];
+ char *p;
+ char *buf, *tmp, c;
+ int fd;
+ ssize_t n;
+ size_t sz, i;
+
+ replacestr(variable, '.', '/');
+
+ strlcpy(path, "/proc/sys/", sizeof(path));
+ if (strlcat(path, variable, sizeof(path)) >= sizeof(path)) {
+ replacestr(variable, '/', '.');
+ return -1;
+ }
+
+ replacestr(variable, '/', '.');
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ i = 0;
+ sz = 1;
+ buf = NULL;
+ while (1) {
+ n = read(fd, &c, 1);
+ if (n < 0) {
+ close(fd);
+ free(buf);
+ return -1;
+ }
+ if (n == 0)
+ break;
+ if (i == sz - 1) {
+ sz *= 2;
+ tmp = realloc(buf, sz);
+ if (!tmp) {
+ close(fd);
+ free(buf);
+ return -1;
+ }
+ buf = tmp;
+ }
+ buf[i++] = c;
+ }
+ buf[i] = '\0';
+
+ p = strrchr(buf, '\n');
+ if (p)
+ *p = '\0';
+
+ *value = buf;
+
+ close(fd);
+
+ return 0;
+}
+
+static int
+setsysctl(char *variable, char *value)
+{
+ char path[PATH_MAX];
+ int fd;
+ ssize_t n;
+
+ replacestr(variable, '.', '/');
+
+ strlcpy(path, "/proc/sys/", sizeof(path));
+ if (strlcat(path, variable, sizeof(path)) >= sizeof(path)) {
+ replacestr(variable, '/', '.');
+ return -1;
+ }
+
+ replacestr(variable, '/', '.');
+
+ fd = open(path, O_WRONLY);
+ if (fd < 0)
+ return -1;
+
+ n = write(fd, value, strlen(value));
+ if ((size_t)n != strlen(value)) {
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+
+ return 0;
+}
+
+static int
+parsepair(char *pair)
+{
+ char *p;
+ char *variable;
+ char *value;
+
+ for (p = pair; *p; p++) {
+ if (p[0] == '.' && p[1] == '.') {
+ weprintf("malformed input: %s\n", pair);
+ return -1;
+ }
+ }
+ p = strchr(pair, '=');
+ if (p) {
+ if (p[1] == '\0') {
+ weprintf("malformed input: %s\n", pair);
+ return -1;
+ }
+ *p = '\0';
+ value = &p[1];
+ } else {
+ value = NULL;
+ }
+ variable = pair;
+ if (value) {
+ if (setsysctl(variable, value) < 0) {
+ weprintf("failed to set sysctl for %s\n", variable);
+ return -1;
+ }
+ } else {
+ if (getsysctl(variable, &value) < 0) {
+ weprintf("failed to get sysctl for %s\n", variable);
+ return -1;
+ }
+ printf("%s = %s\n", variable, value);
+ free(value);
+ }
+
+ return 0;
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-p file] variable[=value]...\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+ char *buf = NULL, *p;
+ char *file = NULL;
+ size_t size = 0;
+ int i;
+ int r = 0;
+
+ ARGBEGIN {
+ case 'p':
+ file = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (!file && argc < 1)
+ usage();
+
+ if (!file) {
+ for (i = 0; i < argc; i++)
+ if (parsepair(argv[i]) < 0)
+ r = 1;
+ } else {
+ fp = fopen(file, "r");
+ if (!fp)
+ eprintf("fopen %s:", file);
+ while (agetline(&buf, &size, fp) != -1) {
+ p = buf;
+ for (p = buf; *p == ' ' || *p == '\t'; p++)
+ ;
+ if (*p == '#' || *p == '\n')
+ continue;
+ for (p = buf; *p; p++) {
+ if (*p == '\n') {
+ *p = '\0';
+ break;
+ }
+ }
+ p = buf;
+ if (parsepair(p) < 0)
+ r = 1;
+ }
+ if (ferror(fp))
+ eprintf("%s: read error:", file);
+ free(buf);
+ fclose(fp);
+ }
+
+ return r;
+}
diff --git a/ubase/text.h b/ubase/text.h
@@ -0,0 +1,13 @@
+/* See LICENSE file for copyright and license details. */
+
+struct linebuf {
+ char **lines;
+ long nlines;
+ long capacity;
+};
+#define EMPTY_LINEBUF {NULL, 0, 0,}
+void getlines(FILE *, struct linebuf *);
+
+ssize_t agetline(char **, size_t *, FILE *);
+
+void concat(FILE *, const char *, FILE *, const char *);
diff --git a/ubase/truncate.1 b/ubase/truncate.1
@@ -0,0 +1,20 @@
+.TH TRUNCATE 1 ubase-VERSION
+.SH NAME
+\fBtruncate\fR - Shrink or extend the size of a file to the specified size
+.SH SYNOPSIS
+\fBtruncate\fR [\fB-c\fR] \fB-s\fI size file...\fR
+.SH DESCRIPTION
+\fBtruncate\fR shrinks or extends the size of each \fIfile\fR to the
+specified size. A \fIfile\fR argument that does not exist is created.
+If a \fIfile\fR is larger than the specified size, the extra data is lost.
+If a \fIfile\fR is shorter, it is extended and the extended part (hole) reads
+as zero bytes.
+.SH OPTIONS
+.TP
+\fB-c\fR
+Do not create any files.
+.TP
+\fB-s\fR
+Set or adjust the file size by \fIsize\fR bytes.
+.SH SEE ALSO
+truncate(2), ftruncate(2)
diff --git a/ubase/truncate.c b/ubase/truncate.c
@@ -0,0 +1,53 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-c] -s size file...\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int cflag = 0, sflag = 0;
+ int fd, i, ret = 0;
+ long size = 0;
+
+ ARGBEGIN {
+ case 's':
+ sflag = 1;
+ size = estrtol(EARGF(usage()), 10);
+ break;
+ case 'c':
+ cflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1 || sflag == 0)
+ usage();
+
+ for (i = 0; i < argc; i++) {
+ fd = open(argv[i], O_WRONLY | (cflag ? 0 : O_CREAT), 0644);
+ if (fd < 0) {
+ weprintf("open: cannot open `%s' for writing:", argv[i]);
+ ret = 1;
+ continue;
+ }
+ if (ftruncate(fd, size) < 0) {
+ weprintf("ftruncate: cannot open `%s' for writing:", argv[i]);
+ ret = 1;
+ }
+ close(fd);
+ }
+ return ret;
+}
diff --git a/ubase/umount.8 b/ubase/umount.8
@@ -0,0 +1,30 @@
+.TH UMOUNT 8 ubase-VERSION
+.SH NAME
+\fBumount\fR - Unmount file systems
+.SH SYNOPSIS
+\fBumount\fR [\fB-lfn\fR] \fItarget...\fR
+.TP
+\fBumount\fR \fB-a\fR [\fB-lfn\fR]
+.SH DESCRIPTION
+\fBumount\fR detaches the \fItarget\fR filesystem(s).
+A file system is specified by giving the directory where it
+has been mounted. Giving the special device on which the file system
+lives may also work, but is obsolete, mainly because it will fail in
+case this device was mounted on more than one directory.
+.SH OPTIONS
+.TP
+\fB-l\fR
+Lazy unmount. Detach the filesystem from the fs hierarchy now, and cleanup
+all references to the filesystem as soon as it is not busy anymore.
+.TP
+\fB-f\fR
+Force unmount (in case of an unreachable NFS server).
+.TP
+\fB-n\fR
+Unmount without writing in /etc/mtab. This is the default action.
+.TP
+\fB-a\fR
+All of the file systems described in /proc/mounts are unmounted. The
+proc filesystem is not unmounted.
+.SH SEE ALSO
+umount(2), mount(8)
diff --git a/ubase/umount.c b/ubase/umount.c
@@ -0,0 +1,88 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/mount.h>
+
+#include <mntent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+static int umountall(int);
+
+static void
+usage(void)
+{
+ weprintf("usage: %s [-lfn] target...\n", argv0);
+ weprintf("usage: %s -a [-lfn]\n", argv0);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i;
+ int aflag = 0;
+ int flags = 0;
+ int ret = 0;
+
+ ARGBEGIN {
+ case 'a':
+ aflag = 1;
+ break;
+ case 'f':
+ flags |= MNT_FORCE;
+ break;
+ case 'l':
+ flags |= MNT_DETACH;
+ break;
+ case 'n':
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1 && aflag == 0)
+ usage();
+
+ if (aflag == 1)
+ return umountall(flags);
+
+ for (i = 0; i < argc; i++) {
+ if (umount2(argv[i], flags) < 0) {
+ weprintf("umount2 %s:", argv[i]);
+ ret = 1;
+ }
+ }
+ return ret;
+}
+
+static int
+umountall(int flags)
+{
+ FILE *fp;
+ struct mntent *me;
+ int ret;
+ char **mntdirs = NULL;
+ int len = 0;
+
+ fp = setmntent("/proc/mounts", "r");
+ if (!fp)
+ eprintf("setmntent %s:", "/proc/mounts");
+ while ((me = getmntent(fp))) {
+ if (strcmp(me->mnt_type, "proc") == 0)
+ continue;
+ mntdirs = erealloc(mntdirs, ++len * sizeof(*mntdirs));
+ mntdirs[len - 1] = estrdup(me->mnt_dir);
+ }
+ endmntent(fp);
+ while (--len >= 0) {
+ if (umount2(mntdirs[len], flags) < 0) {
+ weprintf("umount2 %s:", mntdirs[len]);
+ ret = 1;
+ }
+ free(mntdirs[len]);
+ }
+ free(mntdirs);
+ return ret;
+}
diff --git a/ubase/unshare.1 b/ubase/unshare.1
@@ -0,0 +1,43 @@
+.TH UNSHARE 1 ubase-VERSION
+.SH NAME
+\fBunshare\fR - run program with some namespaces unshared from parent
+.SH SYNOPSIS
+\fBunshare\fR [\fB-muinpU\fR] cmd [\fIargs...\fR]
+.SH DESCRIPTION
+\fBunshare\fR
+Unshares the indicated namespaces from the parent process
+and then executes the specified program. The namespaces to be unshared are
+indicated via options.
+.SH OPTIONS
+.TP
+\fB-m\fR
+Unshare the mount namespace, so that the calling process has a private
+copy of its namespace which is not shared with any other process.
+This flag has the same effect as the \fBclone(2) CLONE_NEWNS\fR flag.
+.TP
+\fB-u\fR
+Unshare the UTS IPC namespace, so that the calling process has a
+private copy of the UTS namespace which is not shared with any other
+process. This flag has the same effect as the \fBclone(2) CLONE_NEWUTS\fR
+flag.
+.TP
+\fB-i\fR
+Unshare the System V IPC namespace, so that the calling process has a
+private copy of the System V IPC namespace which is not shared with
+any other process. This flag has the same effect as the \fBclone(2)
+CLONE_NEWIPC\fR flag
+.TP
+\fB-n\fR
+Unshare the network namespace, so that the calling process is moved
+into a new network namespace which is not shared with any previously
+existing process. This flag has the same effect as the \fBclone(2)
+CLONE_NEWNET\fR flag.
+.TP
+\fB-p\fR
+Create the process in a new PID namespace. This flag has the same
+effect as the \fBclone(2) CLONE_NEWPID\fR flag.
+.TP
+\fB-U\fR
+The process will have a distinct set of UIDs, GIDs and capabilities.
+.SH SEE ALSO
+clone(2), unshare(2)
diff --git a/ubase/unshare.c b/ubase/unshare.c
@@ -0,0 +1,54 @@
+/* See LICENSE file for copyright and license details. */
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-muinpU] cmd [args...]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int flags = 0;
+
+ ARGBEGIN {
+ case 'm':
+ flags |= CLONE_NEWNS;
+ break;
+ case 'u':
+ flags |= CLONE_NEWUTS;
+ break;
+ case 'i':
+ flags |= CLONE_NEWIPC;
+ break;
+ case 'n':
+ flags |= CLONE_NEWNET;
+ break;
+ case 'p':
+ flags |= CLONE_NEWPID;
+ break;
+ case 'U':
+ flags |= CLONE_NEWUSER;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ if (unshare(flags) < 0)
+ eprintf("unshare:");
+
+ if (execvp(argv[0], argv) < 0)
+ eprintf("execvp:");
+
+ return 0;
+}
diff --git a/ubase/uptime.1 b/ubase/uptime.1
@@ -0,0 +1,12 @@
+.TH UPTIME 1 ubase-VERSION
+.SH NAME
+\fBuptime\fR - Tell how long the system has been running
+.SH SYNOPSIS
+\fBuptime\fR
+.SH DESCRIPTION
+\fBuptime\fR gives a one line display of the following information.
+The current time, how long the system has been running, how many users are
+currently logged on and the system load averages for the past 1, 5 and
+15 minutes.
+.SH SEE ALSO
+ps(1), utmp(5)
diff --git a/ubase/uptime.c b/ubase/uptime.c
@@ -0,0 +1,73 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/sysinfo.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <utmpx.h>
+
+#include "config.h"
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct utmpx utx;
+ FILE *ufp;
+ struct sysinfo info;
+ time_t tmptime;
+ struct tm *now;
+ unsigned int days, hours, minutes;
+ int nusers = 0;
+ size_t n;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (sysinfo(&info) < 0)
+ eprintf("sysinfo:");
+ time(&tmptime);
+ now = localtime(&tmptime);
+ printf(" %02d:%02d:%02d up ", now->tm_hour, now->tm_min, now->tm_sec);
+ info.uptime /= 60;
+ minutes = info.uptime % 60;
+ info.uptime /= 60;
+ hours = info.uptime % 24;
+ days = info.uptime / 24;
+ if (days)
+ printf("%d day%s, ", days, days > 1 ? "s" : "");
+ if (hours)
+ printf("%2d:%02d, ", hours, minutes);
+ else
+ printf("%d min, ", minutes);
+
+ if ((ufp = fopen(UTMP_PATH, "r"))) {
+ while ((n = fread(&utx, sizeof(utx), 1, ufp)) > 0) {
+ if (!utx.ut_user[0])
+ continue;
+ if (utx.ut_type != USER_PROCESS)
+ continue;
+ nusers++;
+ }
+ if (ferror(ufp))
+ eprintf("/var/run/utmp: read error:");
+ fclose(ufp);
+ printf(" %d user%s, ", nusers, nusers > 1 ? "s" : "");
+ }
+
+ printf(" load average: %.02f, %.02f, %.02f\n",
+ info.loads[0] / 65536.0f,
+ info.loads[1] / 65536.0f,
+ info.loads[2] / 65536.0f);
+
+ return 0;
+}
diff --git a/ubase/util.h b/ubase/util.h
@@ -0,0 +1,53 @@
+/* See LICENSE file for copyright and license details. */
+#include "arg.h"
+
+#define UTF8_POINT(c) (((c) & 0xc0) != 0x80)
+#define LEN(x) (sizeof (x) / sizeof *(x))
+
+/* eprintf.c */
+extern char *argv0;
+
+/* agetcwd.c */
+char *agetcwd(void);
+
+/* apathmax.c */
+void apathmax(char **, long *);
+
+/* ealloc.c */
+void *ecalloc(size_t, size_t);
+void *emalloc(size_t size);
+void *erealloc(void *, size_t);
+char *estrdup(const char *);
+
+/* eprintf.c */
+void enprintf(int, const char *, ...);
+void eprintf(const char *, ...);
+void weprintf(const char *, ...);
+
+/* estrtol.c */
+long estrtol(const char *, int);
+
+/* estrtoul.c */
+unsigned long estrtoul(const char *, int);
+
+/* explicit_bzero.c */
+#undef explicit_bzero
+void explicit_bzero(void *, size_t);
+
+/* putword.c */
+void putword(const char *);
+
+/* recurse.c */
+void recurse(const char *, void (*)(const char *));
+
+/* strlcpy.c */
+#undef strlcat
+size_t strlcat(char *, const char *, size_t);
+
+/* strlcat.c */
+#undef strlcpy
+size_t strlcpy(char *, const char *, size_t);
+
+/* tty.c */
+void devtotty(int, int *, int *);
+char *ttytostr(int, int);
diff --git a/ubase/vtallow.1 b/ubase/vtallow.1
@@ -0,0 +1,22 @@
+.Dd December 5, 2014
+.Dt VTALLOW 1 ubase\-VERSION
+.Os
+.Sh NAME
+.Nm vtallow
+.Nd enable or disable the VT switch
+.Sh SYNOPSIS
+.Nm vtallow
+.Ar n | Ar y
+.Sh DESCRIPTION
+.Nm
+controls the VT switch lock.
+.Pp
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Ar n
+Disable VT switching.
+.It Ar y
+Enable VT switching.
+.El
+.Sh SEE ALSO
+.Xr chvt 1
diff --git a/ubase/vtallow.c b/ubase/vtallow.c
@@ -0,0 +1,52 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+#define CONSOLE "/dev/console"
+
+#define VT_LOCKSWITCH 0x560B /* disallow vt switching */
+#define VT_UNLOCKSWITCH 0x560C /* allow vt switching */
+
+static void
+usage(void)
+{
+ eprintf("usage: vtallow n | y\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ int allow;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc != 1)
+ usage();
+
+ if (!strcmp(argv[0], "y"))
+ allow = 1;
+ else if (!strcmp(argv[0], "n"))
+ allow = 0;
+ else
+ usage();
+
+ if ((fd = open(CONSOLE, O_WRONLY)) < 0)
+ eprintf("open %s:", CONSOLE);
+ if (ioctl(fd, allow ? VT_UNLOCKSWITCH : VT_LOCKSWITCH) < 0)
+ eprintf("cannot %s VT switch:",
+ allow ? "unlock" : "lock");
+ close(fd);
+ return 0;
+}
diff --git a/ubase/watch.1 b/ubase/watch.1
@@ -0,0 +1,19 @@
+.TH WATCH 1 ubase-VERSION
+.SH NAME
+\fBwatch\fR - Execute a program periodically, showing output fullscreen
+.SH SYNOPSIS
+\fBwatch\fR [\fB-t\fR] [\fB-n\fI interval\fR] \fIcommand\fR
+.SH DESCRIPTION
+\fBwatch\fR runs \fIcommand\fR repeatedly, displaying its output one
+screenfull at a time. This allows you to watch the program output change
+over time. By default the program is run every 2 seconds and will run
+until terminated.
+.SH OPTIONS
+.TP
+\fB-t\fR
+Turn off the header showing the interval, command and current
+time at the top of the display, as well as the following blank line. This
+is the default action.
+.TP
+\fB-n\fR
+Specify the update interval.
diff --git a/ubase/watch.c b/ubase/watch.c
@@ -0,0 +1,58 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [-t] [-n interval] command\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char cmd[BUFSIZ];
+ char *end;
+ useconds_t interval = 2 * 1E6;
+ float period;
+ int i;
+
+ ARGBEGIN {
+ case 't':
+ break;
+ case 'n':
+ period = strtof(EARGF(usage()), &end);
+ if (*end != '\0' || errno != 0)
+ eprintf("invalid interval\n");
+ if (period < 0)
+ period = 0.1f;
+ interval = period * 1E6;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ if (strlcpy(cmd, argv[0], sizeof(cmd)) >= sizeof(cmd))
+ eprintf("command too long\n");
+ for (i = 1; i < argc; i++) {
+ if (strlcat(cmd, " ", sizeof(cmd)) >= sizeof(cmd))
+ eprintf("command too long\n");
+ if (strlcat(cmd, argv[i], sizeof(cmd)) >= sizeof(cmd))
+ eprintf("command too long\n");
+ }
+
+ for (;;) {
+ printf("\x1b[2J\x1b[H"); /* clear */
+ fflush(NULL);
+ system(cmd);
+ usleep(interval);
+ }
+ return 0;
+}
diff --git a/ubase/who.1 b/ubase/who.1
@@ -0,0 +1,21 @@
+.TH WHO 1 ubase-VERSION
+.SH NAME
+\fBwho\fR - Print who has logged on
+.SH SYNOPSIS
+\fBwho\fR [\fB-ml\fR]
+.SH DESCRIPTION
+\fBwho\fR prints a list of who has logged on, their controlling tty, and the
+time at which they logged on.
+.SH OPTIONS
+.TP
+\fB-m\fR
+Only show users on current tty.
+.TP
+\fB-l\fR
+Print LOGIN processes as well.
+.SH BUGS
+\fBwho\fR relies on the utmp file to be updated responsibly. This
+doesn't always happen, which can cause who to print completely
+bogus data.
+.SH SEE ALSO
+utmp(5)
diff --git a/ubase/who.c b/ubase/who.c
@@ -0,0 +1,64 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <utmp.h>
+
+#include "config.h"
+#include "util.h"
+
+static void
+usage(void)
+{
+ eprintf("usage: who [-ml]\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct utmp usr;
+ FILE *ufp;
+ char timebuf[sizeof "yyyy-mm-dd hh:mm"];
+ char *tty, *ttmp;
+ int mflag = 0, lflag = 0;
+ time_t t;
+
+ ARGBEGIN {
+ case 'm':
+ mflag = 1;
+ tty = ttyname(0);
+ if (!tty)
+ eprintf("ttyname: stdin:");
+ if ((ttmp = strrchr(tty, '/')))
+ tty = ttmp+1;
+ break;
+ case 'l':
+ lflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc > 0)
+ usage();
+
+ if (!(ufp = fopen(UTMP_PATH, "r")))
+ eprintf("fopen: %s:", UTMP_PATH);
+
+ while (fread(&usr, sizeof(usr), 1, ufp) == 1) {
+ if (!*usr.ut_name || !*usr.ut_line ||
+ usr.ut_line[0] == '~')
+ continue;
+ if (mflag != 0 && strcmp(usr.ut_line, tty) != 0)
+ continue;
+ if (!!strcmp(usr.ut_name, "LOGIN") == lflag)
+ continue;
+ t = usr.ut_time;
+ strftime(timebuf, sizeof timebuf, "%Y-%m-%d %H:%M", localtime(&t));
+ printf("%-8s %-12s %-16s\n", usr.ut_name, usr.ut_line, timebuf);
+ }
+ fclose(ufp);
+ return 0;
+}