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 */
+ }